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

Charge l1 data availability fee #86

Merged
merged 24 commits into from
Sep 11, 2023
Merged

Conversation

matYang
Copy link
Collaborator

@matYang matYang commented Aug 28, 2023

Motivation

When sending to supported L2 rollups, we undercharge the fee as execution gas cost only represents tiny portion of the fee. Most of the cost at destination comes from L1 security fee.

Solution

Currently supported L2 rollups are optimistic rollups. Their L1 security fee can be estimated with equation

txCost = l2_gasPrice * l2_gasUsed // L2 EVM execution cost
  + l1_base_fee // set by the L2
    * (length_in_bytes(tx_data) * gas_per_byte + fixed_overhead)
    * dynamic_compression_factor // L1 calldata cost

@matYang matYang requested a review from connorwstein August 28, 2023 04:34
@matYang matYang changed the title Feature/charge l1 security fee [WIP] Charge l1 security fee Aug 28, 2023
@github-actions
Copy link
Contributor

LCOV of commit 18d44e2 during Solidity Foundry #329

Summary coverage rate:
  lines......: 0.0% (0 of 909 lines)
  functions..: 0.0% (0 of 199 functions)
  branches...: 0.0% (0 of 386 branches)

Files changed coverage rate: n/a

⛔ The code coverage is too low: 0. Expected at least 98.

@github-actions
Copy link
Contributor

Go solidity wrappers are out-of-date, regenerate them via the make wrappers-all command


