Commit e045f582 authored by smartcontracts's avatar smartcontracts Committed by GitHub

feat[contracts]: add sequencer fee wallet (#1029)

* wip: first draft of the fee wallet

* add fee wallet to dump

* rename to sequencer vault

* add L1 fee wallet to geth config

* add unit tests

* fix geth linting error

* add a basic integration test

* fix broken integration test

* add test for correct storage slot

* add integration test for fee withdrawal

* fix typo in integration tests

* fix a bug bin integration tests

* Update OVM_SequencerFeeVault.sol

* fix bug in contract tests

* chore: add changeset

* fix bug in contract tests
parent baacda34
---
'@eth-optimism/integration-tests': patch
'@eth-optimism/l2geth': patch
'@eth-optimism/contracts': patch
---
Adds new SequencerFeeVault contract to store generated fees
import chai, { expect } from 'chai' import chai, { expect } from 'chai'
import chaiAsPromised from 'chai-as-promised' import chaiAsPromised from 'chai-as-promised'
chai.use(chaiAsPromised) 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 { 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 () => { describe('Fee Payment Integration Tests', async () => {
let env: OptimismEnv
const other = '0x1234123412341234123412341234123412341234' const other = '0x1234123412341234123412341234123412341234'
let env: OptimismEnv
before(async () => { before(async () => {
env = await OptimismEnv.new() 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 () => { it(`Should return a gasPrice of ${TxGasPrice.toString()} wei`, async () => {
const gasPrice = await env.l2Wallet.getGasPrice() const gasPrice = await env.l2Wallet.getGasPrice()
expect(gasPrice).to.deep.eq(TxGasPrice) expect(gasPrice).to.deep.eq(TxGasPrice)
...@@ -36,6 +51,9 @@ describe('Fee Payment Integration Tests', async () => { ...@@ -36,6 +51,9 @@ describe('Fee Payment Integration Tests', async () => {
it('Paying a nonzero but acceptable gasPrice fee', async () => { it('Paying a nonzero but acceptable gasPrice fee', async () => {
const amount = utils.parseEther('0.5') const amount = utils.parseEther('0.5')
const balanceBefore = await env.l2Wallet.getBalance() const balanceBefore = await env.l2Wallet.getBalance()
const feeVaultBalanceBefore = await env.l2Wallet.provider.getBalance(
ovmSequencerFeeVault.address
)
expect(balanceBefore.gt(amount)) expect(balanceBefore.gt(amount))
const tx = await env.ovmEth.transfer(other, amount) const tx = await env.ovmEth.transfer(other, amount)
...@@ -43,10 +61,53 @@ describe('Fee Payment Integration Tests', async () => { ...@@ -43,10 +61,53 @@ describe('Fee Payment Integration Tests', async () => {
expect(receipt.status).to.eq(1) expect(receipt.status).to.eq(1)
const balanceAfter = await env.l2Wallet.getBalance() 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 // 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 // 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( expect(balanceBefore.sub(balanceAfter)).to.deep.equal(
tx.gasPrice.mul(tx.gasLimit).add(amount) expectedFeePaid.add(amount)
)
// Make sure the fee was transferred to the vault.
expect(feeVaultBalanceAfter.sub(feeVaultBalanceBefore)).to.deep.equal(
expectedFeePaid
)
})
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.
})
// 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)
) )
}) })
}) })
...@@ -58,7 +58,7 @@ export class OptimismEnv { ...@@ -58,7 +58,7 @@ export class OptimismEnv {
// fund the user if needed // fund the user if needed
const balance = await l2Wallet.getBalance() const balance = await l2Wallet.getBalance()
if (balance.isZero()) { if (balance.isZero()) {
await fundUser(watcher, gateway, utils.parseEther('10')) await fundUser(watcher, gateway, utils.parseEther('20'))
} }
const ovmEth = getOvmEth(l2Wallet) const ovmEth = getOvmEth(l2Wallet)
......
...@@ -50,6 +50,7 @@ $ USING_OVM=true ./build/bin/geth \ ...@@ -50,6 +50,7 @@ $ USING_OVM=true ./build/bin/geth \
--eth1.chainid $LAYER1_CHAIN_ID \ --eth1.chainid $LAYER1_CHAIN_ID \
--eth1.l1gatewayaddress $ETH1_L1_GATEWAY_ADDRESS \ --eth1.l1gatewayaddress $ETH1_L1_GATEWAY_ADDRESS \
--eth1.l1crossdomainmessengeraddress $ETH1_L1_CROSS_DOMAIN_MESSENGER_ADDRESS \ --eth1.l1crossdomainmessengeraddress $ETH1_L1_CROSS_DOMAIN_MESSENGER_ADDRESS \
--eth1.l1feewalletaddress $ETH1_L1_FEE_WALLET_ADDRESS \
--eth1.addressresolveraddress $ETH1_ADDRESS_RESOLVER_ADDRESS \ --eth1.addressresolveraddress $ETH1_ADDRESS_RESOLVER_ADDRESS \
--eth1.ctcdeploymentheight $CTC_DEPLOY_HEIGHT \ --eth1.ctcdeploymentheight $CTC_DEPLOY_HEIGHT \
--eth1.syncservice \ --eth1.syncservice \
......
...@@ -153,6 +153,7 @@ var ( ...@@ -153,6 +153,7 @@ var (
utils.Eth1SyncServiceEnable, utils.Eth1SyncServiceEnable,
utils.Eth1CanonicalTransactionChainDeployHeightFlag, utils.Eth1CanonicalTransactionChainDeployHeightFlag,
utils.Eth1L1CrossDomainMessengerAddressFlag, utils.Eth1L1CrossDomainMessengerAddressFlag,
utils.Eth1L1FeeWalletAddressFlag,
utils.Eth1ETHGatewayAddressFlag, utils.Eth1ETHGatewayAddressFlag,
utils.Eth1ChainIdFlag, utils.Eth1ChainIdFlag,
utils.RollupClientHttpFlag, utils.RollupClientHttpFlag,
......
...@@ -68,6 +68,7 @@ var AppHelpFlagGroups = []flagGroup{ ...@@ -68,6 +68,7 @@ var AppHelpFlagGroups = []flagGroup{
utils.Eth1SyncServiceEnable, utils.Eth1SyncServiceEnable,
utils.Eth1CanonicalTransactionChainDeployHeightFlag, utils.Eth1CanonicalTransactionChainDeployHeightFlag,
utils.Eth1L1CrossDomainMessengerAddressFlag, utils.Eth1L1CrossDomainMessengerAddressFlag,
utils.Eth1L1FeeWalletAddressFlag,
utils.Eth1ETHGatewayAddressFlag, utils.Eth1ETHGatewayAddressFlag,
utils.Eth1ChainIdFlag, utils.Eth1ChainIdFlag,
utils.RollupClientHttpFlag, utils.RollupClientHttpFlag,
......
...@@ -822,6 +822,12 @@ var ( ...@@ -822,6 +822,12 @@ var (
Value: "0x0000000000000000000000000000000000000000", Value: "0x0000000000000000000000000000000000000000",
EnvVar: "ETH1_L1_CROSS_DOMAIN_MESSENGER_ADDRESS", 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{ Eth1ETHGatewayAddressFlag = cli.StringFlag{
Name: "eth1.l1ethgatewayaddress", Name: "eth1.l1ethgatewayaddress",
Usage: "Deployment address of the Ethereum gateway", Usage: "Deployment address of the Ethereum gateway",
...@@ -1148,6 +1154,10 @@ func setEth1(ctx *cli.Context, cfg *rollup.Config) { ...@@ -1148,6 +1154,10 @@ func setEth1(ctx *cli.Context, cfg *rollup.Config) {
addr := ctx.GlobalString(Eth1L1CrossDomainMessengerAddressFlag.Name) addr := ctx.GlobalString(Eth1L1CrossDomainMessengerAddressFlag.Name)
cfg.L1CrossDomainMessengerAddress = common.HexToAddress(addr) 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) { if ctx.GlobalIsSet(Eth1ETHGatewayAddressFlag.Name) {
addr := ctx.GlobalString(Eth1ETHGatewayAddressFlag.Name) addr := ctx.GlobalString(Eth1ETHGatewayAddressFlag.Name)
cfg.L1ETHGatewayAddress = common.HexToAddress(addr) cfg.L1ETHGatewayAddress = common.HexToAddress(addr)
...@@ -1777,10 +1787,11 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) { ...@@ -1777,10 +1787,11 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
gasLimit = params.GenesisGasLimit gasLimit = params.GenesisGasLimit
} }
xdomainAddress := cfg.Rollup.L1CrossDomainMessengerAddress xdomainAddress := cfg.Rollup.L1CrossDomainMessengerAddress
l1FeeWalletAddress := cfg.Rollup.L1FeeWalletAddress
addrManagerOwnerAddress := cfg.Rollup.AddressManagerOwnerAddress addrManagerOwnerAddress := cfg.Rollup.AddressManagerOwnerAddress
l1ETHGatewayAddress := cfg.Rollup.L1ETHGatewayAddress l1ETHGatewayAddress := cfg.Rollup.L1ETHGatewayAddress
stateDumpPath := cfg.Rollup.StateDumpPath 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)
if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) { if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) {
cfg.Miner.GasPrice = big.NewInt(1) cfg.Miner.GasPrice = big.NewInt(1)
} }
......
...@@ -98,7 +98,7 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester { ...@@ -98,7 +98,7 @@ func newTester(t *testing.T, confOverride func(*eth.Config)) *tester {
t.Fatalf("failed to create node: %v", err) t.Fatalf("failed to create node: %v", err)
} }
ethConf := &eth.Config{ 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{ Miner: miner.Config{
Etherbase: common.HexToAddress(testAddress), Etherbase: common.HexToAddress(testAddress),
}, },
......
...@@ -69,6 +69,7 @@ type Genesis struct { ...@@ -69,6 +69,7 @@ type Genesis struct {
// OVM Specific, used to initialize the l1XDomainMessengerAddress // OVM Specific, used to initialize the l1XDomainMessengerAddress
// in the genesis state // in the genesis state
L1FeeWalletAddress common.Address `json:"-"`
L1CrossDomainMessengerAddress common.Address `json:"-"` L1CrossDomainMessengerAddress common.Address `json:"-"`
AddressManagerOwnerAddress common.Address `json:"-"` AddressManagerOwnerAddress common.Address `json:"-"`
L1ETHGatewayAddress common.Address `json:"-"` L1ETHGatewayAddress common.Address `json:"-"`
...@@ -266,7 +267,7 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig { ...@@ -266,7 +267,7 @@ func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
} }
// ApplyOvmStateToState applies the initial OVM state to a state object. // 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 { if len(stateDump.Accounts) == 0 {
return return
} }
...@@ -330,6 +331,13 @@ func ApplyOvmStateToState(statedb *state.StateDB, stateDump *dump.OvmDump, l1XDo ...@@ -330,6 +331,13 @@ func ApplyOvmStateToState(statedb *state.StateDB, stateDump *dump.OvmDump, l1XDo
maxTxGasLimitValue := common.BytesToHash(new(big.Int).SetUint64(gasLimit).Bytes()) maxTxGasLimitValue := common.BytesToHash(new(big.Int).SetUint64(gasLimit).Bytes())
statedb.SetState(ExecutionManager.Address, maxTxGasLimitSlot, maxTxGasLimitValue) 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")
l1FeeWalletValue := common.BytesToHash(l1FeeWalletAddress.Bytes())
statedb.SetState(OVM_SequencerFeeVault.Address, l1FeeWalletSlot, l1FeeWalletValue)
}
} }
// ToBlock creates the genesis block and writes state of a genesis specification // ToBlock creates the genesis block and writes state of a genesis specification
...@@ -342,7 +350,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { ...@@ -342,7 +350,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block {
if vm.UsingOVM { if vm.UsingOVM {
// OVM_ENABLED // 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 { for addr, account := range g.Alloc {
...@@ -469,7 +477,7 @@ func DefaultGoerliGenesisBlock() *Genesis { ...@@ -469,7 +477,7 @@ func DefaultGoerliGenesisBlock() *Genesis {
} }
// DeveloperGenesisBlock returns the 'geth --dev' genesis block. // 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 // Override the default period to the user requested one
config := *params.AllCliqueProtocolChanges config := *params.AllCliqueProtocolChanges
config.Clique.Period = period config.Clique.Period = period
...@@ -525,6 +533,7 @@ func DeveloperGenesisBlock(period uint64, faucet, l1XDomainMessengerAddress comm ...@@ -525,6 +533,7 @@ func DeveloperGenesisBlock(period uint64, faucet, l1XDomainMessengerAddress comm
common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing common.BytesToAddress([]byte{8}): {Balance: big.NewInt(1)}, // ECPairing
}, },
L1CrossDomainMessengerAddress: l1XDomainMessengerAddress, L1CrossDomainMessengerAddress: l1XDomainMessengerAddress,
L1FeeWalletAddress: l1FeeWalletAddress,
AddressManagerOwnerAddress: addrManagerOwnerAddress, AddressManagerOwnerAddress: addrManagerOwnerAddress,
L1ETHGatewayAddress: l1ETHGatewayAddress, L1ETHGatewayAddress: l1ETHGatewayAddress,
ChainID: config.ChainID, ChainID: config.ChainID,
......
...@@ -23,6 +23,7 @@ type Config struct { ...@@ -23,6 +23,7 @@ type Config struct {
// HTTP endpoint of the data transport layer // HTTP endpoint of the data transport layer
RollupClientHttp string RollupClientHttp string
L1CrossDomainMessengerAddress common.Address L1CrossDomainMessengerAddress common.Address
L1FeeWalletAddress common.Address
AddressManagerOwnerAddress common.Address AddressManagerOwnerAddress common.Address
L1ETHGatewayAddress common.Address L1ETHGatewayAddress common.Address
GasPriceOracleAddress common.Address GasPriceOracleAddress common.Address
......
...@@ -33,6 +33,7 @@ CLI Arguments: ...@@ -33,6 +33,7 @@ CLI Arguments:
--eth1.chainid - eth1 chain id --eth1.chainid - eth1 chain id
--eth1.ctcdeploymentheight - eth1 ctc deploy height --eth1.ctcdeploymentheight - eth1 ctc deploy height
--eth1.l1crossdomainmessengeraddress - eth1 l1 xdomain messenger address --eth1.l1crossdomainmessengeraddress - eth1 l1 xdomain messenger address
--eth1.l1feewalletaddress - eth l1 fee wallet address
--rollup.statedumppath - http path to the initial state dump --rollup.statedumppath - http path to the initial state dump
--rollup.clienthttp - rollup client http --rollup.clienthttp - rollup client http
--rollup.pollinterval - polling interval for the rollup client --rollup.pollinterval - polling interval for the rollup client
...@@ -127,6 +128,15 @@ while (( "$#" )); do ...@@ -127,6 +128,15 @@ while (( "$#" )); do
exit 1 exit 1
fi 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) --eth1.l1ethgatewayaddress)
if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then if [ -n "$2" ] && [ ${2:0:1} != "-" ]; then
ETH1_L1_ETH_GATEWAY_ADDRESS="$2" ETH1_L1_ETH_GATEWAY_ADDRESS="$2"
...@@ -230,6 +240,7 @@ if [[ ! -z "$ROLLUP_SYNC_SERVICE_ENABLE" ]]; then ...@@ -230,6 +240,7 @@ if [[ ! -z "$ROLLUP_SYNC_SERVICE_ENABLE" ]]; then
fi fi
cmd="$cmd --datadir $DATADIR" cmd="$cmd --datadir $DATADIR"
cmd="$cmd --eth1.l1crossdomainmessengeraddress $ETH1_L1_CROSS_DOMAIN_MESSENGER_ADDRESS" 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.addressmanagerowneraddress $ADDRESS_MANAGER_OWNER_ADDRESS"
cmd="$cmd --rollup.statedumppath $ROLLUP_STATE_DUMP_PATH" cmd="$cmd --rollup.statedumppath $ROLLUP_STATE_DUMP_PATH"
cmd="$cmd --eth1.ctcdeploymentheight $ETH1_CTC_DEPLOYMENT_HEIGHT" cmd="$cmd --eth1.ctcdeploymentheight $ETH1_CTC_DEPLOYMENT_HEIGHT"
......
...@@ -8,6 +8,7 @@ import { iOVM_ECDSAContractAccount } from "../../iOVM/accounts/iOVM_ECDSAContrac ...@@ -8,6 +8,7 @@ import { iOVM_ECDSAContractAccount } from "../../iOVM/accounts/iOVM_ECDSAContrac
/* Library Imports */ /* Library Imports */
import { Lib_EIP155Tx } from "../../libraries/codec/Lib_EIP155Tx.sol"; import { Lib_EIP155Tx } from "../../libraries/codec/Lib_EIP155Tx.sol";
import { Lib_ExecutionManagerWrapper } from "../../libraries/wrappers/Lib_ExecutionManagerWrapper.sol"; import { Lib_ExecutionManagerWrapper } from "../../libraries/wrappers/Lib_ExecutionManagerWrapper.sol";
import { Lib_PredeployAddresses } from "../../libraries/constants/Lib_PredeployAddresses.sol";
/* Contract Imports */ /* Contract Imports */
import { OVM_ETH } from "../predeploys/OVM_ETH.sol"; import { OVM_ETH } from "../predeploys/OVM_ETH.sol";
...@@ -40,7 +41,6 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { ...@@ -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 // 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. // to and including the CALL/CREATE which forms the entrypoint of the transaction.
uint256 constant EXECUTION_VALIDATION_GAS_OVERHEAD = 25000; uint256 constant EXECUTION_VALIDATION_GAS_OVERHEAD = 25000;
OVM_ETH constant ovmETH = OVM_ETH(0x4200000000000000000000000000000000000006);
/******************** /********************
...@@ -92,8 +92,8 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { ...@@ -92,8 +92,8 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
// Transfer fee to relayer. // Transfer fee to relayer.
require( require(
ovmETH.transfer( OVM_ETH(Lib_PredeployAddresses.OVM_ETH).transfer(
msg.sender, Lib_PredeployAddresses.SEQUENCER_FEE_WALLET,
SafeMath.mul(transaction.gasLimit, transaction.gasPrice) SafeMath.mul(transaction.gasLimit, transaction.gasPrice)
), ),
"Fee was not transferred to relayer." "Fee was not transferred to relayer."
...@@ -131,7 +131,7 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount { ...@@ -131,7 +131,7 @@ contract OVM_ECDSAContractAccount is iOVM_ECDSAContractAccount {
); );
require( require(
ovmETH.transfer( OVM_ETH(Lib_PredeployAddresses.OVM_ETH).transfer(
transaction.to, transaction.to,
transaction.value transaction.value
), ),
......
// 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;
/*************
* 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("")
);
}
}
...@@ -14,5 +14,6 @@ library Lib_PredeployAddresses { ...@@ -14,5 +14,6 @@ library Lib_PredeployAddresses {
address internal constant L2_CROSS_DOMAIN_MESSENGER = 0x4200000000000000000000000000000000000007; address internal constant L2_CROSS_DOMAIN_MESSENGER = 0x4200000000000000000000000000000000000007;
address internal constant LIB_ADDRESS_MANAGER = 0x4200000000000000000000000000000000000008; address internal constant LIB_ADDRESS_MANAGER = 0x4200000000000000000000000000000000000008;
address internal constant PROXY_EOA = 0x4200000000000000000000000000000000000009; address internal constant PROXY_EOA = 0x4200000000000000000000000000000000000009;
address internal constant SEQUENCER_FEE_WALLET = 0x4200000000000000000000000000000000000011;
address internal constant ERC1820_REGISTRY = 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24; address internal constant ERC1820_REGISTRY = 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24;
} }
...@@ -275,5 +275,9 @@ export const makeContractDeployConfig = async ( ...@@ -275,5 +275,9 @@ export const makeContractDeployConfig = async (
config.gasPriceOracleConfig.initialGasPrice, config.gasPriceOracleConfig.initialGasPrice,
], ],
}, },
OVM_SequencerFeeVault: {
factory: getContractFactory('OVM_SequencerFeeVault'),
params: [`0x${'11'.repeat(20)}`],
},
} }
} }
...@@ -19,5 +19,6 @@ export const predeploys = { ...@@ -19,5 +19,6 @@ export const predeploys = {
OVM_ProxyEOA: '0x4200000000000000000000000000000000000009', OVM_ProxyEOA: '0x4200000000000000000000000000000000000009',
OVM_ExecutionManagerWrapper: '0x420000000000000000000000000000000000000B', OVM_ExecutionManagerWrapper: '0x420000000000000000000000000000000000000B',
OVM_GasPriceOracle: '0x420000000000000000000000000000000000000F', OVM_GasPriceOracle: '0x420000000000000000000000000000000000000F',
OVM_SequencerFeeVault: '0x4200000000000000000000000000000000000011',
ERC1820Registry: '0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24', ERC1820Registry: '0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24',
} }
...@@ -137,6 +137,7 @@ export const makeStateDump = async (cfg: RollupDeployConfig): Promise<any> => { ...@@ -137,6 +137,7 @@ export const makeStateDump = async (cfg: RollupDeployConfig): Promise<any> => {
'OVM_ETH', 'OVM_ETH',
'OVM_ExecutionManagerWrapper', 'OVM_ExecutionManagerWrapper',
'OVM_GasPriceOracle', 'OVM_GasPriceOracle',
'OVM_SequencerFeeVault',
], ],
deployOverrides: {}, deployOverrides: {},
waitForReceipts: false, waitForReceipts: false,
...@@ -159,6 +160,7 @@ export const makeStateDump = async (cfg: RollupDeployConfig): Promise<any> => { ...@@ -159,6 +160,7 @@ export const makeStateDump = async (cfg: RollupDeployConfig): Promise<any> => {
'OVM_ProxyEOA', 'OVM_ProxyEOA',
'OVM_ExecutionManagerWrapper', 'OVM_ExecutionManagerWrapper',
'OVM_GasPriceOracle', 'OVM_GasPriceOracle',
'OVM_SequencerFeeVault',
] ]
const deploymentResult = await deploy(config) const deploymentResult = await deploy(config)
......
import { expect } from '../../../setup'
/* Imports: External */
import hre from 'hardhat'
import { MockContract, smockit } from '@eth-optimism/smock'
import { Contract, Signer } from 'ethers'
/* Imports: Internal */
import { predeploys } from '../../../../src'
describe('OVM_SequencerFeeVault', () => {
let signer1: Signer
before(async () => {
;[signer1] = await hre.ethers.getSigners()
})
let Mock__OVM_ETH: MockContract
before(async () => {
Mock__OVM_ETH = await smockit('OVM_ETH', {
address: predeploys.OVM_ETH,
})
})
let OVM_SequencerFeeVault: Contract
beforeEach(async () => {
const factory = await hre.ethers.getContractFactory('OVM_SequencerFeeVault')
OVM_SequencerFeeVault = await factory.deploy(await signer1.getAddress())
})
describe('withdraw', async () => {
it('should fail if the contract does not have more than the minimum balance', async () => {
Mock__OVM_ETH.smocked.balanceOf.will.return.with(0)
await expect(OVM_SequencerFeeVault.withdraw()).to.be.reverted
})
it('should succeed when the contract has exactly sufficient balance', async () => {
const amount = await OVM_SequencerFeeVault.MIN_WITHDRAWAL_AMOUNT()
Mock__OVM_ETH.smocked.balanceOf.will.return.with(amount)
await expect(OVM_SequencerFeeVault.withdraw()).to.not.be.reverted
expect(Mock__OVM_ETH.smocked.withdrawTo.calls[0]).to.deep.equal([
await signer1.getAddress(),
amount,
0,
'0x',
])
})
it('should succeed when the contract has more than sufficient balance', async () => {
const amount = hre.ethers.utils.parseEther('100')
Mock__OVM_ETH.smocked.balanceOf.will.return.with(amount)
await expect(OVM_SequencerFeeVault.withdraw()).to.not.be.reverted
expect(Mock__OVM_ETH.smocked.withdrawTo.calls[0]).to.deep.equal([
await signer1.getAddress(),
amount,
0,
'0x',
])
})
it('should have an owner in storage slot 0x00...00', async () => {
// Deploy a new temporary instance with an address that's easier to make assertions about.
const factory = await hre.ethers.getContractFactory(
'OVM_SequencerFeeVault'
)
OVM_SequencerFeeVault = await factory.deploy(`0x${'11'.repeat(20)}`)
expect(
await hre.ethers.provider.getStorageAt(
OVM_SequencerFeeVault.address,
hre.ethers.constants.HashZero
)
).to.equal(`0x000000000000000000000000${'11'.repeat(20)}`)
})
})
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment