Giter VIP home page Giter VIP logo

weth10's Introduction

WETH10

This twitter hackathon project ๐Ÿฆ updates the canonical "Wrapped Ether" WETH(9) contract with minor but significant upgrades to save Ethereum network users gas and time in making transactions with tokenized ETH on lo-trust, lo-code ๐Ÿฌโ›ฝ.

Mainnet deployment of commit (34d2712) ๐Ÿ”จ. The contract has been deployed at the same address in Kovan, Goerli, Rinkeby and Ropsten.

Total Supply

The supply of WETH10 is capped at type(uint112).max.

Wrapping Ether

Any operation that ends with this contract holding Wrapped Ether is prohibited.

deposit Ether in this contract to receive Wrapped Ether (WETH), which implements the ERC20 standard. WETH is interchangeable with Ether in a 1:1 basis.

withdraw Ether from this contract by unwrapping WETH from your wallet.

The depositTo and withdrawTo convenience functions allow to place the resulting WETH or Ether in an address other than the caller.

The withdrawFrom function allows to unwrap Ether from an owner wallet to a recipient wallet, as long as the owner called approve

Approvals

When an approval is set to type(uint256).max it will not decrease through transferFrom or withdrawFrom calls.

WETH10 implements EIP2612 to set approvals through off-chain signatures

Call Chaining

The depositAndCall and transferAndCall functions allow to deposit Ether or transfer WETH, executing a call in a user-defined contract immediately afterwards, but within the same transaction.

This function will call onTokenTransfer on the recipient address, receiving and passing along a bytes parameter which can be used by the calling contract to process the callback. See EIP667.

Flash Loans

This contract implements EIP3156 that allows to flashLoan an arbitrary amount of Wrapped Ether, unbacked by real Ether, with the condition that it is burned before the end of the transaction. No fees are charged.

This function will call onFlashLoan on the calling address, receiving and passing along a bytes parameter which can be used by the calling contract to process the callback.

Function unrolling

For a minimal gas cost, all functions in WETH10 are external, and a great deal of code repetition exists. To help in understanding the code, blocks that are used recurrently are preceded by a commented-out function call such as // _transferFrom(msg.sender, to, value) that describes the functionality of the block, and followed by a blank line.

weth10's People

Contributors

alcueca avatar amxx avatar dmihal avatar fubuloubu avatar paulrberg avatar ro5s avatar wighawag avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

weth10's Issues

Kelvin versioning error

this information would've probably been more useful to you earlier, but I've just heard about your project. so, WETH was created with Kelvin versioning.

dapphub/ds-weth@becce32

https://chat.makerdao.com/channel/oasis?msg=sy73LpH8EYLdXDBX2

if you're not sure what Kelvin versioning is, here's an explainer: https://jtobin.io/kelvin-versioning

while the augmentations you added are very interesting, this should be rather called WETH8. calling it WETH10 is ignoring the spirit in which WETH was created and what its developers stood for.

tough there may be a reason why your implementation is not WETH8, which I'll explain in the next issue.

Prevent transfers to token contract

To prevent expensive mistakes, the WETH contract should prevent transfers to the token contract.

I've put together two different implementations of this:

#38 adds a few require checks to prevent self transfers
#39 uses the existing overflow protection to prevent self-transfers without incurring any additional gas costs on each transfer

totalSupply is not ERC20 compliant

Bring this from twitter : https://twitter.com/wighawag/status/1319658306697371649

As currently implemented, the function totalSupply is not compliant with the ERC20 standard if the following happen:

  • ETH is sent to WETH10 through a self destruct
  • during a flash loan

For totalSupply to be compliant,

  1. the sum of all Transfer (from zero address) minus the sum of all Transfer (to zero address) need to be equal to totalSupply
  2. the sum of all balances should be equal to totalSupply()

flash loan as currently implemented breaks 2.
self destruct with ETH sent to WETH10 breaks 1.

DOMAIN_SEPARATOR function is missing

The ERC-2612 spec defines a public view function for retrieving the permit DOMAIN_SEPARATOR:

function DOMAIN_SEPARATOR() external view returns (bytes32)

This function is missing from WETH10.

Furthermore, the domain separator is currently calculated every time permit() is called. The function would be more gas efficient if the value was calculated once in the constructor and stored.

deploys on every testnet

hi, for WETH10 to be maximally useful, it should be deployed on all testnets, ideally at the same address.

require(to != address(this), "SELF")

This line would not be necessary if instead of the overflow trick you would simply do require(to != address(this), "SELF") which consume less gas
or am I missing something?

The result of totalSupply() could mismatch the sum of all balances plus flashSupply

I feel really bad that we rely on actual contract balance here: https://github.com/WETH10/WETH10/blob/main/contracts/WETH10.sol#L56

