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

update L1Block contract with new fields for 4844 #5

Draft
wants to merge 9 commits into
base: 4844/rebase
Choose a base branch
from

Conversation

roberto-bayardo
Copy link
Owner

No description provided.

/// roots to L1. Adds 68 bytes of padding to account for the fact that the input does
/// not have a signature.
/// @notice Computes the amount of L1 gas used for a transaction. Adds 68 bytes
/// of padding to account for the fact that the input does not have a signature.
/// @param _data Unsigned fully RLP-encoded transaction to get the L1 gas for.
/// @return Amount of L1 gas used to publish the transaction.
function getL1GasUsed(bytes memory _data) public view returns (uint256) {
Copy link

Choose a reason for hiding this comment

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

Looks like this can be pure

Comment on lines 115 to 116
uint256 compressedTxSize = total / 16;
return compressedTxSize + (68 * 16);
Copy link

@mds1 mds1 Dec 14, 2023

Choose a reason for hiding this comment

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

We can remove some precision error here by moving division to the end, the current version will slightly underestimate due to flooring when computing total / 16

Suggested change
uint256 compressedTxSize = total / 16;
return compressedTxSize + (68 * 16);
return (total + (68 * 16 * 16)) / 16;

Copy link
Collaborator

Choose a reason for hiding this comment

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

@mds1 do you mean return (total + (68 * 16)) / 16; ?

Copy link

Choose a reason for hiding this comment

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

I think the original suggestion was correct, here's my work:

gasUsed = compressedTxSize + (68 * 16)
gasUsed = (total / 16) + (68 * 16)
gasUsed * 16 = total + (68 * 16 * 16)
gasUsed = (total + (68 * 16 * 16)) / 16

Copy link

Choose a reason for hiding this comment

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

Oh I see, I was missing a set of parentheses, just edited it 😅

Copy link
Collaborator

Choose a reason for hiding this comment

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

ok yes makes sense!

@anikaraghu anikaraghu changed the base branch from 4844/rebase to develop December 15, 2023 01:22
@anikaraghu anikaraghu changed the base branch from develop to 4844/rebase December 15, 2023 01:24
uint256 public l1FeeOverhead;

/// @notice The scalar value applied to the L1 portion of the transaction fee.
/// @custom:legacy
uint256 public l1FeeScalar;
Copy link
Collaborator

@anikaraghu anikaraghu Dec 15, 2023

Choose a reason for hiding this comment

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

would be nice to make l1FeeOverhead and l1FeeScalar internal so that people don't call them, but then we can't use them in the GPO contract (which we need for backwards compatibility)

Copy link

Choose a reason for hiding this comment

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

If we really wanted that we could revert on msg.sender but then people can spoof with eth_call anyways

msg.sender == L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).DEPOSITOR_ACCOUNT(),
"GasPriceOracle: only the depositor account can set isEclipse flag"
);
isEclipse = _isEclipse;
Copy link

Choose a reason for hiding this comment

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

Perhaps only allow for setting it to true because there isn't really a concept of turning a hardfork off

