From 0ac3a934792ac32712acdfc0a744802c72458c1b Mon Sep 17 00:00:00 2001 From: Kingster Date: Mon, 21 Oct 2024 20:51:06 -0700 Subject: [PATCH 1/2] forge install: solady v0.0.259 --- .gitmodules | 3 +++ lib/solady | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/solady diff --git a/.gitmodules b/.gitmodules index 888d42d..0f07815 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/solady"] + path = lib/solady + url = https://github.com/vectorized/solady diff --git a/lib/solady b/lib/solady new file mode 160000 index 0000000..7deab02 --- /dev/null +++ b/lib/solady @@ -0,0 +1 @@ +Subproject commit 7deab021af0426307ae79d091c4d1e26e9e89cf0 From aa13eaf4df28edb243403f46d0558d7aae6b023f Mon Sep 17 00:00:00 2001 From: Kingster Date: Mon, 21 Oct 2024 21:54:25 -0700 Subject: [PATCH 2/2] Reuse Solady's ERC20 implementation. --- remappings.txt | 2 + src/WIP.sol | 226 ++++--------------------------------------------- test/WIP.t.sol | 4 +- 3 files changed, 20 insertions(+), 212 deletions(-) create mode 100644 remappings.txt diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..e425114 --- /dev/null +++ b/remappings.txt @@ -0,0 +1,2 @@ +forge-std/=lib/forge-std/src/ +solady/=lib/solady/src/ diff --git a/src/WIP.sol b/src/WIP.sol index 44050ca..07cb2c0 100644 --- a/src/WIP.sol +++ b/src/WIP.sol @@ -16,236 +16,42 @@ pragma solidity 0.8.26; -interface IWIP { - function deposit() external payable; - function withdraw(uint wad) external; +import { ERC20 } from "solady/tokens/ERC20.sol"; +/// @notice Wrapped IP implementation. +/// @author Inspired by WETH9 (https://github.com/dapphub/ds-weth/blob/master/src/weth9.sol) +contract WIP is ERC20 { + event Deposit(address indexed from, uint amount); + event Withdrawal(address indexed to, uint amount); - event Deposit(address indexed dst, uint wad); - event Withdrawal(address indexed src, uint wad); - - error WIP_IPTransferFailed(); - error WIP_InvalidSignature(); - error WIP_ExpiredSignature(); - error WIP_InvalidTransferRecipient(); - - // ERC20 - function name() external view returns (string memory); - function symbol() external view returns (string memory); - function decimals() external view returns (uint8); - - function totalSupply() external view returns (uint); - function balanceOf(address guy) external view returns (uint); - function allowance(address src, address dst) external view returns (uint); - - function approve(address spender, uint wad) external returns (bool); - function transfer(address dst, uint wad) external returns (bool); - function transferFrom(address src, address dst, uint wad) external returns (bool); - - event Approval(address indexed src, address indexed dst, uint wad); - event Transfer(address indexed src, address indexed dst, uint wad); - - // ERC-165 - function supportsInterface(bytes4 interfaceID) external view returns (bool); - - // ERC-2612 - function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external; - function nonces(address owner) external view returns (uint); - function DOMAIN_SEPARATOR() external view returns (bytes32); - - // Permit2 - function permit2(address owner, address spender, uint amount, uint deadline, bytes calldata signature) external; -} - -contract WIP is IWIP { - string public constant override name = "Wrapped IP"; - string public constant override symbol = "WIP"; - uint8 public override decimals = 18; - - mapping (address => uint) public override balanceOf; - mapping (address => mapping (address => uint)) public override allowance; - - // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); - bytes32 private constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; - // bytes4(keccak256("isValidSignature(bytes32,bytes)") - bytes4 private constant MAGICVALUE = 0x1626ba7e; - mapping(address => uint) public override nonces; - - uint private immutable INITIAL_CHAIN_ID; - bytes32 private immutable INITIAL_DOMAIN_SEPARATOR; - - constructor() { - INITIAL_CHAIN_ID = block.chainid; - INITIAL_DOMAIN_SEPARATOR = _computeDomainSeparator(); - } + error IPTransferFailed(); receive() external payable { deposit(); } - function supportsInterface(bytes4 interfaceID) external pure override returns (bool) { - return - // ERC-165 - interfaceID == this.supportsInterface.selector || - // ERC-2612 - interfaceID == this.permit.selector || - // Permit2 - interfaceID == this.permit2.selector; - } - - function DOMAIN_SEPARATOR() public view override returns (bytes32) { - return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : _computeDomainSeparator(); - } - - function _computeDomainSeparator() private view returns (bytes32) { - return keccak256( - abi.encode( - // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") - 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f, - // keccak256(bytes('Wrapped IP')), - 0x4a24dd8304360c3edc71acded1e27d8467d787ccaeefb153eaaadce60e21753b, - // keccak256(bytes("1")) - 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6, - block.chainid, - address(this) - ) - ); - } - function deposit() public payable { - balanceOf[msg.sender] += msg.value; + _mint(msg.sender, msg.value); emit Deposit(msg.sender, msg.value); } - function withdraw(uint value) external override { - balanceOf[msg.sender] -= value; + function withdraw(uint value) external { + _burn(msg.sender, value); (bool success, ) = msg.sender.call{value: value}(""); if (!success) { - revert WIP_IPTransferFailed(); + revert IPTransferFailed(); } emit Withdrawal(msg.sender, value); } - function totalSupply() external view override returns (uint) { - return address(this).balance; - } - - function approve(address spender, uint value) external override returns (bool) { - allowance[msg.sender][spender] = value; - emit Approval(msg.sender, spender, value); - return true; - } - - modifier ensuresRecipient(address to) { - // Prevents from burning or sending WIP tokens to the contract. - if (to == address(0)) { - revert WIP_InvalidTransferRecipient(); - } - if (to == address(this)) { - revert WIP_InvalidTransferRecipient(); - } - _; + function name() public view virtual override returns (string memory) { + return "Wrapped IP"; } - function transfer(address to, uint value) external ensuresRecipient(to) override returns (bool) { - balanceOf[msg.sender] -= value; - balanceOf[to] += value; - - emit Transfer(msg.sender, to, value); - return true; + function symbol() public view virtual override returns (string memory) { + return "WIP"; } - function transferFrom(address from, address to, uint value) external ensuresRecipient(to) override returns (bool) { - if (from != msg.sender) { - uint _allowance = allowance[from][msg.sender]; - if (_allowance != type(uint).max) { - allowance[from][msg.sender] -= value; - } - } - - balanceOf[from] -= value; - balanceOf[to] += value; - - emit Transfer(from, to, value); + function _givePermit2InfiniteAllowance() internal view override returns (bool) { return true; } - - function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external override { - if (block.timestamp > deadline) { - revert WIP_ExpiredSignature(); - } - bytes32 digest = keccak256( - abi.encodePacked( - '\x19\x01', - DOMAIN_SEPARATOR(), - keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline)) - ) - ); - address recoveredAddress = ecrecover(digest, v, r, s); - if (recoveredAddress != owner) { - revert WIP_InvalidSignature(); - } - if (recoveredAddress == address(0)) { - revert WIP_InvalidSignature(); - } - allowance[owner][spender] = value; - emit Approval(owner, spender, value); - } - - function permit2(address owner, address spender, uint value, uint deadline, bytes calldata signature) external override { - if (block.timestamp > deadline) { - revert WIP_ExpiredSignature(); - } - bytes32 digest = keccak256( - abi.encodePacked( - '\x19\x01', - DOMAIN_SEPARATOR(), - keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline)) - ) - ); - if (!_checkSignature(owner, digest, signature)) { - revert WIP_InvalidSignature(); - } - allowance[owner][spender] = value; - emit Approval(owner, spender, value); - } - - function _checkSignature(address signer, bytes32 hash, bytes memory signature) private view returns (bool) { - (address recoveredAddress) = _recover(hash, signature); - if (recoveredAddress == signer) { - if (recoveredAddress != address(0)) { - return true; - } - } - - (bool success, bytes memory result) = signer.staticcall( - abi.encodeWithSelector(MAGICVALUE, hash, signature) - ); - return ( - success && - result.length == 32 && - abi.decode(result, (bytes32)) == bytes32(MAGICVALUE) - ); - } - - function _recover(bytes32 hash, bytes memory signature) private pure returns (address) { - if (signature.length != 65) { - return address(0); - } - - bytes32 r; - bytes32 s; - uint8 v; - - assembly { - r := mload(add(signature, 0x20)) - s := mload(add(signature, 0x40)) - v := byte(0, mload(add(signature, 0x60))) - } - - if (uint(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { - return address(0); - } - - return ecrecover(hash, v, r, s); - } } \ No newline at end of file diff --git a/test/WIP.t.sol b/test/WIP.t.sol index 5f6d751..a507ab1 100644 --- a/test/WIP.t.sol +++ b/test/WIP.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.26; import { Test } from "forge-std/Test.sol"; -import { IWIP, WIP } from "../src/WIP.sol"; +import { WIP } from "../src/WIP.sol"; contract ContractWithoutReceive {} @@ -78,7 +78,7 @@ contract WIPTest is Test { assertEq(wip.balanceOf(owner), 1 ether); - vm.expectRevert(IWIP.WIP_IPTransferFailed.selector); + vm.expectRevert(WIP.IPTransferFailed.selector); vm.prank(owner); wip.withdraw(1 ether); }