/// @dev Returns the total supply of WETH10 as the Ether held in this contract.
function totalSupply() external view override returns(uint256) {
  return address(this).balance + flashSupply;
}

We should understand that balance could be increased externally and would not match the sum of balances plus flashSupply:

computing the domain separator once in the constructor enables replay attacks after a fork

DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256(bytes("1")),
chainId,
address(this)));

if ethereum forks and a new chain id is assigned, the domain separator will be outdated.

either allow updating the domain separator or compute it every time

the risk here is replay attacks on both chains

Stopping WETH transfers to the WETH contract is not worth it.

Assuming these numbers:
300000 daily transactions.
40 Gwei/GAS
400 USD/ETH

Each require to stop transfers to the WETH contract is ~30 gas. That's about $200 per day.

There are $21000 locked in the WETH contract after three years of operation. The require will waste that much in less than four months.

consider removing receive

/// @dev Fallback, `msg.value` of ether sent to contract grants caller account a matching increase in WETH10 token balance.
/// Emits {Transfer} event to reflect WETH10 token mint of `msg.value` from zero address to caller account.
/// Operations that end with this contract sending Ether to itself are prohibited.
receive() external payable {
require(msg.sender != address(this));
_balanceOf[msg.sender] += msg.value;
emit Transfer(address(0), msg.sender, msg.value);
}

imo not really any more usable than deposit and not gas cheaper

Universal Deployment with CREATE2

Another no brainer in the wETH upgrade contract would be a way to take advantage of CREATE2 method to allow users to associate an address with 'wrapping ether' regardless of chainID.

hardcoded chainId

Having chainId (and as a result the domain separator) set in the constructor will result in issues if a hardfork happen for the chain that will need to change the chainId :

assembly {chainId := chainid()}

Can there be any issues caused by selfdestruct?

When a contract is self-destructed, the remaining ETH is sent to a designated address.

Afaik, this is a way to send ETH to a contract that doesn't trigger that contract's receive function (formerly the fallback function).

Could this cause WETH10 to malfunction?

Longer require messages

There is no reason to have those messages as terse as they are (e.g. !recipient)

It reduces contract size but this is a one time deployment

Mismatch between Readme and Code regard Total Supply

The Readme says:

Total Supply
The supply of WETH10 is capped at type(uint112).max.

However, the code defines it as:

function totalSupply() external view override returns (uint256) {
return address(this).balance + flashMinted;
}

As flashMinted alone can reach type(uint112).max:

require(flashMinted <= type(uint112).max, "WETH: total loan limit exceeded");

It seems that address(this).balance + flashMinted can exceed type(uint112).max.

Ability to rescue tokens and ETH sent to contract address

USDCv2 has option to recover funds

USDC proxy: https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48#readProxyContract

Implementation: https://etherscan.io/address/0xb7277a6e95992041568d9391d09d0122023778a2#code

Starts at line 1313:

contract Rescuable is Ownable {
    using SafeERC20 for IERC20;

    address private _rescuer;

    event RescuerChanged(address indexed newRescuer);

    /**
     * @notice Returns current rescuer
     * @return Rescuer's address
     */
    function rescuer() external view returns (address) {
        return _rescuer;
    }

    /**
     * @notice Revert if called by any account other than the rescuer.
     */
    modifier onlyRescuer() {
        require(msg.sender == _rescuer, "Rescuable: caller is not the rescuer");
        _;
    }

    /**
     * @notice Rescue ERC20 tokens locked up in this contract.
     * @param tokenContract ERC20 token contract address
     * @param to        Recipient address
     * @param amount    Amount to withdraw
     */
    function rescueERC20(
        IERC20 tokenContract,
        address to,
        uint256 amount
    ) external onlyRescuer {
        tokenContract.safeTransfer(to, amount);
    }

    /**
     * @notice Assign the rescuer role to a given address.
     * @param newRescuer New rescuer's address
     */
    function updateRescuer(address newRescuer) external onlyOwner {
        require(
            newRescuer != address(0),
            "Rescuable: new rescuer is the zero address"
        );
        _rescuer = newRescuer;
        emit RescuerChanged(newRescuer);
    }
}

Dead code in WETH10

The WETH10 implementation contains dead code:

WETH10/contracts/WETH10.sol

