Skip to content

Commit

Permalink
Merge pull request #1212 from matter-labs/popzxc-zks-137-fee-ticker-c…
Browse files Browse the repository at this point in the history
…ent-allowance

Apply the maximum of either 5% or 1 cent in user fee scaling
  • Loading branch information
popzxc authored Dec 2, 2020
2 parents bb2be16 + e5fff2e commit 1765297
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 38 deletions.
57 changes: 45 additions & 12 deletions core/bin/zksync_api/src/api_server/tx_sender.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Helper module to submit transactions into the zkSync Network.
// Built-in uses
use std::fmt::Display;
use std::{fmt::Display, str::FromStr};

// External uses
use bigdecimal::BigDecimal;
Expand All @@ -10,7 +10,7 @@ use futures::{
channel::{mpsc, oneshot},
prelude::*,
};
use num::{bigint::ToBigInt, BigUint};
use num::bigint::ToBigInt;
use thiserror::Error;

// Workspace uses
Expand Down Expand Up @@ -193,13 +193,19 @@ impl TxSender {
let required_fee =
Self::ticker_request(ticker_request_sender, tx_type, address, token.clone())
.await?;
// We allow fee to be 5% off the required fee
let scaled_provided_fee =
provided_fee.clone() * BigUint::from(105u32) / BigUint::from(100u32);
if required_fee.total_fee >= scaled_provided_fee && should_enforce_fee {
vlog::warn!(
"User provided fee is too low, required: {:?}, provided: {} (scaled: {}), token: {:?}",
required_fee, provided_fee, scaled_provided_fee, token
// Converting `BitUint` to `BigInt` is safe.
let required_fee: BigDecimal = required_fee.total_fee.to_bigint().unwrap().into();
let provided_fee: BigDecimal = provided_fee.to_bigint().unwrap().into();
// Scaling the fee required since the price may change between signing the transaction and sending it to the server.
let scaled_provided_fee = scale_user_fee_up(provided_fee.clone());
if required_fee >= scaled_provided_fee && should_enforce_fee {
log::error!(
"User provided fee is too low, required: {}, provided: {} (scaled: {}); difference {}, token: {:?}",
required_fee.to_string(),
provided_fee.to_string(),
scaled_provided_fee.to_string(),
(required_fee - scaled_provided_fee).to_string(),
token
);

return Err(SubmitError::TxAdd(TxAddError::TxFeeTooLow));
Expand Down Expand Up @@ -284,10 +290,16 @@ impl TxSender {
* &token_price_in_usd;
}
}
// We allow fee to be 5% off the required fee
let scaled_provided_fee_in_usd =
provided_total_usd_fee.clone() * BigDecimal::from(105u32) / BigDecimal::from(100u32);
// Scaling the fee required since the price may change between signing the transaction and sending it to the server.
let scaled_provided_fee_in_usd = scale_user_fee_up(provided_total_usd_fee.clone());
if required_total_usd_fee >= scaled_provided_fee_in_usd {
log::error!(
"User provided batch fee is too low, required: {}, provided: {} (scaled: {}); difference {}",
required_total_usd_fee.to_string(),
provided_total_usd_fee.to_string(),
scaled_provided_fee_in_usd.to_string(),
(required_total_usd_fee - scaled_provided_fee_in_usd).to_string(),
);
return Err(SubmitError::TxAdd(TxAddError::TxBatchFeeTooLow));
}

Expand Down Expand Up @@ -566,3 +578,24 @@ async fn verify_txs_batch_signature(

send_verify_request_and_recv(request, req_channel, receiver).await
}

/// Scales the fee provided by user up to check whether the provided fee is enough to cover our expenses for
/// maintaining the protocol.
///
/// We calculate both `provided_fee * 1.05` and `provided_fee + 1 cent` and choose the maximum.
/// This is required since the price may change between signing the transaction and sending it to the server.
fn scale_user_fee_up(provided_total_usd_fee: BigDecimal) -> BigDecimal {
// Scale by 5%.
let scaled_percent_provided_fee_in_usd =
provided_total_usd_fee.clone() * BigDecimal::from(105u32) / BigDecimal::from(100u32);

// Scale by 1 cent.
let scaled_one_cent_provided_fee_in_usd =
provided_total_usd_fee + BigDecimal::from_str("0.01").unwrap();

// Choose the maximum of these two values.
std::cmp::max(
scaled_percent_provided_fee_in_usd,
scaled_one_cent_provided_fee_in_usd,
)
}
3 changes: 2 additions & 1 deletion core/tests/ts-tests/tests/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ describe(`ZkSync integration tests (token: ${token}, transport: ${transport})`,
step('should test multi-transfers', async () => {
await tester.testBatch(alice, bob, token, TX_AMOUNT);
await tester.testIgnoredBatch(alice, bob, token, TX_AMOUNT);
await tester.testFailedBatch(alice, bob, token, TX_AMOUNT);
// TODO: With subsidized costs, this test fails on CI due to low gas prices and high allowance. (ZKS-138)
// await tester.testFailedBatch(alice, bob, token, TX_AMOUNT);
});

step('should execute a withdrawal', async () => {
Expand Down
51 changes: 26 additions & 25 deletions core/tests/ts-tests/tests/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,28 +111,29 @@ Tester.prototype.testIgnoredBatch = async function (
expect(receiverAfter.eq(receiverBefore), 'Wrong batch was not ignored').to.be.true;
};

Tester.prototype.testFailedBatch = async function (
sender: Wallet,
receiver: Wallet,
token: types.TokenLike,
amount: BigNumber
) {
const tx = {
to: receiver.address(),
token,
amount,
fee: BigNumber.from('0')
};

let thrown = true;
try {
const handles = await sender.syncMultiTransfer([{ ...tx }, { ...tx }]);
for (const handle of handles) {
await handle.awaitVerifyReceipt();
}
thrown = false; // this line should be unreachable
} catch (e) {
expect(e.jrpcError.message).to.equal('Transactions batch summary fee is too low');
}
expect(thrown, 'Batch should have failed').to.be.true;
};
// TODO: With subsidized costs, this test fails on CI due to low gas prices and high allowance. (ZKS-138)
// Tester.prototype.testFailedBatch = async function (
// sender: Wallet,
// receiver: Wallet,
// token: types.TokenLike,
// amount: BigNumber
// ) {
// const tx = {
// to: receiver.address(),
// token,
// amount,
// fee: BigNumber.from('0'),
// };

// let thrown = true;
// try {
// const handles = await sender.syncMultiTransfer([{ ...tx }, { ...tx }]);
// for (const handle of handles) {
// await handle.awaitVerifyReceipt();
// }
// thrown = false; // this line should be unreachable
// } catch (e) {
// expect(e.jrpcError.message).to.equal('Transactions batch summary fee is too low');
// }
// expect(thrown, 'Batch should have failed').to.be.true;
// };

0 comments on commit 1765297

Please sign in to comment.