Skip to content

Commit

Permalink
Merge pull request #1 from piplabs/wip-impl
Browse files Browse the repository at this point in the history
 Introduce "Wrapped IP" Token
  • Loading branch information
kingster-will authored Oct 16, 2024
2 parents 244cab6 + dc416f9 commit 686c0d2
Show file tree
Hide file tree
Showing 10 changed files with 1,107 additions and 45 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
STORY_PRIVATEKEY = 0x12341234123412341234123412341234
5 changes: 0 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,6 @@ jobs:
run: |
forge --version
- name: Run Forge fmt
run: |
forge fmt --check
id: fmt

- name: Run Forge build
run: |
forge build --sizes
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ out/

# Ignores development broadcast logs
!/broadcast
/broadcast/
/broadcast/*/31337/
/broadcast/**/dry-run/

Expand Down
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

56 changes: 16 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
## Foundry
# Wrapped IP

**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**
The "Wrapped IP" refer to WETH-9 with additional features through relatively minor changes.

Foundry consists of:
## Deployments
[STORY Odyssey Testnet](https://internal.storyscan.xyz/address/0xfa057f2e7515267ffab367d0a769f3fa1489b869) `0xFA057f2e7515267FFAB367D0a769F3Fa1489b869`

- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.

## Documentation

https://book.getfoundry.sh/

## Usage
## Features
- Supports [ERC-165](https://eips.ethereum.org/EIPS/eip-165) interface detection.
- Supports [ERC-2612](https://eips.ethereum.org/EIPS/eip-2612) signed approvals.
- Supports [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) contract signature verification.
- Prevents from burning or sending WIP tokens to the contract.

### Build

Expand All @@ -27,40 +24,19 @@ $ forge build
$ forge test
```

### Format

```shell
$ forge fmt
```

### Gas Snapshots

```shell
$ forge snapshot
```

### Anvil

### Deploy on Story Odyssey Testnet
Create a `.env` file with the following content:
```shell
$ anvil
STORY_PRIVATEKEY = <private_key of wallet address to execute command below>
```

### Deploy
you can also refer to `.env.example` file for reference.

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
export STORY_PRIVATE_KEY=<private_key>
$ forge script script/Deploy.s.sol:Deploy --fork-url https://odyssey.storyrpc.io/ -v --broadcast --sender <wallet address> --priority-gas-price 1 --slow --legacy --skip-simulation --verify --verifier=blockscout --verifier-url=https://internal.storyscan.xyz/api
```


### Cast

```shell
$ cast <subcommand>
```

### Help

```shell
$ forge --help
$ anvil --help
$ cast --help
```
2 changes: 2 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
src = "src"
out = "out"
libs = ["lib"]
optimizer = true
optimizer_runs = 20000

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
15 changes: 15 additions & 0 deletions gas-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
| src/WIP.sol:WIP contract | |
|--------------------------|-----------------|
| Deployment Cost | Deployment Size |
| 1318220 | 6020 |

| Function Name | min | avg | median | max | # calls |
|--------------------------|-----------------|-------|--------|-------|---------|
| balanceOf | 552 | 1350 | 552 | 2552 | 1287 |
| decimals | 2402 | 2402 | 2402 | 2402 | 1 |
| deposit | 25034 | 44664 | 44934 | 44934 | 516 |
| name | 582 | 582 | 582 | 582 | 1 |
| receive | 24885 | 44475 | 44785 | 44785 | 257 |
| symbol | 625 | 625 | 625 | 625 | 1 |
| totalSupply | 273 | 273 | 273 | 273 | 1286 |
| withdraw | 25562 | 34528 | 35086 | 35206 | 259 |
17 changes: 17 additions & 0 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;

import { Script, console } from "forge-std/Script.sol";
import { WIP } from "../src/WIP.sol";

contract Deploy is Script {
WIP public wip;

function setUp() public {}

function run() public {
vm.startBroadcast(vm.envUint("STORY_PRIVATEKEY"));
wip = new WIP();
vm.stopBroadcast();
}
}
251 changes: 251 additions & 0 deletions src/WIP.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2015, 2016, 2017 Dapphub

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

pragma solidity 0.8.26;

interface IWIP {
function deposit() external payable;
function withdraw(uint wad) external;

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();
}

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;
emit Deposit(msg.sender, msg.value);
}

function withdraw(uint value) external override {
balanceOf[msg.sender] -= value;
(bool success, ) = msg.sender.call{value: value}("");
if (!success) {
revert WIP_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 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 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);
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);
}
}
Loading

0 comments on commit 686c0d2

Please sign in to comment.