Commit 16efedcd authored by Kelvin Fichter's avatar Kelvin Fichter Committed by Kelvin Fichter

feat: reintroduce whitelist

parent f38b8000
/* Imports: External */
import { Contract, ContractFactory, Wallet } from 'ethers'
import { ethers } from 'hardhat'
import chai, { expect } from 'chai'
import { solidity } from 'ethereum-waffle'
import { predeploys } from '@eth-optimism/contracts'
/* Imports: Internal */
import { OptimismEnv } from './shared/env'
import { l2Provider } from './shared/utils'
chai.use(solidity)
describe('Whitelist', async () => {
const initialAmount = 1000
const tokenName = 'OVM Test'
const tokenDecimals = 8
const TokenSymbol = 'OVM'
let Factory__ERC20: ContractFactory
let env: OptimismEnv
before(async () => {
env = await OptimismEnv.new()
Factory__ERC20 = await ethers.getContractFactory('ERC20', env.l2Wallet)
})
describe('when the whitelist is disabled', () => {
it('should be able to deploy a contract', async () => {
await expect(
l2Provider.send('eth_call', [
Factory__ERC20.getDeployTransaction(
initialAmount,
tokenName,
tokenDecimals,
TokenSymbol
),
'latest',
{
[predeploys.OVM_DeployerWhitelist]: {
state: {
['0x0000000000000000000000000000000000000000000000000000000000000000']:
'0x0000000000000000000000000000000000000000000000000000000000000000',
},
},
},
])
).to.not.be.reverted
})
})
describe('when the whitelist is enabled', () => {
const sender = '0x' + '22'.repeat(20)
it('should fail if the user is not whitelisted', async () => {
await expect(
l2Provider.send('eth_call', [
{
...Factory__ERC20.getDeployTransaction(
initialAmount,
tokenName,
tokenDecimals,
TokenSymbol
),
from: sender,
},
'latest',
{
[predeploys.OVM_DeployerWhitelist]: {
state: {
// Set an owner but don't allow this user to deploy
// Owner here is address(1) instead of address(0)
['0x0000000000000000000000000000000000000000000000000000000000000000']:
'0x0000000000000000000000000000000000000000000000000000000000000001',
},
},
},
])
).to.be.revertedWith(`deployer address not whitelisted: ${sender}`)
})
it('should succeed if the user is whitelisted', async () => {
await expect(
l2Provider.send('eth_call', [
{
...Factory__ERC20.getDeployTransaction(
initialAmount,
tokenName,
tokenDecimals,
TokenSymbol
),
from: sender,
},
'latest',
{
[predeploys.OVM_DeployerWhitelist]: {
state: {
// Set an owner
['0x0000000000000000000000000000000000000000000000000000000000000000']:
'0x0000000000000000000000000000000000000000000000000000000000000001',
// See https://docs.soliditylang.org/en/v0.8.7/internals/layout_in_storage.html for
// reference on how the correct storage slot should be set.
// Whitelist mapping is located at storage slot 1.
// whitelist[address] will be located at:
// keccak256(uint256(address) . uint256(1)))
[ethers.utils.keccak256(
'0x' +
// uint256(address)
'0000000000000000000000002222222222222222222222222222222222222222' +
// uint256(1)
'0000000000000000000000000000000000000000000000000000000000000001'
)]:
'0x0000000000000000000000000000000000000000000000000000000000000001', // Boolean (1)
},
},
},
])
).to.not.be.reverted
})
})
})
......@@ -18,6 +18,7 @@ package vm
import (
"bytes"
"fmt"
"math/big"
"sync/atomic"
"time"
......@@ -25,6 +26,10 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rollup/dump"
"github.com/ethereum/go-ethereum/rollup/rcfg"
"github.com/ethereum/go-ethereum/rollup/util"
"golang.org/x/crypto/sha3"
)
// emptyCodeHash is used by create to ensure deployment is disallowed to already
......@@ -414,6 +419,20 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
if !evm.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, common.Address{}, gas, ErrInsufficientBalance
}
if rcfg.UsingOVM {
// Make sure the creator address should be able to deploy.
if !evm.AddressWhitelisted(caller.Address()) {
// Try to encode this error as a Solidity error message so it's more clear to end-users
// what's going on when a contract creation fails.
solerr := fmt.Errorf("deployer address not whitelisted: %s", caller.Address().Hex())
ret, err := util.EncodeSolidityError(solerr)
if err != nil {
// If we're unable to properly encode the error then just return the original message.
return nil, common.Address{}, gas, solerr
}
return ret, common.Address{}, gas, errExecutionReverted
}
}
nonce := evm.StateDB.GetNonce(caller.Address())
evm.StateDB.SetNonce(caller.Address(), nonce+1)
......@@ -499,3 +518,23 @@ func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *
// ChainConfig returns the environment's chain configuration
func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }
func (evm *EVM) AddressWhitelisted(addr common.Address) bool {
// First check if the owner is address(0), which implicitly disables the whitelist.
ownerKey := common.Hash{}
owner := evm.StateDB.GetState(dump.OvmWhitelistAddress, ownerKey)
if (owner == common.Hash{}) {
return true
}
// Next check if the user is whitelisted by resolving the position where the
// true/false value would be.
position := common.Big1
hasher := sha3.NewLegacyKeccak256()
hasher.Write(common.LeftPadBytes(addr.Bytes(), 32))
hasher.Write(common.LeftPadBytes(position.Bytes(), 32))
digest := hasher.Sum(nil)
key := common.BytesToHash(digest)
isWhitelisted := evm.StateDB.GetState(dump.OvmWhitelistAddress, key)
return isWhitelisted != common.Hash{}
}
......@@ -6,3 +6,4 @@ import (
var OvmEthAddress = common.HexToAddress("0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000")
var OvmFeeWallet = common.HexToAddress("0x4200000000000000000000000000000000000011")
var OvmWhitelistAddress = common.HexToAddress("0x4200000000000000000000000000000000000002")
package util
import (
"fmt"
"strings"
"github.com/ethereum/go-ethereum/accounts/abi"
)
var codec abi.ABI
func init() {
const abidata = `
[
{
"type": "function",
"name": "Error",
"constant": true,
"inputs": [
{
"name": "msg",
"type": "string"
}
],
"outputs": []
}
]
`
var err error
codec, err = abi.JSON(strings.NewReader(abidata))
if err != nil {
panic(fmt.Errorf("unable to create abi decoder: %v", err))
}
}
// EncodeSolidityError generates an abi-encoded error message.
func EncodeSolidityError(err error) ([]byte, error) {
return codec.Pack("Error", err.Error())
}
......@@ -35,7 +35,8 @@ services:
DEPLOYER_PRIVATE_KEY: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
SEQUENCER_PRIVATE_KEY: "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
GAS_PRICE_ORACLE_OWNER: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
WHITELIST_OWNER: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
# setting the whitelist owner to address(0) disables the whitelist
WHITELIST_OWNER: "0x0000000000000000000000000000000000000000"
L1_FEE_WALLET_ADDRESS: "0x391716d440c151c42cdf1c95c1d83a5427bca52c"
# skip compilation when run in docker-compose, since the contracts
# were already compiled in the builder step
......
......@@ -10,10 +10,16 @@ import { makeStateDump } from '../src/make-dump'
const outfile = path.join(outdir, 'state-dump.latest.json')
mkdirp.sync(outdir)
// Basic warning so users know that the whitelist will be disabled if the owner is the zero address.
if (process.env.WHITELIST_OWNER === '0x' + '00'.repeat(20)) {
console.log(
'WARNING: whitelist owner is address(0), whitelist will be disabled'
)
}
const dump = await makeStateDump({
whitelistConfig: {
owner: process.env.WHITELIST_OWNER,
allowArbitraryContractDeployment: true,
},
gasPriceOracleConfig: {
owner: process.env.GAS_PRICE_ORACLE_OWNER,
......
......@@ -12,12 +12,20 @@ pragma solidity >0.5.0 <0.8.0;
*/
contract OVM_DeployerWhitelist {
/**********
* Events *
**********/
event OwnerChanged(address oldOwner, address newOwner);
event WhitelistStatusChanged(address deployer, bool whitelisted);
event WhitelistDisabled(address oldOwner);
/**********************
* Contract Constants *
**********************/
bool public initialized;
bool public allowArbitraryDeployment;
// WARNING: When owner is set to address(0), the whitelist is disabled.
address public owner;
mapping (address => bool) public whitelist;
......@@ -42,26 +50,6 @@ contract OVM_DeployerWhitelist {
* Public Functions *
********************/
/**
* Initializes the whitelist.
* @param _owner Address of the owner for this contract.
* @param _allowArbitraryDeployment Whether or not to allow arbitrary contract deployment.
*/
function initialize(
address _owner,
bool _allowArbitraryDeployment
)
external
{
if (initialized == true) {
return;
}
initialized = true;
allowArbitraryDeployment = _allowArbitraryDeployment;
owner = _owner;
}
/**
* Adds or removes an address from the deployment whitelist.
* @param _deployer Address to update permissions for.
......@@ -75,6 +63,7 @@ contract OVM_DeployerWhitelist {
onlyOwner
{
whitelist[_deployer] = _isWhitelisted;
emit WhitelistStatusChanged(_deployer, _isWhitelisted);
}
/**
......@@ -87,20 +76,16 @@ contract OVM_DeployerWhitelist {
public
onlyOwner
{
owner = _owner;
}
// Prevent users from setting the whitelist owner to address(0) except via
// enableArbitraryContractDeployment. If you want to burn the whitelist owner, send it to any
// other address that doesn't have a corresponding knowable private key.
require(
_owner != address(0),
"OVM_DeployerWhitelist: whitelist can only be disabled via enableArbitraryContractDeployment"
);
/**
* Updates the arbitrary deployment flag.
* @param _allowArbitraryDeployment Whether or not to allow arbitrary contract deployment.
*/
function setAllowArbitraryDeployment(
bool _allowArbitraryDeployment
)
public
onlyOwner
{
allowArbitraryDeployment = _allowArbitraryDeployment;
emit OwnerChanged(owner, _owner);
owner = _owner;
}
/**
......@@ -110,8 +95,8 @@ contract OVM_DeployerWhitelist {
external
onlyOwner
{
setAllowArbitraryDeployment(true);
setOwner(address(0));
emit WhitelistDisabled(owner);
owner = address(0);
}
/**
......@@ -123,14 +108,11 @@ contract OVM_DeployerWhitelist {
address _deployer
)
external
view
returns (
bool
)
{
return (
initialized == false
|| allowArbitraryDeployment == true
|| whitelist[_deployer]
);
return (owner == address(0) || whitelist[_deployer]);
}
}
......@@ -12,7 +12,6 @@ import { getContractArtifact } from './contract-artifacts'
export interface RollupDeployConfig {
whitelistConfig: {
owner: string | Signer
allowArbitraryContractDeployment: boolean
}
gasPriceOracleConfig: {
owner: string | Signer
......@@ -32,9 +31,6 @@ export interface RollupDeployConfig {
export const makeStateDump = async (cfg: RollupDeployConfig): Promise<any> => {
const variables = {
OVM_DeployerWhitelist: {
initialized: true,
allowArbitraryDeployment:
cfg.whitelistConfig.allowArbitraryContractDeployment,
owner: cfg.whitelistConfig.owner,
},
OVM_GasPriceOracle: {
......
......@@ -60,18 +60,12 @@ task('whitelist')
const addr = await signer.getAddress()
console.log(`Using signer: ${addr}`)
let owner = await deployerWhitelist.owner()
console.log(`OVM_DeployerWhitelist owner: ${owner}`)
const owner = await deployerWhitelist.owner()
if (owner === '0x0000000000000000000000000000000000000000') {
console.log(`Initializing whitelist`)
const response = await deployerWhitelist.initialize(addr, false, {
gasPrice: args.transactionGasPrice,
})
const receipt = await response.wait()
console.log(`Initialized whitelist: ${receipt.transactionHash}`)
owner = await deployerWhitelist.owner()
console.log(`Whitelist is disabled. Exiting early.`)
return
} else {
console.log(`OVM_DeployerWhitelist owner: ${owner}`)
}
if (addr !== owner) {
......
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