Skip to content

Commit

Permalink
Fix the problem of not being able to send all ETH (#175)
Browse files Browse the repository at this point in the history
* Fix the problem of not being able to send all ETH

* Add unit tests

* Fix typo
  • Loading branch information
boyuan-chen authored Jun 3, 2022
1 parent b233ad0 commit b47739e
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 31 deletions.
59 changes: 59 additions & 0 deletions integration-tests/test/fee-payment.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,65 @@ describe('Fee Payment Integration Tests', async () => {
}
})

it('{tag:other} should transfer all ETH with correct gas limit', async () => {
await setPrices(env, 1000)
const randomWallet = ethers.Wallet.createRandom().connect(env.l2Provider)

await env.l2Wallet.sendTransaction({
to: randomWallet.address,
value: ethers.utils.parseEther('1'),
})

// estimate gas fee
const estimatedGas = await randomWallet.estimateGas({
to: env.l2Wallet.address,
value: ethers.utils.parseEther('0.1'),
})
const gasPrice = await env.l2Provider.getGasPrice()
const estimatedGasFee = estimatedGas.mul(gasPrice)

// should transfer funds back
const balance = await randomWallet.getBalance()
await randomWallet.sendTransaction({
to: env.l2Wallet.address,
value: balance.sub(estimatedGasFee),
gasLimit: estimatedGas.toNumber(),
})

const postBalance = await randomWallet.getBalance()
expect(postBalance).to.be.eq(ethers.BigNumber.from('0'))

await env.l2Wallet.sendTransaction({
to: randomWallet.address,
value: ethers.utils.parseEther('1'),
})

// the gas fee should be constant
await randomWallet.sendTransaction({
to: env.l2Wallet.address,
value: balance.sub(estimatedGasFee).sub(BigNumber.from('10')),
gasLimit: estimatedGas.toNumber(),
})

const postBalance2 = await randomWallet.getBalance()
expect(postBalance2).to.be.eq(ethers.BigNumber.from('10'))

// should reject tx if gas limit is not provided
await env.l2Wallet.sendTransaction({
to: randomWallet.address,
value: ethers.utils.parseEther('1'),
})

await expect(
randomWallet.sendTransaction({
to: env.l2Wallet.address,
value: balance.sub(estimatedGasFee),
})
).to.be.rejected

await setPrices(env, 1)
})