// little endian
function _unpackUint224Into112(uint224 value) internal pure returns (uint112, uint112) {
return (uint112(value & UINT_112__MASK), uint112((value >> 112) & UINT_112__MASK));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do you need the mask or is downcasting enough already?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

good point, downcasting truncates higher order bits, that'll just work

@github-actions
Copy link
Contributor

LCOV of commit 3b6da8a during Solidity Foundry #378

Summary coverage rate:
  lines......: 0.0% (0 of 894 lines)
  functions..: 0.0% (0 of 194 functions)
  branches...: 0.0% (0 of 378 branches)

Files changed coverage rate: n/a

⛔ The code coverage is too low: 0. Expected at least 98.

@github-actions
Copy link
Contributor

Go solidity wrappers are out-of-date, regenerate them via the make wrappers-all command

@github-actions
Copy link
Contributor

LCOV of commit 3bdab64 during Solidity Foundry #380

Summary coverage rate:
  lines......: 0.0% (0 of 894 lines)
  functions..: 0.0% (0 of 194 functions)
  branches...: 0.0% (0 of 378 branches)

Files changed coverage rate: n/a

⛔ The code coverage is too low: 0. Expected at least 98.

@github-actions
Copy link
Contributor

Go solidity wrappers are out-of-date, regenerate them via the make wrappers-all command

@github-actions
Copy link
Contributor

LCOV of commit 2079ffb during Solidity Foundry #387

Summary coverage rate:
  lines......: 98.3% (883 of 898 lines)
  functions..: 95.9% (187 of 195 functions)
  branches...: 90.5% (342 of 378 branches)

Files changed coverage rate: n/a

@github-actions
Copy link
Contributor

Go solidity wrappers are out-of-date, regenerate them via the make wrappers-all command

@github-actions
Copy link
Contributor

LCOV of commit bc6e602 during Solidity Foundry #388

Summary coverage rate:
  lines......: 98.9% (888 of 898 lines)
  functions..: 96.4% (188 of 195 functions)
  branches...: 90.7% (343 of 378 branches)

Files changed coverage rate: n/a

@github-actions
Copy link
Contributor

Go solidity wrappers are out-of-date, regenerate them via the make wrappers-all command

@github-actions
Copy link
Contributor

LCOV of commit 8465a72 during Solidity Foundry #398

Summary coverage rate:
  lines......: 98.9% (888 of 898 lines)
  functions..: 96.4% (188 of 195 functions)
  branches...: 90.7% (343 of 378 branches)

Files changed coverage rate: n/a

@matYang matYang changed the title [WIP] Charge l1 security fee Charge l1 security fee Aug 30, 2023
@matYang matYang marked this pull request as ready for review August 30, 2023 22:05
@matYang matYang requested a review from a team as a code owner August 30, 2023 22:05
uint16 maxTokensLength; // | Maximum number of ERC20 token transfers that can be included per message
uint32 destGasOverhead; // | Extra gas charged on top of the gasLimit
uint16 destGasPerPayloadByte; // | Destination chain gas charged per byte of `data` payload
uint32 destCalldataOverhead; // | Extra L1 calldata gas on top of the message used in security fee
Copy link
Collaborator

Choose a reason for hiding this comment

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

let's add more info here, because L1 doesn't make sense for any L1. Also, i've never heard the term "security fee"

Copy link
Collaborator

Choose a reason for hiding this comment

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

I've heard of security fee, but as far as nomenclature goes I think DA (data availability) is the most general purpose/generic and we should use it everywhere. L2's happen to store the data as calldata, but need not, its really just about paying a fee to ensure the data is made available. Should be able to leave out the notion of L1/L2's all together as well

uint32 destGasOverhead; // | Extra gas charged on top of the gasLimit
uint16 destGasPerPayloadByte; // | Destination chain gas charged per byte of `data` payload
uint32 destCalldataOverhead; // | Extra L1 calldata gas on top of the message used in security fee
uint16 destGasPerCalldataByte; // ---┘ Number of L1 calldata gas to charge per byte of message
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: amount of instead of number of

return evm_2_evm_onramp.EVM2EVMOnRampDynamicConfig{}, err
}
return evm_2_evm_onramp.EVM2EVMOnRampDynamicConfig{
Router: legacyDynamicConfig.Router,
Copy link
Collaborator

Choose a reason for hiding this comment

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

You're missing some values that are in 1.1

@@ -39,7 +39,7 @@ type CCIPE2ELoad struct {
CallTimeOut time.Duration // max time to wait for various on-chain events
reports *testreporters.CCIPLaneStats
msg router.ClientEVM2AnyMessage
MaxDataSize uint32
MaxDataSize *big.Int
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this big int because go doesn' have uint24?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think so.
uint16 gives us 64KB, that's not enough, Ethereum can do >120KB.
uint32 gives us 4GB, that's seems a bit too big.
so went with uint24, and it become big.int in generated code

@@ -34,15 +34,15 @@ contract PriceRegistry is IPriceRegistry, OwnerIsCreator {
/// Very Expensive: 1 unit of gas costs 1 USD -> 1e18
/// Expensive: 1 unit of gas costs 0.1 USD -> 1e17
/// Cheap: 1 unit of gas costs 0.000001 USD -> 1e12
mapping(uint64 destChainSelector => Internal.TimestampedUint192Value price)
mapping(uint64 destChainSelector => Internal.TimestampedPackedUint224 price)
Copy link
Collaborator

Choose a reason for hiding this comment

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

comments need updating


/// @notice Get the `tokenPrice` for an array of tokens.
/// @param tokens The tokens to get prices for.
/// @return tokenPrices The tokenPrices for the given tokens.
function getTokenPrices(address[] calldata tokens) external view returns (Internal.TimestampedUint192Value[] memory);
function getTokenPrices(address[] calldata tokens) external view returns (Internal.TimestampedPackedUint224[] memory);

/// @notice Get the `gasPrice` for a given destination chain ID.
/// @param destChainSelector The destination chain to get the price for.
/// @return gasPrice The gasPrice for the given destination chain ID.
function getDestinationChainGasPrice(
Copy link
Collaborator

Choose a reason for hiding this comment

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

probably update the comment here? it now returns a multi-dimensional gas price

}

/// @dev Gas price is stored in 112-bit unsigned int. uint224 can pack 2 prices.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we abstract this from price reg clients all together? Since the price reg API is breaking anyways, something like getValidatedExecutionPrices(destinationSelector)->(gasPrice, daPrice) where the 2 return values are already parsed for the client. Also that way I think we don't even need the multiplier check (if daPrice == 0, then DA is free/covered by gasPrice)

Copy link
Collaborator

Choose a reason for hiding this comment

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

spoke offline - I'm cool with opaque gas price and ramps interpreting

@@ -13,7 +13,7 @@ import {EnumerableSet} from "../vendor/openzeppelin-solidity/v4.8.0/contracts/ut
/// and the price of a token in USD allowing the owner or priceUpdater to update this value.
contract PriceRegistry is IPriceRegistry, OwnerIsCreator {
using EnumerableSet for EnumerableSet.AddressSet;
using USDPriceWith18Decimals for uint192;
Copy link
Collaborator

Choose a reason for hiding this comment

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

great opportunity to add a type and version to the price reg

) external view override returns (uint192 tokenPrice, uint192 gasPriceValue) {
Internal.TimestampedUint192Value memory gasPrice = s_usdPerUnitGasByDestChainSelector[destChainSelector];
) external view override returns (uint224 tokenPrice, uint224 gasPriceValue) {
Internal.TimestampedPackedUint224 memory gasPrice = s_usdPerUnitGasByDestChainSelector[destChainSelector];
Copy link
Collaborator

Choose a reason for hiding this comment

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

thoughts on an internal _getValidatedExecutionPrices analogous to the _getValidatedTokenPrice? Yields some nice symmetry validatedXX means timestamp checked for both gas and tokens

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

will include in a follow up PR that includes _getValidatedGasPrices and getValidatedExecutionPrices

uint16 maxTokensLength; // | Maximum number of ERC20 token transfers that can be included per message
uint32 destGasOverhead; // | Extra gas charged on top of the gasLimit
uint16 destGasPerPayloadByte; // | Destination chain gas charged per byte of `data` payload
uint32 destCalldataOverhead; // | Extra L1 calldata gas on top of the message used in security fee
Copy link
Collaborator

Choose a reason for hiding this comment

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

I've heard of security fee, but as far as nomenclature goes I think DA (data availability) is the most general purpose/generic and we should use it everywhere. L2's happen to store the data as calldata, but need not, its really just about paying a fee to ensure the data is made available. Should be able to leave out the notion of L1/L2's all together as well

@@ -9,19 +9,24 @@ library Internal {
struct PriceUpdates {
TokenPriceUpdate[] tokenPriceUpdates;
uint64 destChainSelector; // --┐ Destination chain selector
uint192 usdPerUnitGas; // -----┘ 1e18 USD per smallest unit (e.g. wei) of destination chain gas
uint224 usdPerUnitGas; // -----┘ 1e18 USD per smallest unit (e.g. wei) of destination chain gas
Copy link
Collaborator

Choose a reason for hiding this comment

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

while we're changing this interface, we should open the door for batched gas price updates i.e. have a GasPriceUpdate[] where GasPriceUpdate{DestChainSelector, GasPrice}. Can be a follow up if painful to add now

Copy link
Collaborator

Choose a reason for hiding this comment

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

As long as it is in 1.2 and doesn't bleed into 1.3 I don't mind doing it in follow up

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'll post it in a followup PR, this is PR is already quite big.

/// Each variable array takes 1 more slot to store its length.
/// Hence, when abiEncoded, excluding the dynamic contents of variable arrays,
/// EVM2EVMMessage takes up a fixed number of 14 lots, 32 bytes each.
uint256 public constant MESSAGE_FIXED_BYTES = 32 * 14;
Copy link
Collaborator

Choose a reason for hiding this comment

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

hmm I'm ok with it here as its a tightly coupled property of EVM2EVMMessage

uint256 numberOfTokens,
uint32 tokenTransferBytesOverhead
) internal view returns (uint256 calldataCostUSD) {
uint256 calldataLength = Internal.MESSAGE_FIXED_BYTES +
Copy link
Collaborator

Choose a reason for hiding this comment

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

would use units suffixes everywhere i.e. calldataLengthBytes, s_dynamicConfig.destCalldataOverheadGas etc

@matYang matYang changed the title Charge l1 security fee Charge l1 data availability fee Sep 6, 2023
@github-actions
Copy link
Contributor

github-actions bot commented Sep 6, 2023

LCOV of commit 125f004 during Solidity Foundry #454

Summary coverage rate:
  lines......: 98.9% (909 of 919 lines)
  functions..: 96.4% (189 of 196 functions)
  branches...: 91.1% (357 of 392 branches)

Files changed coverage rate: n/a

@@ -29,20 +30,27 @@ contract PriceRegistry is IPriceRegistry, OwnerIsCreator {
event UsdPerUnitGasUpdated(uint64 indexed destChain, uint256 value, uint256 timestamp);
event UsdPerTokenUpdated(address indexed token, uint256 value, uint256 timestamp);

/// @dev The price, in USD with 18 decimals, of 1 unit of gas for a given destination chain.
// STATIC CONFIG
Copy link
Collaborator

Choose a reason for hiding this comment

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

not sure we need this header for a single item

/// @notice Get the `gasPrice` for a given destination chain ID.
/// @notice Get an encoded `gasPrice` for a given destination chain ID.
/// The 224-bit result could encode multiple price components. For example, if Optimism is the destination chain,
/// gas price can include L1 base fee in higher-order bits, and L2 gas price in lower-order bits.
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: gas price can include
it's not can, it always will, right? If not explain when this is and isnt the case

uint16 destGasPerDataAvailabilityByte; // --┘ Amount of gas to charge per byte of data that needs availability
uint16 destDataAvailabilityMultiplier; // --┐ Multiplier for data availability gas, multples of 1e-4, or 0.0001
address priceRegistry; // | Price registry address
uint24 maxDataSize; // | Maximum payload data size, max 16MB
Copy link
Collaborator

Choose a reason for hiding this comment

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

Let's make this uint32 as it fits and 24 is a strange datatype that becomes big.int offchain

@github-actions
Copy link
Contributor

github-actions bot commented Sep 7, 2023

LCOV of commit 178c6f8 during Solidity Foundry #468

Summary coverage rate:
  lines......: 98.9% (909 of 919 lines)
  functions..: 96.4% (189 of 196 functions)
  branches...: 91.1% (357 of 392 branches)

Files changed coverage rate: n/a

@RensR RensR self-requested a review September 7, 2023 21:19
@github-actions
Copy link
Contributor

github-actions bot commented Sep 9, 2023

LCOV of commit 6e2be8f during Solidity Foundry #481

Summary coverage rate:
  lines......: 98.9% (909 of 919 lines)
  functions..: 96.4% (189 of 196 functions)
  branches...: 91.1% (357 of 392 branches)

Files changed coverage rate: n/a

@github-actions
Copy link
Contributor

LCOV of commit 6807c01 during Solidity Foundry #482

Summary coverage rate:
  lines......: 98.9% (909 of 919 lines)
  functions..: 96.4% (189 of 196 functions)
  branches...: 91.1% (357 of 392 branches)

Files changed coverage rate: n/a

@github-actions
Copy link
Contributor

LCOV of commit e813a07 during Solidity Foundry #483

Summary coverage rate:
  lines......: 98.9% (909 of 919 lines)
  functions..: 96.4% (189 of 196 functions)
  branches...: 91.1% (357 of 392 branches)

Files changed coverage rate: n/a

@github-actions
Copy link
Contributor

LCOV of commit 7f22a73 during Solidity Foundry #488

Summary coverage rate:
  lines......: 98.9% (909 of 919 lines)
  functions..: 96.4% (189 of 196 functions)
  branches...: 91.1% (357 of 392 branches)

Files changed coverage rate: n/a

@matYang matYang merged commit e41e93a into ccip-develop Sep 11, 2023
71 checks passed
@matYang matYang deleted the feature/charge-l1-security-fee branch September 11, 2023 19:15
chainchad pushed a commit to chainchad/ccip that referenced this pull request Jan 24, 2024
asoliman92 pushed a commit that referenced this pull request Jul 31, 2024
* add uint224 price split and call data config fields

* add calldata calculation

* address early comments

* add more comments to explain config values

* completing onramp

* fill in transfer fee tests

* add tests for getMessageCalldataCostUSD

* onchain tests complete

* complete onchain portion

* update offchain tests

* update snapshot

* fix loadgen test MaxDataSize

* better variable names

* update changelog

* address comments

* address comments

* re-generate snapshot

* update loadgen

* reset submodule

* update snapshot
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants