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

feat[contracts]: add sequencer fee wallet #1029

Merged
merged 16 commits into from
Jun 9, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions .changeset/shy-brooms-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@eth-optimism/integration-tests': patch
'@eth-optimism/l2geth': patch
'@eth-optimism/contracts': patch
---

Adds new SequencerFeeVault contract to store generated fees
71 changes: 66 additions & 5 deletions integration-tests/test/fee-payment.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
import chai, { expect } from 'chai'
import chaiAsPromised from 'chai-as-promised'
chai.use(chaiAsPromised)
import { BigNumber, utils } from 'ethers'
import { OptimismEnv } from './shared/env'

/* Imports: External */
import { BigNumber, Contract, utils } from 'ethers'
import { TxGasLimit, TxGasPrice } from '@eth-optimism/core-utils'
import { predeploys, getContractInterface } from '@eth-optimism/contracts'

/* Imports: Internal */
import { OptimismEnv } from './shared/env'
import { Direction } from './shared/watcher-utils'

describe('Fee Payment Integration Tests', async () => {
let env: OptimismEnv
const other = '0x1234123412341234123412341234123412341234'

let env: OptimismEnv
before(async () => {
env = await OptimismEnv.new()
})

let ovmSequencerFeeVault: Contract
before(async () => {
ovmSequencerFeeVault = new Contract(
predeploys.OVM_SequencerFeeVault,
getContractInterface('OVM_SequencerFeeVault'),
env.l2Wallet
)
})

it(`Should return a gasPrice of ${TxGasPrice.toString()} wei`, async () => {
const gasPrice = await env.l2Wallet.getGasPrice()
expect(gasPrice).to.deep.eq(TxGasPrice)
Expand All @@ -36,17 +51,63 @@ describe('Fee Payment Integration Tests', async () => {
it('Paying a nonzero but acceptable gasPrice fee', async () => {
const amount = utils.parseEther('0.5')
const balanceBefore = await env.l2Wallet.getBalance()
const feeVaultBalanceBefore = await env.l2Wallet.provider.getBalance(
ovmSequencerFeeVault.address
)
expect(balanceBefore.gt(amount))

const tx = await env.ovmEth.transfer(other, amount)
const receipt = await tx.wait()
expect(receipt.status).to.eq(1)

const balanceAfter = await env.l2Wallet.getBalance()
const feeVaultBalanceAfter = await env.l2Wallet.provider.getBalance(
ovmSequencerFeeVault.address
)
const expectedFeePaid = tx.gasPrice.mul(tx.gasLimit)

// The fee paid MUST be the receipt.gasUsed, and not the tx.gasLimit
// https://github.com/ethereum-optimism/optimism/blob/0de7a2f9c96a7c4860658822231b2d6da0fefb1d/packages/contracts/contracts/optimistic-ethereum/OVM/accounts/OVM_ECDSAContractAccount.sol#L103
expect(balanceBefore.sub(balanceAfter)).to.be.deep.eq(
tx.gasPrice.mul(tx.gasLimit).add(amount)
expect(balanceBefore.sub(balanceAfter)).to.deep.equal(
expectedFeePaid.add(amount)
)

// Make sure the fee was transferred to the vault.
expect(feeVaultBalanceAfter.sub(feeVaultBalanceBefore)).to.deep.equal(
expectedFeePaid
smartcontracts marked this conversation as resolved.
Show resolved Hide resolved
)
})

it('should not be able to withdraw fees before the minimum is met', async () => {
await expect(ovmSequencerFeeVault.withdraw()).to.be.rejected
})

it('should be able to withdraw fees back to L1 once the minimum is met', async () => {
const l1FeeWallet = await ovmSequencerFeeVault.l1FeeWallet()
const balanceBefore = await env.l1Wallet.provider.getBalance(l1FeeWallet)

// Transfer the minimum required to withdraw.
await env.ovmEth.transfer(
ovmSequencerFeeVault.address,
await ovmSequencerFeeVault.MIN_WITHDRAWAL_AMOUNT()
)

const vaultBalance = await env.ovmEth.balanceOf(
ovmSequencerFeeVault.address
)

// Submit the withdrawal.
const withdrawTx = await ovmSequencerFeeVault.withdraw({
gasPrice: 0, // Need a gasprice of 0 or the balances will include the fee paid during this tx.
Copy link
Contributor

Choose a reason for hiding this comment

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

don't actually think the gasPrice of 0 is needed here, since the fee is paid by the wallet initiating the withdrawal, not the Vault contract

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fee ends up getting paid to the vault contract which messes the test up (lmao)

})

// Wait for the withdrawal to be relayed to L1.
await env.waitForXDomainTransaction(withdrawTx, Direction.L2ToL1)

// Balance difference should be equal to old L2 balance.
const balanceAfter = await env.l1Wallet.provider.getBalance(l1FeeWallet)
expect(balanceAfter.sub(balanceBefore)).to.deep.equal(
BigNumber.from(vaultBalance)
)
})
})
2 changes: 1 addition & 1 deletion integration-tests/test/shared/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class OptimismEnv {
// fund the user if needed
const balance = await l2Wallet.getBalance()
if (balance.isZero()) {
await fundUser(watcher, gateway, utils.parseEther('10'))
await fundUser(watcher, gateway, utils.parseEther('20'))
}

const ovmEth = getOvmEth(l2Wallet)
Expand Down
1 change: 1 addition & 0 deletions l2geth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ $ USING_OVM=true ./build/bin/geth \
--eth1.chainid $LAYER1_CHAIN_ID \
--eth1.l1gatewayaddress $ETH1_L1_GATEWAY_ADDRESS \
--eth1.l1crossdomainmessengeraddress $ETH1_L1_CROSS_DOMAIN_MESSENGER_ADDRESS \
--eth1.l1feewalletaddress $ETH1_L1_FEE_WALLET_ADDRESS \
--eth1.addressresolveraddress $ETH1_ADDRESS_RESOLVER_ADDRESS \
--eth1.ctcdeploymentheight $CTC_DEPLOY_HEIGHT \
--eth1.syncservice \
Expand Down
1 change: 1 addition & 0 deletions l2geth/cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ var (
utils.Eth1SyncServiceEnable,
utils.Eth1CanonicalTransactionChainDeployHeightFlag,
utils.Eth1L1CrossDomainMessengerAddressFlag,
utils.Eth1L1FeeWalletAddressFlag,
tynes marked this conversation as resolved.
Show resolved Hide resolved
utils.Eth1ETHGatewayAddressFlag,
utils.Eth1ChainIdFlag,
utils.RollupClientHttpFlag,
Expand Down
1 change: 1 addition & 0 deletions l2geth/cmd/geth/usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ var AppHelpFlagGroups = []flagGroup{
utils.Eth1SyncServiceEnable,
utils.Eth1CanonicalTransactionChainDeployHeightFlag,
utils.Eth1L1CrossDomainMessengerAddressFlag,
utils.Eth1L1FeeWalletAddressFlag,
utils.Eth1ETHGatewayAddressFlag,
utils.Eth1ChainIdFlag,
utils.RollupClientHttpFlag,
Expand Down
13 changes: 12 additions & 1 deletion l2geth/cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,12 @@ var (
Value: "0x0000000000000000000000000000000000000000",
EnvVar: "ETH1_L1_CROSS_DOMAIN_MESSENGER_ADDRESS",
}
Eth1L1FeeWalletAddressFlag = cli.StringFlag{
Name: "eth1.l1feewalletaddress",
Usage: "Address of the L1 wallet that will collect fees",
Value: "0x0000000000000000000000000000000000000000",
EnvVar: "ETH1_L1_FEE_WALLET_ADDRESS",
}
Eth1ETHGatewayAddressFlag = cli.StringFlag{
Name: "eth1.l1ethgatewayaddress",
Usage: "Deployment address of the Ethereum gateway",
Expand Down Expand Up @@ -1148,6 +1154,10 @@ func setEth1(ctx *cli.Context, cfg *rollup.Config) {
addr := ctx.GlobalString(Eth1L1CrossDomainMessengerAddressFlag.Name)
cfg.L1CrossDomainMessengerAddress = common.HexToAddress(addr)
}
if ctx.GlobalIsSet(Eth1L1FeeWalletAddressFlag.Name) {
addr := ctx.GlobalString(Eth1L1FeeWalletAddressFlag.Name)
cfg.L1FeeWalletAddress = common.HexToAddress(addr)
}
if ctx.GlobalIsSet(Eth1ETHGatewayAddressFlag.Name) {
addr := ctx.GlobalString(Eth1ETHGatewayAddressFlag.Name)
cfg.L1ETHGatewayAddress = common.HexToAddress(addr)
Expand Down Expand Up @@ -1777,10 +1787,11 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
gasLimit = params.GenesisGasLimit
}
xdomainAddress := cfg.Rollup.L1CrossDomainMessengerAddress
l1FeeWalletAddress := cfg.Rollup.L1FeeWalletAddress
addrManagerOwnerAddress := cfg.Rollup.AddressManagerOwnerAddress
l1ETHGatewayAddress := cfg.Rollup.L1ETHGatewayAddress
stateDumpPath := cfg.Rollup.StateDumpPath
cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address, xdomainAddress, l1ETHGatewayAddress, addrManagerOwnerAddress, stateDumpPath, chainID, gasLimit)
cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address, xdomainAddress, l1ETHGatewayAddress, addrManagerOwnerAddress, l1FeeWalletAddress, stateDumpPath, chainID, gasLimit)
smartcontracts marked this conversation as resolved.
Show resolved Hide resolved
if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) {
cfg.Miner.GasPrice = big.NewInt(1)
}
Expand Down
2 changes: 1 addition & 1 deletion l2geth/console/console_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester {
t.Fatalf("failed to create node: %v", err)
}
ethConf := &eth.Config{
Genesis: core.DeveloperGenesisBlock(15, common.Address{}, common.Address{}, common.Address{}, common.Address{}, "", nil, 12000000),
Genesis: core.DeveloperGenesisBlock(15, common.Address{}, common.Address{}, common.Address{}, common.Address{}, common.Address{}, "", nil, 12000000),
Miner: miner.Config{
Etherbase: common.HexToAddress(testAddress),
},
Expand Down
15 changes: 12 additions & 3 deletions l2geth/core/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type Genesis struct {

// OVM Specific, used to initialize the l1XDomainMessengerAddress
// in the genesis state
L1FeeWalletAddress common.Address `json:"-"`
L1CrossDomainMessengerAddress common.Address `json:"-"`
AddressManagerOwnerAddress common.Address `json:"-"`
L1ETHGatewayAddress common.Address `json:"-"`
Expand Down Expand Up @@ -266,7 +267,7 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
}

// ApplyOvmStateToState applies the initial OVM state to a state object.
func ApplyOvmStateToState(statedb *state.StateDB, stateDump *dump.OvmDump, l1XDomainMessengerAddress common.Address, l1ETHGatewayAddress common.Address, addrManagerOwnerAddress common.Address, chainID *big.Int, gasLimit uint64) {
func ApplyOvmStateToState(statedb *state.StateDB, stateDump *dump.OvmDump, l1XDomainMessengerAddress common.Address, l1ETHGatewayAddress common.Address, addrManagerOwnerAddress common.Address, l1FeeWalletAddress common.Address, chainID *big.Int, gasLimit uint64) {
if len(stateDump.Accounts) == 0 {
return
}
Expand Down Expand Up @@ -330,6 +331,13 @@ func ApplyOvmStateToState(statedb *state.StateDB, stateDump *dump.OvmDump, l1XDo
maxTxGasLimitValue := common.BytesToHash(new(big.Int).SetUint64(gasLimit).Bytes())
statedb.SetState(ExecutionManager.Address, maxTxGasLimitSlot, maxTxGasLimitValue)
}
OVM_SequencerFeeVault, ok := stateDump.Accounts["OVM_SequencerFeeVault"]
if ok {
log.Info("Setting l1FeeWallet in OVM_SequencerFeeVault", "wallet", l1FeeWalletAddress.Hex())
l1FeeWalletSlot := common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000")
tynes marked this conversation as resolved.
Show resolved Hide resolved
l1FeeWalletValue := common.BytesToHash(l1FeeWalletAddress.Bytes())
statedb.SetState(OVM_SequencerFeeVault.Address, l1FeeWalletSlot, l1FeeWalletValue)
}
}

// ToBlock creates the genesis block and writes state of a genesis specification
Expand All @@ -342,7 +350,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {

if vm.UsingOVM {
// OVM_ENABLED
ApplyOvmStateToState(statedb, g.Config.StateDump, g.L1CrossDomainMessengerAddress, g.L1ETHGatewayAddress, g.AddressManagerOwnerAddress, g.ChainID, g.GasLimit)
ApplyOvmStateToState(statedb, g.Config.StateDump, g.L1CrossDomainMessengerAddress, g.L1ETHGatewayAddress, g.AddressManagerOwnerAddress, g.L1FeeWalletAddress, g.ChainID, g.GasLimit)
}

for addr, account := range g.Alloc {
Expand Down Expand Up @@ -469,7 +477,7 @@ func DefaultGoerliGenesisBlock() *Genesis {
}

// DeveloperGenesisBlock returns the 'geth --dev' genesis block.
func DeveloperGenesisBlock(period uint64, faucet, l1XDomainMessengerAddress common.Address, l1ETHGatewayAddress common.Address, addrManagerOwnerAddress common.Address, stateDumpPath string, chainID *big.Int, gasLimit uint64) *Genesis {
func DeveloperGenesisBlock(period uint64, faucet, l1XDomainMessengerAddress common.Address, l1ETHGatewayAddress common.Address, addrManagerOwnerAddress common.Address, l1FeeWalletAddress common.Address, stateDumpPath string, chainID *big.Int, gasLimit uint64) *Genesis {
// Override the default period to the user requested one
config := *params.AllCliqueProtocolChanges
config.Clique.Period = period
Expand Down Expand Up @@ -525,6 +533,7 @@ func DeveloperGenesisBlock(period uint64, faucet, l1XDomainMessengerAddress comm
common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
},
L1CrossDomainMessengerAddress: l1XDomainMessengerAddress,
L1FeeWalletAddress: l1FeeWalletAddress,
AddressManagerOwnerAddress: addrManagerOwnerAddress,
L1ETHGatewayAddress: l1ETHGatewayAddress,
ChainID: config.ChainID,
Expand Down
1 change: 1 addition & 0 deletions l2geth/rollup/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Config struct {
// HTTP endpoint of the data transport layer
RollupClientHttp string
L1CrossDomainMessengerAddress common.Address
L1FeeWalletAddress common.Address
AddressManagerOwnerAddress common.Address
L1ETHGatewayAddress common.Address
GasPriceOracleAddress common.Address
Expand Down
11 changes: 11 additions & 0 deletions l2geth/scripts/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ CLI Arguments:
--eth1.chainid - eth1 chain id
--eth1.ctcdeploymentheight - eth1 ctc deploy height
--eth1.l1crossdomainmessengeraddress - eth1 l1 xdomain messenger address
--eth1.l1feewalletaddress - eth l1 fee wallet address
--rollup.statedumppath - http path to the initial state dump
--rollup.clienthttp - rollup client http
--rollup.pollinterval - polling interval for the rollup client
Expand Down Expand Up @@ -127,6 +128,15 @@ while (( "$#" )); do
exit 1
fi
;;
--eth1.l1feewalletaddress)
if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then
ETH1_L1_FEE_WALLET_ADDRESS="$2"
shift 2
else
echo "Error: Argument for $1 is missing" >&2
exit 1
fi
;;
--eth1.l1ethgatewayaddress)
if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then
ETH1_L1_ETH_GATEWAY_ADDRESS="$2"
Expand Down Expand Up @@ -230,6 +240,7 @@ if [[ ! -z "$ROLLUP_SYNC_SERVICE_ENABLE" ]]; then
fi
cmd="$cmd --datadir $DATADIR"
cmd="$cmd --eth1.l1crossdomainmessengeraddress $ETH1_L1_CROSS_DOMAIN_MESSENGER_ADDRESS"
cmd="$cmd --eth1.l1feewalletaddress $ETH1_L1_FEE_WALLET_ADDRESS"
cmd="$cmd --rollup.addressmanagerowneraddress $ADDRESS_MANAGER_OWNER_ADDRESS"
cmd="$cmd --rollup.statedumppath $ROLLUP_STATE_DUMP_PATH"
cmd="$cmd --eth1.ctcdeploymentheight $ETH1_CTC_DEPLOYMENT_HEIGHT"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { iOVM_ECDSAContractAccount } from "../../iOVM/accounts/iOVM_ECDSAContrac
/* Library Imports */
import { Lib_EIP155Tx } from "../../libraries/codec/Lib_EIP155Tx.sol";
import { Lib_ExecutionManagerWrapper } from "../../libraries/wrappers/Lib_ExecutionManagerWrapper.sol";
import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol";

/* Contract Imports */
import { OVM_ETH } from "../predeploys/OVM_ETH.sol";
Expand Down Expand Up @@ -40,7 +41,6 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
// TODO: should be the amount sufficient to cover the gas costs of all of the transactions up
// to and including the CALL/CREATE which forms the entrypoint of the transaction.
uint256 constant EXECUTION_VALIDATION_GAS_OVERHEAD = 25000;
OVM_ETH constant ovmETH = OVM_ETH(0x4200000000000000000000000000000000000006);


/********************
Expand Down Expand Up @@ -92,8 +92,8 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {

// Transfer fee to relayer.
require(
ovmETH.transfer(
msg.sender,
OVM_ETH(Lib_PredeployAddresses.OVM_ETH).transfer(
Lib_PredeployAddresses.SEQUENCER_FEE_WALLET,
SafeMath.mul(transaction.gasLimit, transaction.gasPrice)
),
"Fee was not transferred to relayer."
Expand Down Expand Up @@ -131,7 +131,7 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
);

require(
ovmETH.transfer(
OVM_ETH(Lib_PredeployAddresses.OVM_ETH).transfer(
transaction.to,
transaction.value
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT
pragma solidity >0.5.0 <0.8.0;

/* Library Imports */
import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol";

/* Contract Imports */
import { OVM_ETH } from "../predeploys/OVM_ETH.sol";

/**
* @title OVM_SequencerFeeVault
* @dev Simple holding contract for fees paid to the Sequencer. Likely to be replaced in the future
* but "good enough for now".
*
* Compiler used: optimistic-solc
* Runtime target: OVM
*/
contract OVM_SequencerFeeVault {

/*************
* Constants *
*************/

// Minimum ETH balance that can be withdrawn in a single withdrawal.
uint256 public constant MIN_WITHDRAWAL_AMOUNT = 15 ether;
smartcontracts marked this conversation as resolved.
Show resolved Hide resolved


/*************
* Variables *
*************/

// Address on L1 that will hold the fees once withdrawn. Dynamically initialized within l2geth.
address public l1FeeWallet;


/***************
* Constructor *
***************/

/**
* @param _l1FeeWallet Initial address for the L1 wallet that will hold fees once withdrawn.
* Currently HAS NO EFFECT in production because l2geth will mutate this storage slot during
* the genesis block. This is ONLY for testing purposes.
*/
constructor(
address _l1FeeWallet
) {
l1FeeWallet = _l1FeeWallet;
}


/********************
* Public Functions *
********************/

function withdraw()
public
{
uint256 balance = OVM_ETH(Lib_PredeployAddresses.OVM_ETH).balanceOf(address(this));

require(
balance >= MIN_WITHDRAWAL_AMOUNT,
"OVM_SequencerFeeVault: withdrawal amount must be greater than minimum withdrawal amount"
);

OVM_ETH(Lib_PredeployAddresses.OVM_ETH).withdrawTo(
l1FeeWallet,
balance,
0,
bytes("")
);
}
}
Loading