// https://github.com/bobanetwork/boba/pull/22
it('{tag:other} should be able to configure l1 gas price in a rare situation', async () => {
// This blocks all txs, because the gas usage for the l1 security fee is too large
Expand Down
5 changes: 5 additions & 0 deletions l2geth/rollup/sync_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,11 @@ func (s *SyncService) verifyFee(tx *types.Transaction) error {
nextBlockNumber := new(big.Int).Add(s.bc.CurrentBlock().Number(), big.NewInt(1))
isFeeTokenUpdate := s.bc.Config().IsFeeTokenUpdate(nextBlockNumber)

// After fee token hard fork, tx.Gas() includes l1 security fee
if isFeeTokenUpdate {
fee = new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas()))
}

state, err := s.bc.State()
if err != nil {
return err
Expand Down
93 changes: 90 additions & 3 deletions l2geth/rollup/sync_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,65 @@ func TestFeeGasPriceOracleOwnerTransactions(t *testing.T) {
}
}

func TestVerifyFee(t *testing.T) {
// Generate a key
key, _ := crypto.GenerateKey()
sender := crypto.PubkeyToAddress(key.PublicKey)
// Generate a new service
service, _, _, err := newTestSyncService(true, &sender)
if err != nil {
t.Fatal(err)
}

signer := types.NewEIP155Signer(big.NewInt(420))
// Fees must be enforced for this test
service.enforceFees = true
// Create a mock transaction and sign using the
// sender's key
tx := mockNoneZeroGasLimitGasPriceTx(100000000000000, big.NewInt(1))
// Make sure the gas price is not 0 on the dummy tx
if tx.GasPrice().Cmp(common.Big0) == 0 {
t.Fatal("gas price is 0")
}
// Make sure the balance is equal to gas price * gas limit
// Get state
state, err := service.bc.State()
if err != nil {
t.Fatal("Cannot get state db")
}
balance := state.GetBalance(sender)
if balance.Cmp(new(big.Int).Mul(tx.GasPrice(), big.NewInt(int64(tx.Gas())))) != 0 {
t.Fatal("balance mismatch")
}
// Sign the dummy tx with the sender key
signedTx, err := types.SignTx(tx, signer, key)
if err != nil {
t.Fatal(err)
}
// Verify the fee of the signed tx, ensure it does not error
if err := service.verifyFee(signedTx); err != nil {
t.Fatal(err)
}
badTx := mockNoneZeroGasLimitGasPriceTx(100000000000000, big.NewInt(2))
// Make sure the gas price is not 0 on the dummy tx
if tx.GasPrice().Cmp(common.Big0) == 0 {
t.Fatal("gas price is 0")
}
// Make sure the balance is not equal to gas price * gas limit
if balance.Cmp(new(big.Int).Mul(badTx.GasPrice(), big.NewInt(int64(badTx.Gas())))) > 0 {
t.Fatal("balance match")
}
// Sign the dummy tx with the sender key
signedTx, err = types.SignTx(badTx, signer, key)
if err != nil {
t.Fatal(err)
}
// Verify the fee of the signed tx, ensure it does not error
if err := service.verifyFee(signedTx); err == nil {
t.Fatal(err)
}
}

func TestZeroGasPriceTransactionsUsingBobaAsFeeToken(t *testing.T) {
service, _, _, err := newTestSyncService(false, nil)
if err != nil {
Expand Down Expand Up @@ -802,7 +861,7 @@ func TestInsufficientGasForL1SecurityFee(t *testing.T) {
t.Fatal(err)
}
// Create a mock transaction
tx := mockNoneZeroGasLimiteTx(100)
tx := mockNoneZeroGasLimitTx(100)
// Create oracle
service.RollupGpo = gasprice.NewRollupOracle()
// Get state
Expand Down Expand Up @@ -831,7 +890,7 @@ func TestInsufficientGasForL1SecurityFee(t *testing.T) {
t.Fatal("err is nil")
}
// Type 2 -- fee / l2GasPrice <= tx.Gas
tx = mockNoneZeroGasLimiteTx(30000)
tx = mockNoneZeroGasLimitTx(30000)
overhead = big.NewInt(1)
state.SetState(rcfg.L2GasPriceOracleAddress, rcfg.OverheadSlot, common.BigToHash(overhead))
_, _ = state.Commit(false)
Expand Down Expand Up @@ -1242,7 +1301,7 @@ func mockTx() *types.Transaction {
return tx
}

func mockNoneZeroGasLimiteTx(gasLimit uint64) *types.Transaction {
func mockNoneZeroGasLimitTx(gasLimit uint64) *types.Transaction {
address := make([]byte, 20)
rand.Read(address)

Expand Down Expand Up @@ -1270,6 +1329,34 @@ func mockNoneZeroGasLimiteTx(gasLimit uint64) *types.Transaction {
return tx
}

func mockNoneZeroGasLimitGasPriceTx(gasLimit uint64, gasPrice *big.Int) *types.Transaction {
address := make([]byte, 20)
rand.Read(address)

target := common.BytesToAddress(address)
timestamp := uint64(0)

rand.Read(address)
l1TxOrigin := common.BytesToAddress(address)

data := []byte{0x00, 0x00}
l1BlockNumber := big.NewInt(0)

tx := types.NewTransaction(0, target, big.NewInt(0), gasLimit, gasPrice, data)
meta := types.NewTransactionMeta(
l1BlockNumber,
timestamp,
[]byte{0},
&l1TxOrigin,
types.QueueOriginSequencer,
nil,
nil,
nil,
)
tx.SetTransactionMeta(meta)
return tx
}

func setMockTxL1Timestamp(tx *types.Transaction, ts uint64) *types.Transaction {
meta := tx.GetMeta()
meta.L1Timestamp = ts
Expand Down
81 changes: 53 additions & 28 deletions ops_boba/api/metatransaction-api/metaTransaction_getTestnetETH.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,8 @@ const ethers = require('ethers')
const YAML = require('yaml')
const fs = require('fs')

// Load env
const file = fs.readFileSync('./env.yml', 'utf8')
const env = YAML.parse(file)
const L2_NODE_WEB3_URL = env.L2_NODE_WEB3_TESTNET_URL === undefined ? env.L2_NODE_WEB3_URL : env.L2_NODE_WEB3_TESTNET_URL
const PRIVATE_KEY = env.PRIVATE_KEY_FAUCET
const BOBA_AUTHENTICATEDFAUCET_ADDRESS = env.BOBA_AUTHENTICATEDFAUCET_TESTNET_ADDRESS

// Get provider and wallet
const l2Provider = new ethers.providers.JsonRpcProvider(L2_NODE_WEB3_URL)
const l2Wallet = new ethers.Wallet(PRIVATE_KEY).connect(l2Provider)

// ABI
const TwitterAuthenticatedFaucetInterface = new ethers.utils.Interface([
'function sendFundsMeta(address,string,bytes32,bytes)',
])

// Load contracts
const Boba_AuthenticatedFaucet = new ethers.Contract(
BOBA_AUTHENTICATEDFAUCET_ADDRESS,
TwitterAuthenticatedFaucetInterface,
l2Wallet
)
// Support local tests
require('dotenv').config()

const headers = {
'Access-Control-Allow-Origin': '*',
Expand All @@ -36,14 +16,58 @@ const headers = {
'Permissions-Policy': '*',
}

// Load contracts
const loadContracts = () => {
// Load env
let env = process.env
if (fs.existsSync('./env.yml')) {
const file = fs.readFileSync('./env.yml', 'utf8')
env = YAML.parse(file)
}
const L2_NODE_WEB3_URL =
env.L2_NODE_WEB3_TESTNET_URL === undefined
? env.L2_NODE_WEB3_URL
: env.L2_NODE_WEB3_TESTNET_URL
const PRIVATE_KEY = env.PRIVATE_KEY_FAUCET
const BOBA_AUTHENTICATEDFAUCET_ADDRESS =
env.BOBA_AUTHENTICATEDFAUCET_TESTNET_ADDRESS

// Get provider and wallet
const l2Provider = new ethers.providers.JsonRpcProvider(L2_NODE_WEB3_URL)
const l2Wallet = new ethers.Wallet(PRIVATE_KEY).connect(l2Provider)

// ABI
const TwitterAuthenticatedFaucetInterface = new ethers.utils.Interface([
'function sendFundsMeta(address,string,bytes32,bytes)',
])

// Load contracts
const Boba_AuthenticatedFaucet = new ethers.Contract(
BOBA_AUTHENTICATEDFAUCET_ADDRESS,
TwitterAuthenticatedFaucetInterface,
l2Wallet
)
return Boba_AuthenticatedFaucet
}

// Verify message and send to node if it's correct
module.exports.mainnetHandler = async (event, context, callback) => {
const body = JSON.parse(event.body)

const { hashedMsg, signature, tweetId, walletAddress } = body

const Boba_AuthenticatedFaucet = loadContracts()

// Send transaction to node
try {
console.log("SendFundsMeta: ", walletAddress, tweetId, hashedMsg, signature, L2_NODE_WEB3_URL)
console.log(
'SendFundsMeta: ',
walletAddress,
tweetId,
hashedMsg,
signature,
L2_NODE_WEB3_URL
)

await Boba_AuthenticatedFaucet.estimateGas.sendFundsMeta(
walletAddress,
Expand All @@ -58,8 +82,7 @@ module.exports.mainnetHandler = async (event, context, callback) => {
hashedMsg,
signature
)
await execTx.wait();

await execTx.wait()
} catch (err) {
console.error(err)
return callback(null, {
Expand All @@ -81,9 +104,12 @@ module.exports.rinkebyHandler = async (event, context, callback) => {
const body = JSON.parse(event.body)

const { hashedMsg, signature, tweetId, walletAddress } = body

const Boba_AuthenticatedFaucet = loadContracts()

// Send transaction to node
try {
console.log("SendFundsMeta: ", walletAddress, tweetId, hashedMsg, signature)
console.log('SendFundsMeta: ', walletAddress, tweetId, hashedMsg, signature)

await Boba_AuthenticatedFaucet.estimateGas.sendFundsMeta(
walletAddress,
Expand All @@ -98,8 +124,7 @@ module.exports.rinkebyHandler = async (event, context, callback) => {
hashedMsg,
signature
)
await execTx.wait();

await execTx.wait()
} catch (err) {
console.error(err)
return callback(null, {
Expand Down

0 comments on commit b47739e

Please sign in to comment.