-
Notifications
You must be signed in to change notification settings - Fork 1
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
base: 4844/rebase
Are you sure you want to change the base?
Conversation
94f825c
to
1a917ae
Compare
/// 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) { |
There was a problem hiding this comment.
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
uint256 compressedTxSize = total / 16; | ||
return compressedTxSize + (68 * 16); |
There was a problem hiding this comment.
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
uint256 compressedTxSize = total / 16; | |
return compressedTxSize + (68 * 16); | |
return (total + (68 * 16 * 16)) / 16; |
There was a problem hiding this comment.
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;
?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok yes makes sense!
f5a3456
to
a7024d6
Compare
96728f8
to
6e4e12c
Compare
uint256 public l1FeeOverhead; | ||
|
||
/// @notice The scalar value applied to the L1 portion of the transaction fee. | ||
/// @custom:legacy | ||
uint256 public l1FeeScalar; |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
a370485
to
6bb91fa
Compare
if (isEclipse) { | ||
return _getL1GasUsed(_data); | ||
} | ||
return _getL1GasUsedPreEclipse(_data); |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this needs natspec
Thoughts on breaking out the smart contract changes solely in this PR? |
6bb91fa
to
b96fc11
Compare
181663b
to
9cf46ce
Compare
b96fc11
to
3779153
Compare
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
3779153
to
899346c
Compare
9cf46ce
to
07278b7
Compare
contract GasPriceOracleBedrock_Test is GasPriceOracle_Test { | ||
/// @dev Sets up the test suite. | ||
function setUp() public virtual override { | ||
super.setUp(); | ||
depositor = l1Block.DEPOSITOR_ACCOUNT(); |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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()")); |
There was a problem hiding this comment.
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""); | |||
} | |||
} | |||
|
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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()")); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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"); | ||
} | ||
|
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be isEcotone
assembly { | ||
let offset := 0x4 | ||
_number := shr(192, calldataload(offset)) // uint64 |
There was a problem hiding this comment.
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
536034a
to
afc8480
Compare
No description provided.