Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WETH deposit and withdraw on OVM_ETH #1083

Merged
merged 8 commits into from
Jun 15, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/fluffy-jobs-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@eth-optimism/integration-tests': patch
'@eth-optimism/contracts': patch
---

Add WETH9 compatible deposit and withdraw functions to OVM_ETH
78 changes: 78 additions & 0 deletions integration-tests/test/native-eth.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Direction } from './shared/watcher-utils'

import {
expectApprox,
fundUser,
PROXY_SEQUENCER_ENTRYPOINT_ADDRESS,
} from './shared/utils'
import { OptimismEnv } from './shared/env'
Expand Down Expand Up @@ -302,4 +303,81 @@ describe('Native ETH Integration Tests', async () => {
expect(l1BalanceAfter).to.deep.eq(l1BalanceBefore.add(withdrawnAmount))
expect(l2BalanceAfter).to.deep.eq(amount.sub(withdrawnAmount).sub(fee))
})

describe('WETH9 functionality', async () => {
let initialBalance: BigNumber
const value = 10

beforeEach(async () => {
await fundUser(env.watcher, env.l1Bridge, value, env.l2Wallet.address)
initialBalance = await env.l2Wallet.provider.getBalance(
env.l2Wallet.address
)
})

it('successfully deposits', async () => {
const depositTx = await env.ovmEth.deposit({ value, gasPrice: 0 })
const receipt = await depositTx.wait()

expect(
await env.l2Wallet.provider.getBalance(env.l2Wallet.address)
).to.equal(initialBalance)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After a successful deposit why would the balance be the same as the initial balance?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we're adding deposit and withdraw to do nothing. If you want to "deposit" (wrap) your ETH into WETH, you actually don't need to do anything, since ETH is already an ERC20 in L2. This is just being added to be backwards compatible with people's contracts that assume that you have to call deposit to wrap your ETH to be ERC20-compatible.

expect(receipt.events.length).to.equal(4)

// The first transfer event is fee payment
const [
,
firstTransferEvent,
secondTransferEvent,
depositEvent,
] = receipt.events

expect(firstTransferEvent.event).to.equal('Transfer')
expect(firstTransferEvent.args.from).to.equal(env.l2Wallet.address)
expect(firstTransferEvent.args.to).to.equal(env.ovmEth.address)
expect(firstTransferEvent.args.value).to.equal(value)

expect(secondTransferEvent.event).to.equal('Transfer')
expect(secondTransferEvent.args.from).to.equal(env.ovmEth.address)
expect(secondTransferEvent.args.to).to.equal(env.l2Wallet.address)
expect(secondTransferEvent.args.value).to.equal(value)

expect(depositEvent.event).to.equal('Deposit')
expect(depositEvent.args.dst).to.equal(env.l2Wallet.address)
expect(depositEvent.args.wad).to.equal(value)
})

it('successfully deposits on fallback', async () => {
const fallbackTx = await env.l2Wallet.sendTransaction({
to: env.ovmEth.address,
value,
gasPrice: 0,
})
const receipt = await fallbackTx.wait()
expect(receipt.status).to.equal(1)
expect(
await env.l2Wallet.provider.getBalance(env.l2Wallet.address)
).to.equal(initialBalance)
})

it('successfully withdraws', async () => {
const withdrawTx = await env.ovmEth.withdraw(value, { gasPrice: 0 })
const receipt = await withdrawTx.wait()
expect(
await env.l2Wallet.provider.getBalance(env.l2Wallet.address)
).to.equal(initialBalance)
expect(receipt.events.length).to.equal(2)

// The first transfer event is fee payment
const depositEvent = receipt.events[1]
expect(depositEvent.event).to.equal('Withdrawal')
expect(depositEvent.args.src).to.equal(env.l2Wallet.address)
expect(depositEvent.args.wad).to.equal(value)
})

it('reverts on invalid withdraw', async () => {
await expect(env.ovmEth.withdraw(initialBalance.add(1), { gasPrice: 0 }))
.to.be.reverted
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployA

/* Contract Imports */
import { L2StandardERC20 } from "../../libraries/standards/L2StandardERC20.sol";
import { IWETH9 } from "../../libraries/standards/IWETH9.sol";

/**
* @title OVM_ETH
Expand All @@ -15,7 +16,7 @@ import { L2StandardERC20 } from "../../libraries/standards/L2StandardERC20.sol";
* Compiler used: optimistic-solc
* Runtime target: OVM
*/
contract OVM_ETH is L2StandardERC20 {
contract OVM_ETH is L2StandardERC20, IWETH9 {

/***************
* Constructor *
Expand All @@ -29,4 +30,50 @@ contract OVM_ETH is L2StandardERC20 {
"ETH"
)
{}


/******************************
* Custom WETH9 Functionality *
K-Ho marked this conversation as resolved.
Show resolved Hide resolved
******************************/
fallback() external payable {
deposit();
}

/**
* Implements the WETH9 deposit() function as a no-op.
* WARNING: this function does NOT have to do with cross-chain asset bridging. The
* relevant deposit and withdraw functions for that use case can be found at L2StandardBridge.sol.
* This function allows developers to treat OVM_ETH as WETH without any modifications to their code.
*/
function deposit()
public
K-Ho marked this conversation as resolved.
Show resolved Hide resolved
payable
override
{
// Calling deposit() with nonzero value will send the ETH to this contract address. Once recieved here,
// We transfer it back by sending to the msg.sender.
_transfer(address(this), msg.sender, msg.value);

emit Deposit(msg.sender, msg.value);
}

/**
* Implements the WETH9 withdraw() function as a no-op.
* WARNING: this function does NOT have to do with cross-chain asset bridging. The
* relevant deposit and withdraw functions for that use case can be found at L2StandardBridge.sol.
* This function allows developers to treat OVM_ETH as WETH without any modifications to their code.
* @param _wad Amount being withdrawn
*/
function withdraw(
uint256 _wad
)
external
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Visibility here is external while deposit is public. This is asymmetric and likely unintentional? In the L1 WETH token both of these in addition to fallback are public.

Copy link
Contributor Author

@K-Ho K-Ho Jun 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deposit is used in the fallback, so it has to be public. John requested everything that can be to be changed to external, but we could just keep them public to remain loyal to the original implementation 🤷🏻‍♂️. #1083 (comment)

override
{
// Calling withdraw() with value exceeding the withdrawer's ovmBALANCE should revert, as in WETH9.
require(balanceOf(msg.sender) >= _wad);

// Other than emitting an event, OVM_ETH already is native ETH, so we don't need to do anything else.
emit Withdrawal(msg.sender, _wad);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ library Lib_PredeployAddresses {
address internal constant DEPLOYER_WHITELIST = 0x4200000000000000000000000000000000000002;
address internal constant ECDSA_CONTRACT_ACCOUNT = 0x4200000000000000000000000000000000000003;
address internal constant SEQUENCER_ENTRYPOINT = 0x4200000000000000000000000000000000000005;
address internal constant OVM_ETH = 0x4200000000000000000000000000000000000006;
address payable internal constant OVM_ETH = 0x4200000000000000000000000000000000000006;
K-Ho marked this conversation as resolved.
Show resolved Hide resolved
address internal constant L2_CROSS_DOMAIN_MESSENGER = 0x4200000000000000000000000000000000000007;
address internal constant LIB_ADDRESS_MANAGER = 0x4200000000000000000000000000000000000008;
address internal constant PROXY_EOA = 0x4200000000000000000000000000000000000009;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;

/// @title Interface for WETH9
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';

/// @title Interface for WETH9
K-Ho marked this conversation as resolved.
Show resolved Hide resolved
interface IWETH9 is IERC20 {
event Deposit(address indexed dst, uint256 wad);
event Withdrawal(address indexed src, uint256 wad);

/// @notice Deposit ether to get wrapped ether
function deposit() external payable;

/// @notice Withdraw wrapped ether to get ether
function withdraw(uint256) external;
}