replace mint with weth-pegged deposit and withdraw
92dc0c7 parent: 7c6b16b modified
script/Love.s.sol +32 -12 | @@ -2,25 +2,32 @@ | ||
| 2 | 2 | pragma solidity ^0.8.30; |
| 3 | 3 | |
| 4 | 4 | import {Love} from "../src/Love.sol"; |
| 5 | +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | |
| 5 | 6 | import {Script, console} from "forge-std/Script.sol"; |
| 6 | 7 | |
| 7 | 8 | /// @notice Deploys `Love` with CREATE2 through the canonical deterministic |
| 8 | -/// deployer, so the token gets the same address on every EVM network. | |
| 9 | -/// The address depends only on the salt and the creation code — keep | |
| 9 | +/// deployer. The address depends on the salt and the creation code, and | |
| 10 | +/// the creation code embeds the wETH address — so the token only gets | |
| 11 | +/// the same address on chains where wETH sits at the same address. Keep | |
| 10 | 12 | /// `solc`, the optimizer settings and `bytecode_hash = "none"` as they |
| 11 | -/// are in `foundry.toml`, or the address changes. | |
| 13 | +/// are in `foundry.toml`, or the address changes too. | |
| 12 | 14 | contract LoveScript is Script { |
| 13 | 15 | bytes32 public constant DEFAULT_SALT = keccak256("LOVE"); |
| 14 | 16 | |
| 17 | + /// @dev wETH on Base, Optimism and every other OP-Stack chain. | |
| 18 | + address public constant DEFAULT_WETH = 0x4200000000000000000000000000000000000006; | |
| 19 | + | |
| 15 | 20 | Love public love; |
| 16 | 21 | |
| 17 | 22 | function run() public returns (Love) { |
| 18 | 23 | bytes32 s = salt(); |
| 19 | - address predicted = predict(s); | |
| 24 | + address w = wethAddress(); | |
| 25 | + address predicted = predict(s, w); | |
| 20 | 26 | |
| 21 | 27 | console.log("deployer ", CREATE2_FACTORY); |
| 22 | 28 | console.log("salt ", vm.toString(s)); |
| 23 | - console.log("initCodeHash ", vm.toString(initCodeHash())); | |
| 29 | + console.log("weth ", w); | |
| 30 | + console.log("initCodeHash ", vm.toString(initCodeHash(w))); | |
| 24 | 31 | console.log("predicted ", predicted); |
| 25 | 32 | |
| 26 | 33 | if (predicted.code.length > 0) { |
| @@ -30,7 +37,7 @@ contract LoveScript is Script { | ||
| 30 | 37 | } |
| 31 | 38 | |
| 32 | 39 | vm.startBroadcast(); |
| 33 | - love = new Love{salt: s}(); | |
| 40 | + love = new Love{salt: s}(IERC20(w)); | |
| 34 | 41 | vm.stopBroadcast(); |
| 35 | 42 | |
| 36 | 43 | require(address(love) == predicted, "LoveScript: address mismatch"); |
| @@ -38,6 +45,8 @@ contract LoveScript is Script { | ||
| 38 | 45 | console.log("Love deployed at", address(love)); |
| 39 | 46 | console.log("name ", love.name()); |
| 40 | 47 | console.log("symbol ", love.symbol()); |
| 48 | + console.log("weth ", address(love.weth())); | |
| 49 | + console.log("rate ", love.RATE()); | |
| 41 | 50 | console.log("totalSupply ", love.totalSupply()); |
| 42 | 51 | |
| 43 | 52 | return love; |
| @@ -46,23 +55,34 @@ contract LoveScript is Script { | ||
| 46 | 55 | /// @notice Print the address `run()` would deploy to, without broadcasting. |
| 47 | 56 | function predict() public view returns (address predicted) { |
| 48 | 57 | bytes32 s = salt(); |
| 49 | - predicted = predict(s); | |
| 58 | + address w = wethAddress(); | |
| 59 | + predicted = predict(s, w); | |
| 50 | 60 | |
| 51 | 61 | console.log("salt ", vm.toString(s)); |
| 52 | - console.log("initCodeHash ", vm.toString(initCodeHash())); | |
| 62 | + console.log("weth ", w); | |
| 63 | + console.log("initCodeHash ", vm.toString(initCodeHash(w))); | |
| 53 | 64 | console.log("predicted ", predicted); |
| 54 | 65 | } |
| 55 | 66 | |
| 56 | - function predict(bytes32 s) public pure returns (address) { | |
| 57 | - return vm.computeCreate2Address(s, initCodeHash(), CREATE2_FACTORY); | |
| 67 | + function predict(bytes32 s, address w) public pure returns (address) { | |
| 68 | + return vm.computeCreate2Address(s, initCodeHash(w), CREATE2_FACTORY); | |
| 69 | + } | |
| 70 | + | |
| 71 | + function initCode(address w) public pure returns (bytes memory) { | |
| 72 | + return abi.encodePacked(type(Love).creationCode, abi.encode(w)); | |
| 58 | 73 | } |
| 59 | 74 | |
| 60 | - function initCodeHash() public pure returns (bytes32) { | |
| 61 | - return keccak256(type(Love).creationCode); | |
| 75 | + function initCodeHash(address w) public pure returns (bytes32) { | |
| 76 | + return keccak256(initCode(w)); | |
| 62 | 77 | } |
| 63 | 78 | |
| 64 | 79 | /// @dev `SALT` overrides the default, e.g. to mine a vanity address. |
| 65 | 80 | function salt() public view returns (bytes32) { |
| 66 | 81 | return vm.envOr("SALT", DEFAULT_SALT); |
| 67 | 82 | } |
| 83 | + | |
| 84 | + /// @dev `WETH` overrides the default, which only holds on OP-Stack chains. | |
| 85 | + function wethAddress() public view returns (address) { | |
| 86 | + return vm.envOr("WETH", DEFAULT_WETH); | |
| 87 | + } | |
| 68 | 88 | } |
| @@ -2,25 +2,32 @@ | |||
| 2 | pragma solidity ^0.8.30; | 2 | pragma solidity ^0.8.30; |
| 3 | 3 | ||
| 4 | import {Love} from "../src/Love.sol"; | 4 | import {Love} from "../src/Love.sol"; |
| 5 | +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
| 5 | import {Script, console} from "forge-std/Script.sol"; | 6 | import {Script, console} from "forge-std/Script.sol"; |
| 6 | 7 | ||
| 7 | /// @notice Deploys `Love` with CREATE2 through the canonical deterministic | 8 | /// @notice Deploys `Love` with CREATE2 through the canonical deterministic |
| 8 | -/// deployer, so the token gets the same address on every EVM network. | 9 | +/// deployer. The address depends on the salt and the creation code, and |
| 9 | -/// The address depends only on the salt and the creation code — keep | 10 | +/// the creation code embeds the wETH address — so the token only gets |
| 11 | +/// the same address on chains where wETH sits at the same address. Keep | ||
| 10 | /// `solc`, the optimizer settings and `bytecode_hash = "none"` as they | 12 | /// `solc`, the optimizer settings and `bytecode_hash = "none"` as they |
| 11 | -/// are in `foundry.toml`, or the address changes. | 13 | +/// are in `foundry.toml`, or the address changes too. |
| 12 | contract LoveScript is Script { | 14 | contract LoveScript is Script { |
| 13 | bytes32 public constant DEFAULT_SALT = keccak256("LOVE"); | 15 | bytes32 public constant DEFAULT_SALT = keccak256("LOVE"); |
| 14 | 16 | ||
| 17 | + /// @dev wETH on Base, Optimism and every other OP-Stack chain. | ||
| 18 | + address public constant DEFAULT_WETH = 0x4200000000000000000000000000000000000006; | ||
| 19 | + | ||
| 15 | Love public love; | 20 | Love public love; |
| 16 | 21 | ||
| 17 | function run() public returns (Love) { | 22 | function run() public returns (Love) { |
| 18 | bytes32 s = salt(); | 23 | bytes32 s = salt(); |
| 19 | - address predicted = predict(s); | 24 | + address w = wethAddress(); |
| 25 | + address predicted = predict(s, w); | ||
| 20 | 26 | ||
| 21 | console.log("deployer ", CREATE2_FACTORY); | 27 | console.log("deployer ", CREATE2_FACTORY); |
| 22 | console.log("salt ", vm.toString(s)); | 28 | console.log("salt ", vm.toString(s)); |
| 23 | - console.log("initCodeHash ", vm.toString(initCodeHash())); | 29 | + console.log("weth ", w); |
| 30 | + console.log("initCodeHash ", vm.toString(initCodeHash(w))); | ||
| 24 | console.log("predicted ", predicted); | 31 | console.log("predicted ", predicted); |
| 25 | 32 | ||
| 26 | if (predicted.code.length > 0) { | 33 | if (predicted.code.length > 0) { |
| @@ -30,7 +37,7 @@ contract LoveScript is Script { | |||
| 30 | } | 37 | } |
| 31 | 38 | ||
| 32 | vm.startBroadcast(); | 39 | vm.startBroadcast(); |
| 33 | - love = new Love{salt: s}(); | 40 | + love = new Love{salt: s}(IERC20(w)); |
| 34 | vm.stopBroadcast(); | 41 | vm.stopBroadcast(); |
| 35 | 42 | ||
| 36 | require(address(love) == predicted, "LoveScript: address mismatch"); | 43 | require(address(love) == predicted, "LoveScript: address mismatch"); |
| @@ -38,6 +45,8 @@ contract LoveScript is Script { | |||
| 38 | console.log("Love deployed at", address(love)); | 45 | console.log("Love deployed at", address(love)); |
| 39 | console.log("name ", love.name()); | 46 | console.log("name ", love.name()); |
| 40 | console.log("symbol ", love.symbol()); | 47 | console.log("symbol ", love.symbol()); |
| 48 | + console.log("weth ", address(love.weth())); | ||
| 49 | + console.log("rate ", love.RATE()); | ||
| 41 | console.log("totalSupply ", love.totalSupply()); | 50 | console.log("totalSupply ", love.totalSupply()); |
| 42 | 51 | ||
| 43 | return love; | 52 | return love; |
| @@ -46,23 +55,34 @@ contract LoveScript is Script { | |||
| 46 | /// @notice Print the address `run()` would deploy to, without broadcasting. | 55 | /// @notice Print the address `run()` would deploy to, without broadcasting. |
| 47 | function predict() public view returns (address predicted) { | 56 | function predict() public view returns (address predicted) { |
| 48 | bytes32 s = salt(); | 57 | bytes32 s = salt(); |
| 49 | - predicted = predict(s); | 58 | + address w = wethAddress(); |
| 59 | + predicted = predict(s, w); | ||
| 50 | 60 | ||
| 51 | console.log("salt ", vm.toString(s)); | 61 | console.log("salt ", vm.toString(s)); |
| 52 | - console.log("initCodeHash ", vm.toString(initCodeHash())); | 62 | + console.log("weth ", w); |
| 63 | + console.log("initCodeHash ", vm.toString(initCodeHash(w))); | ||
| 53 | console.log("predicted ", predicted); | 64 | console.log("predicted ", predicted); |
| 54 | } | 65 | } |
| 55 | 66 | ||
| 56 | - function predict(bytes32 s) public pure returns (address) { | 67 | + function predict(bytes32 s, address w) public pure returns (address) { |
| 57 | - return vm.computeCreate2Address(s, initCodeHash(), CREATE2_FACTORY); | 68 | + return vm.computeCreate2Address(s, initCodeHash(w), CREATE2_FACTORY); |
| 69 | + } | ||
| 70 | + | ||
| 71 | + function initCode(address w) public pure returns (bytes memory) { | ||
| 72 | + return abi.encodePacked(type(Love).creationCode, abi.encode(w)); | ||
| 58 | } | 73 | } |
| 59 | 74 | ||
| 60 | - function initCodeHash() public pure returns (bytes32) { | 75 | + function initCodeHash(address w) public pure returns (bytes32) { |
| 61 | - return keccak256(type(Love).creationCode); | 76 | + return keccak256(initCode(w)); |
| 62 | } | 77 | } |
| 63 | 78 | ||
| 64 | /// @dev `SALT` overrides the default, e.g. to mine a vanity address. | 79 | /// @dev `SALT` overrides the default, e.g. to mine a vanity address. |
| 65 | function salt() public view returns (bytes32) { | 80 | function salt() public view returns (bytes32) { |
| 66 | return vm.envOr("SALT", DEFAULT_SALT); | 81 | return vm.envOr("SALT", DEFAULT_SALT); |
| 67 | } | 82 | } |
| 83 | + | ||
| 84 | + /// @dev `WETH` overrides the default, which only holds on OP-Stack chains. | ||
| 85 | + function wethAddress() public view returns (address) { | ||
| 86 | + return vm.envOr("WETH", DEFAULT_WETH); | ||
| 87 | + } | ||
| 68 | } | 88 | } |
modified
script/multichain-check.sh +7 -4 | @@ -1,11 +1,13 @@ | ||
| 1 | 1 | #!/usr/bin/env bash |
| 2 | 2 | # |
| 3 | 3 | # Deploys Love on two local anvil nodes with different chain ids and checks |
| 4 | -# that CREATE2 gives the token the same address on both. | |
| 4 | +# that CREATE2 gives the token the same address on both. The address embeds the | |
| 5 | +# wETH address, so this only holds for chains sharing one wETH deployment — | |
| 6 | +# both nodes here are given the same WETH. | |
| 5 | 7 | # |
| 6 | 8 | # Usage: ./script/multichain-check.sh |
| 7 | 9 | # |
| 8 | -# Env overrides: PORT_A, PORT_B, CHAIN_ID_A, CHAIN_ID_B, SALT | |
| 10 | +# Env overrides: PORT_A, PORT_B, CHAIN_ID_A, CHAIN_ID_B, SALT, WETH | |
| 9 | 11 | |
| 10 | 12 | set -euo pipefail |
| 11 | 13 | |
| @@ -77,10 +79,11 @@ report() { | ||
| 77 | 79 | exit 1 |
| 78 | 80 | fi |
| 79 | 81 | |
| 80 | - printf '%s %s %s bytes %s / %s\n' \ | |
| 82 | + printf '%s %s %s bytes %s / %s weth %s\n' \ | |
| 81 | 83 | "$label" "$addr" "$(((${#code} - 2) / 2))" \ |
| 82 | 84 | "$(cast call "$addr" 'name()(string)' --rpc-url "$rpc")" \ |
| 83 | - "$(cast call "$addr" 'symbol()(string)' --rpc-url "$rpc")" | |
| 85 | + "$(cast call "$addr" 'symbol()(string)' --rpc-url "$rpc")" \ | |
| 86 | + "$(cast call "$addr" 'weth()(address)' --rpc-url "$rpc")" | |
| 84 | 87 | } |
| 85 | 88 | |
| 86 | 89 | cd "$ROOT" |
| @@ -1,11 +1,13 @@ | |||
| 1 | #!/usr/bin/env bash | 1 | #!/usr/bin/env bash |
| 2 | # | 2 | # |
| 3 | # Deploys Love on two local anvil nodes with different chain ids and checks | 3 | # Deploys Love on two local anvil nodes with different chain ids and checks |
| 4 | -# that CREATE2 gives the token the same address on both. | 4 | +# that CREATE2 gives the token the same address on both. The address embeds the |
| 5 | +# wETH address, so this only holds for chains sharing one wETH deployment — | ||
| 6 | +# both nodes here are given the same WETH. | ||
| 5 | # | 7 | # |
| 6 | # Usage: ./script/multichain-check.sh | 8 | # Usage: ./script/multichain-check.sh |
| 7 | # | 9 | # |
| 8 | -# Env overrides: PORT_A, PORT_B, CHAIN_ID_A, CHAIN_ID_B, SALT | 10 | +# Env overrides: PORT_A, PORT_B, CHAIN_ID_A, CHAIN_ID_B, SALT, WETH |
| 9 | 11 | ||
| 10 | set -euo pipefail | 12 | set -euo pipefail |
| 11 | 13 | ||
| @@ -77,10 +79,11 @@ report() { | |||
| 77 | exit 1 | 79 | exit 1 |
| 78 | fi | 80 | fi |
| 79 | 81 | ||
| 80 | - printf '%s %s %s bytes %s / %s\n' \ | 82 | + printf '%s %s %s bytes %s / %s weth %s\n' \ |
| 81 | "$label" "$addr" "$(((${#code} - 2) / 2))" \ | 83 | "$label" "$addr" "$(((${#code} - 2) / 2))" \ |
| 82 | "$(cast call "$addr" 'name()(string)' --rpc-url "$rpc")" \ | 84 | "$(cast call "$addr" 'name()(string)' --rpc-url "$rpc")" \ |
| 83 | - "$(cast call "$addr" 'symbol()(string)' --rpc-url "$rpc")" | 85 | + "$(cast call "$addr" 'symbol()(string)' --rpc-url "$rpc")" \ |
| 86 | + "$(cast call "$addr" 'weth()(address)' --rpc-url "$rpc")" | ||
| 84 | } | 87 | } |
| 85 | 88 | ||
| 86 | cd "$ROOT" | 89 | cd "$ROOT" |
modified
src/Love.sol +42 -7 | @@ -2,16 +2,51 @@ | ||
| 2 | 2 | pragma solidity ^0.8.30; |
| 3 | 3 | |
| 4 | 4 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; |
| 5 | +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | |
| 6 | +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | |
| 5 | 7 | |
| 6 | 8 | /// @title Love |
| 7 | -/// @notice A basic ERC-20 with an unrestricted mint: anyone can mint any amount | |
| 8 | -/// to any address. Supply is therefore unbounded and meaningless — this | |
| 9 | -/// is a faucet/demo token, not a token with economic value. | |
| 9 | +/// @notice An ERC-20 pegged to wETH at a fixed rate of 100000 LOVE per wETH. | |
| 10 | +/// Every LOVE in circulation is backed by wETH held by this contract: | |
| 11 | +/// supply only moves through `deposit` and `withdraw`, and both are | |
| 12 | +/// permissionless. | |
| 10 | 13 | contract Love is ERC20 { |
| 11 | - constructor() ERC20("Love", "LOVE") {} | |
| 14 | + using SafeERC20 for IERC20; | |
| 12 | 15 | |
| 13 | - /// @notice Mint `amount` tokens to `to`. Callable by anyone, without limit. | |
| 14 | - function mint(address to, uint256 amount) external { | |
| 15 | - _mint(to, amount); | |
| 16 | + uint256 public constant RATE = 100_000; | |
| 17 | + | |
| 18 | + IERC20 public immutable weth; | |
| 19 | + | |
| 20 | + error AmountNotDivisibleByRate(uint256 loveAmount, uint256 rate); | |
| 21 | + | |
| 22 | + event Deposit(address indexed account, uint256 wethAmount, uint256 loveAmount); | |
| 23 | + event Withdraw(address indexed account, uint256 loveAmount, uint256 wethAmount); | |
| 24 | + | |
| 25 | + constructor(IERC20 weth_) ERC20("Love", "LOVE") { | |
| 26 | + weth = weth_; | |
| 27 | + } | |
| 28 | + | |
| 29 | + /// @notice Lock `wethAmount` wETH and mint `wethAmount * RATE` LOVE to the caller. | |
| 30 | + function deposit(uint256 wethAmount) external { | |
| 31 | + uint256 loveAmount = wethAmount * RATE; | |
| 32 | + | |
| 33 | + weth.safeTransferFrom(msg.sender, address(this), wethAmount); | |
| 34 | + _mint(msg.sender, loveAmount); | |
| 35 | + | |
| 36 | + emit Deposit(msg.sender, wethAmount, loveAmount); | |
| 37 | + } | |
| 38 | + | |
| 39 | + /// @notice Burn `loveAmount` LOVE and release `loveAmount / RATE` wETH to the caller. | |
| 40 | + /// @dev Reverts unless `loveAmount` is a multiple of `RATE`, so the peg never | |
| 41 | + /// rounds against the caller or the remaining holders. | |
| 42 | + function withdraw(uint256 loveAmount) external { | |
| 43 | + if (loveAmount % RATE != 0) revert AmountNotDivisibleByRate(loveAmount, RATE); | |
| 44 | + | |
| 45 | + uint256 wethAmount = loveAmount / RATE; | |
| 46 | + | |
| 47 | + _burn(msg.sender, loveAmount); | |
| 48 | + weth.safeTransfer(msg.sender, wethAmount); | |
| 49 | + | |
| 50 | + emit Withdraw(msg.sender, loveAmount, wethAmount); | |
| 16 | 51 | } |
| 17 | 52 | } |
| @@ -2,16 +2,51 @@ | |||
| 2 | pragma solidity ^0.8.30; | 2 | pragma solidity ^0.8.30; |
| 3 | 3 | ||
| 4 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | 4 | import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; |
| 5 | +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
| 6 | +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
| 5 | 7 | ||
| 6 | /// @title Love | 8 | /// @title Love |
| 7 | -/// @notice A basic ERC-20 with an unrestricted mint: anyone can mint any amount | 9 | +/// @notice An ERC-20 pegged to wETH at a fixed rate of 100000 LOVE per wETH. |
| 8 | -/// to any address. Supply is therefore unbounded and meaningless — this | 10 | +/// Every LOVE in circulation is backed by wETH held by this contract: |
| 9 | -/// is a faucet/demo token, not a token with economic value. | 11 | +/// supply only moves through `deposit` and `withdraw`, and both are |
| 12 | +/// permissionless. | ||
| 10 | contract Love is ERC20 { | 13 | contract Love is ERC20 { |
| 11 | - constructor() ERC20("Love", "LOVE") {} | 14 | + using SafeERC20 for IERC20; |
| 12 | 15 | ||
| 13 | - /// @notice Mint `amount` tokens to `to`. Callable by anyone, without limit. | 16 | + uint256 public constant RATE = 100_000; |
| 14 | - function mint(address to, uint256 amount) external { | 17 | + |
| 15 | - _mint(to, amount); | 18 | + IERC20 public immutable weth; |
| 19 | + | ||
| 20 | + error AmountNotDivisibleByRate(uint256 loveAmount, uint256 rate); | ||
| 21 | + | ||
| 22 | + event Deposit(address indexed account, uint256 wethAmount, uint256 loveAmount); | ||
| 23 | + event Withdraw(address indexed account, uint256 loveAmount, uint256 wethAmount); | ||
| 24 | + | ||
| 25 | + constructor(IERC20 weth_) ERC20("Love", "LOVE") { | ||
| 26 | + weth = weth_; | ||
| 27 | + } | ||
| 28 | + | ||
| 29 | + /// @notice Lock `wethAmount` wETH and mint `wethAmount * RATE` LOVE to the caller. | ||
| 30 | + function deposit(uint256 wethAmount) external { | ||
| 31 | + uint256 loveAmount = wethAmount * RATE; | ||
| 32 | + | ||
| 33 | + weth.safeTransferFrom(msg.sender, address(this), wethAmount); | ||
| 34 | + _mint(msg.sender, loveAmount); | ||
| 35 | + | ||
| 36 | + emit Deposit(msg.sender, wethAmount, loveAmount); | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + /// @notice Burn `loveAmount` LOVE and release `loveAmount / RATE` wETH to the caller. | ||
| 40 | + /// @dev Reverts unless `loveAmount` is a multiple of `RATE`, so the peg never | ||
| 41 | + /// rounds against the caller or the remaining holders. | ||
| 42 | + function withdraw(uint256 loveAmount) external { | ||
| 43 | + if (loveAmount % RATE != 0) revert AmountNotDivisibleByRate(loveAmount, RATE); | ||
| 44 | + | ||
| 45 | + uint256 wethAmount = loveAmount / RATE; | ||
| 46 | + | ||
| 47 | + _burn(msg.sender, loveAmount); | ||
| 48 | + weth.safeTransfer(msg.sender, wethAmount); | ||
| 49 | + | ||
| 50 | + emit Withdraw(msg.sender, loveAmount, wethAmount); | ||
| 16 | } | 51 | } |
| 17 | } | 52 | } |
added
test/mocks/MockWETH.sol +23 -0 | new file mode 100644 | ||
| @@ -0,0 +1,23 @@ | ||
| 1 | +// SPDX-License-Identifier: MIT | |
| 2 | +pragma solidity ^0.8.30; | |
| 3 | + | |
| 4 | +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | |
| 5 | + | |
| 6 | +/// @notice Minimal stand-in for WETH9: wraps ETH 1:1, same 18 decimals, same | |
| 7 | +/// `deposit`/`withdraw` entrypoints. Test-only. | |
| 8 | +contract MockWETH is ERC20 { | |
| 9 | + constructor() ERC20("Wrapped Ether", "WETH") {} | |
| 10 | + | |
| 11 | + receive() external payable { | |
| 12 | + deposit(); | |
| 13 | + } | |
| 14 | + | |
| 15 | + function deposit() public payable { | |
| 16 | + _mint(msg.sender, msg.value); | |
| 17 | + } | |
| 18 | + | |
| 19 | + function withdraw(uint256 amount) external { | |
| 20 | + _burn(msg.sender, amount); | |
| 21 | + payable(msg.sender).transfer(amount); | |
| 22 | + } | |
| 23 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | +// SPDX-License-Identifier: MIT | ||
| 2 | +pragma solidity ^0.8.30; | ||
| 3 | + | ||
| 4 | +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
| 5 | + | ||
| 6 | +/// @notice Minimal stand-in for WETH9: wraps ETH 1:1, same 18 decimals, same | ||
| 7 | +/// `deposit`/`withdraw` entrypoints. Test-only. | ||
| 8 | +contract MockWETH is ERC20 { | ||
| 9 | + constructor() ERC20("Wrapped Ether", "WETH") {} | ||
| 10 | + | ||
| 11 | + receive() external payable { | ||
| 12 | + deposit(); | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | + function deposit() public payable { | ||
| 16 | + _mint(msg.sender, msg.value); | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + function withdraw(uint256 amount) external { | ||
| 20 | + _burn(msg.sender, amount); | ||
| 21 | + payable(msg.sender).transfer(amount); | ||
| 22 | + } | ||
| 23 | +} | ||