/// @notice Retrieves the current fee overhead.
/// @return Current fee overhead.
function overhead() public view returns (uint256) {
return L1Block(Predeploys.L1_BLOCK_ATTRIBUTES).l1FeeOverhead();
function overhead() public pure returns (uint256) {
Copy link

Choose a reason for hiding this comment

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

We would still want these functions to operate in the non eclipse mode

Copy link
Collaborator

Choose a reason for hiding this comment

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

ah yes fair

uint256 l1GasUsed = getL1GasUsed(_data);
uint256 l1Fee = l1GasUsed * l1BaseFee();
uint256 unscaled;
if (isEclipse) {
Copy link

Choose a reason for hiding this comment

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

How would you feel about breaking this up into 2 internal functions so each branch could be easily observed individually?

Copy link
Collaborator

Choose a reason for hiding this comment

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

sounds reasonable to me

@@ -83,7 +122,7 @@ contract GasPriceOracle is ISemver {
/// not have a signature.
/// @param _data Unsigned fully RLP-encoded transaction to get the L1 gas for.
/// @return Amount of L1 gas used to publish the transaction.
function getL1GasUsed(bytes memory _data) public view returns (uint256) {
function _getL1GasUsedPreEclipse(bytes memory _data) internal view returns (uint256) {
Copy link

Choose a reason for hiding this comment

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

Is this used? I don't see branching logic in getL1GasUsed

Also the name is very verbose but we don't want to break the public api

Copy link
Collaborator

Choose a reason for hiding this comment

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

oops yes I will just do the branching logic in the original function, got confused for a sec


(uint64 _number, uint64 _timestamp, uint256 _basefee, uint256 _blobBaseFee, bytes32 _hash,
uint64 _sequenceNumber, bytes32 _batcherHash, uint32 _baseFeeScalar, uint32 _blobBaseFeeScalar) =
abi.decode(msg.data[4:], (uint64, uint64, uint256, uint256, bytes32, uint64, bytes32, uint32, uint32));
Copy link

Choose a reason for hiding this comment

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

Are you sure this will tightly pack things?

Copy link
Collaborator

Choose a reason for hiding this comment

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

no not 100% sure, definitely need to test this 😅 and also add some validations probably. @tynes did you have a different method for parsing the msg.data in mind though?

Copy link
Collaborator

Choose a reason for hiding this comment

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

ah I see, I can manually check the bits Im interested in

Copy link
Owner Author

@roberto-bayardo roberto-bayardo Dec 17, 2023

Choose a reason for hiding this comment

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

[edit] yes I believe abi.encode/decode assumes 32 bytes for each fixed-width parameter regardless of type. there's abi.encodePacked but there's no corresponding decoder. I think we will want to just assume each is fixed-width encoded big endian? Looks like this would be straightforward to decode in Solidity (see "Integer types" section on page 66 of https://buildmedia.readthedocs.org/media/pdf/solidity/v0.5.11/solidity.pdf)

Copy link
Owner Author

@roberto-bayardo roberto-bayardo Dec 19, 2023

Choose a reason for hiding this comment

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

I've documented the layout in a spec update PR here: https://github.com/roberto-bayardo/optimism/pull/6/files

@roberto-bayardo roberto-bayardo force-pushed the 4844/rebase branch 2 times, most recently from a370485 to 6bb91fa Compare December 18, 2023 16:12
if (isEclipse) {
return _getL1GasUsed(_data);
}
return _getL1GasUsedPreEclipse(_data);
Copy link

Choose a reason for hiding this comment

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

nit: _getL1GasUsedBedrock and _getL1GasUsedEclipse. Will make future extensions easier

@@ -72,5 +84,66 @@ contract L1Block is ISemver {
batcherHash = _batcherHash;
l1FeeOverhead = _l1FeeOverhead;
l1FeeScalar = _l1FeeScalar;

(uint256 _blobBaseFee) = abi.decode(msg.data[260:], (uint256));
Copy link

Choose a reason for hiding this comment

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

This function should not change

Copy link
Collaborator

Choose a reason for hiding this comment

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

oops sorry was just testing somethig

uint32 _blobBaseFeeScalar;

assembly {
_number := mload(add(_data, 0x8))
Copy link

Choose a reason for hiding this comment

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

chatgpt should be able to autogen this code with a good prompt

require(msg.sender == DEPOSITOR_ACCOUNT, "L1Block: only the depositor account can set L1 block values");

bytes memory _data = msg.data[4:];
// if (_msgData.length != XXX) { // TODO: configure
Copy link

Choose a reason for hiding this comment

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

We should omit this revert check to save gas. The more gas we can save here, the more scalable the system is because it saves more gas for user transactions. We should be able to do everything using calldataload instead of msg.data. We get good test coverage through differential testing against the go code


/// @notice Updates the L1 block values for a post-blob activated chain.
/// Params are passed in as part of msg.data in order to compress the calldata.
/// Params should be passed in in the following order:
Copy link

Choose a reason for hiding this comment

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

These should be in order so they are 32 byte aligned so that it is easy to use calldataload to pop all of the values to the stack

uint256 divisor = 10 ** DECIMALS;
uint256 unscaled = l1Fee * scalar();
uint256 scaled = unscaled / divisor;
return scaled;
}

Copy link

Choose a reason for hiding this comment

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

this needs natspec

@tynes
Copy link

tynes commented Dec 19, 2023

Thoughts on breaking out the smart contract changes solely in this PR?

protolambda and others added 2 commits December 19, 2023 15:33
Original rebased prototype by proto, plus changes from Roberto:
- encapsulate data <-> blob conversion code, add unit tests
- update 4844 code to be compatible with latest beacon node api
- remove stray file, include one more invalid blob test
- appropriate fee bumping & blob fee estimation for blob transactions
- misc other improvements
contract GasPriceOracleBedrock_Test is GasPriceOracle_Test {
/// @dev Sets up the test suite.
function setUp() public virtual override {
super.setUp();
depositor = l1Block.DEPOSITOR_ACCOUNT();
Copy link

Choose a reason for hiding this comment

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

we can delete this assignment of depositor since now it happens in super.setUp

bytes4 functionSignature = bytes4(keccak256("setL1BlockValuesV2()"));

// Encode the function signature and extra data
bytes memory callDataPacked = abi.encodePacked(
Copy link

Choose a reason for hiding this comment

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

You technically can just add the function signature in as the first arg here and not need to calls to abi.encodePacked

super.setUp();

// Define the function signature
bytes4 functionSignature = bytes4(keccak256("setL1BlockValuesV2()"));
Copy link

Choose a reason for hiding this comment

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

We agreed on updating this to setL1BlockValuesEcotone

@@ -93,3 +102,90 @@ contract GasPriceOracle_Test is CommonTest {
assertEq(returndata, hex"");
}
}

Copy link

Choose a reason for hiding this comment

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

I would recommend making a GasPriceOracleEclipseTransition_Test contract and explicitly test the transition. Then in GasPriceOracleEclipse_Test I would recommend using vm.store to set isEclipse to true directly via storage and then test the functionality that way. Also it should be isEcotone now

Copy link

Choose a reason for hiding this comment

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

This will make the hardfork activation testing explicit and not part of setup and decouple the tests cleanly from implementation and transition

function setUp() public virtual override {
super.setUp();

bytes4 functionSignature = bytes4(keccak256("setL1BlockValuesV2()"));
Copy link

Choose a reason for hiding this comment

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

We should consider writing a simple library that can be imported that can abi encode the data properly and used in many locations. It would accept all of the args and return bytes with the function selector prefixed to the fixed size encodings of each arg

Copy link

Choose a reason for hiding this comment

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

We would basically be able to delete the setup function and instead only fuzz

(bool success, ) = address(l1Block).call(functionCallDataPacked);
require(success, "Function call failed");
}

Copy link

Choose a reason for hiding this comment

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

A fuzz test here would be useful. To be honest I think only having a fuzz test is much more useful than having tests that assert against "magic" values that are set in setup. I know you are following precedent, curious what you think

string public constant version = "1.2.0";

/// @notice Flag that indicates whether the network is in eclipse mode.
bool public isEclipse;
Copy link

Choose a reason for hiding this comment

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

This should be isEcotone

Comment on lines +114 to +116
assembly {
let offset := 0x4
_number := shr(192, calldataload(offset)) // uint64
Copy link

Choose a reason for hiding this comment

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

Is dropping into assembly and using calldataload cheaper than saying in solidity and indexing into msg.data? The latter would be more readable, but assembly is ok if it provides gas savings that we need

@roberto-bayardo roberto-bayardo force-pushed the 4844/rebase branch 4 times, most recently from 536034a to afc8480 Compare December 28, 2023 00:45
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.

6 participants