Lines 381 to 388 in 269b367

} else { // Withdraw
uint256 balance = balanceOf[msg.sender];
require(balance >= value, "WETH: burn amount exceeds balance");
balanceOf[msg.sender] = balance - value;
emit Transfer(msg.sender, address(0), value);
(bool success, ) = msg.sender.call{value: value}("");
require(success, "WETH: ETH transfer failed");

This code can never execute successfully as to == address(0) so that the Solidity-integrated check will fail for the following line:

return ITransferReceiver(to).onTokenTransfer(msg.sender, value, data);

Dead code in principle is not bad, it just fails later than it should and could confuse integrators.

WETH9 in constructor prevent the use of create2 for deterministic address across chains

Having weth9 in the constructor is unnecessary and prevent the use of create 2 for deterministic address across chains (see #22 (comment)) unless weth9 has itself the same address across chains (but believe it is not the case, hence :

if (network === 'kovan') {
)

Instead of having WETH10 tied to WETH9 the various convert functions could accept an extra argument specifying the WETH contract that should receive the eth.

This would also allow easy transition to a future WETH11 if any.

Unroll internal functions for optimal gas cost

Just before deployment to mainnet, make all functions external, allowing code duplication. This will cut on gas costs.

Before the contract is ready for mainnet deployment, leave as is.

Audits

Who do we think might step up here? wrapping ether into tokenized form in "best o' class solidity" feels like no brainer common good.

unnecessary address check in balanceOf

return account == address(this) ? 0 : _balanceOf[account];

the ternary is not worth it when 99% of the time balanceOf isn't called on the contract, and you should just have an invariant test that checks the token balance of the contract is always 0

related is removing the function altogether and just renaming _balanceOf to balanceOf

Increase coverage to 100% of functions

This is not acceptable.

-------------|----------|----------|----------|----------|----------------|
File         |  % Stmts | % Branch |  % Funcs |  % Lines |Uncovered Lines |
-------------|----------|----------|----------|----------|----------------|
 contracts/  |       64 |    32.14 |    66.67 |    62.26 |                |
  WETH10.sol |       64 |    32.14 |    66.67 |    62.26 |... 6,78,82,117 |
-------------|----------|----------|----------|----------|----------------|
All files    |       64 |    32.14 |    66.67 |    62.26 |                |
-------------|----------|----------|----------|----------|----------------|

I'm not sure with the permit test fails when running coverage.

The goal is to have 100% coverage of statements, but let's start with the functions.

Limit how much can be flash minted by the total supply

This is not really a technical vulnerability, but the current implementation leaves the door open for economic manipulation.

The current flashMint function doesn't enforce a cap on how much money there can be minted, which means that anybody can get as much as uint256(-1) WETH. It's true that flash loans are truly useful only when transferred to another contract to perform some sort of trade, and attempting to transfer uint256(-1) tokens would revert. But what if there's an external system that can be influenced solely by the return value of the balanceOf function in WETH10?

Suppose that a third-party community needs to settle a contentious debate via a poll and for whatever reason they decide to use WETH10 balances to tally votes. Further suppose that they're not taking a snapshot of the blockchain, but they are using an on-chain implementation that checks for the balances in the WETH10 contract. Ethereum did something similar in 2016 with Carbonvote.

A shrewd attacker would wait until the voting period is almost done, flash mint a bajillion WETH10 and ballot their vote, influencing the result in their favour.

I know that exactly because of the reasons expounded in this post nobody will want to use WETH10 (in the current form) for vote tallying, or if they really wanted to, they would take a snapshot of the blockchain, or they would re-do the vote - but why not eliminate the possibility altogether?

Let's add a check that the updated balance of the caller is not bigger than the total supply:

function flashMint(uint256 value, bytes calldata data) external lock {
    balanceOf[msg.sender] += value;
    require(balanceOf[msg.sender] >= value, "overflow");
    // Add this check
    require(balanceOf[msg.sender] <= totalSupply(), "!totalSupply");
   ...
}

Use uint256 when possible

Especially when calculating keccak256:

EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)

and

Permit(address src,address guy,uint256 wad,uint256 nonce,uint256 deadline)

Implicit total supply invariant is broken with the current flash loan functionality

Bringing over a discussion from Twitter.

I think that the current way the flash mint function is built is dangerous because it breaks the invariant that total supply of a token should never exceed 2^256 -1 (or MAX_UINT256).

https://github.com/WETH10/WETH10/blob/main/contracts/WETH10.sol#L111-L125

Giving a specific example of how this invariant could be broken:

total supply == sum(account balances)

balance(account_1) == 100 Wei

**Account 2 uses Flash Mint**

balance(account_2) == 2^256 - 1 Wei

total supply == 2^256 + 99 Wei > 2^256 - 1 Wei

Even though this invariant is not very clearly specified in the ERC20 standard, I do believe it is widely accepted and implicitly used by many of Ethereum's developers.
And so, I firmly believe that, if the flash minting functionality is deployed to main net, it should also abide to this invariant.

Decide on locking eth functions while flash minting

I locked the eth functions while flash minting as a precaution, but it's that needed? Should we just assert that the eth balance is equal or greater than the weth supply at the end of a flash mint?

The lock uses 5K gas, so removing it would be great.

On the other hand, could anything be broken with a billion dollars worth of ETH?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.