-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This reverts commit 40efbb7.
- Loading branch information
Showing
8 changed files
with
2,036 additions
and
0 deletions.
There are no files selected for viewing
403 changes: 403 additions & 0 deletions
403
packages/protocol/contracts/layer1/based/ITaikoInbox.sol
Large diffs are not rendered by default.
Oops, something went wrong.
823 changes: 823 additions & 0 deletions
823
packages/protocol/contracts/layer1/based/TaikoInbox.sol
Large diffs are not rendered by default.
Oops, something went wrong.
127 changes: 127 additions & 0 deletions
127
packages/protocol/contracts/layer1/forced-inclusion/ForcedInclusionStore.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.24; | ||
|
||
import "src/shared/common/EssentialContract.sol"; | ||
import "src/shared/libs/LibMath.sol"; | ||
import "src/shared/libs/LibAddress.sol"; | ||
import "src/shared/libs/LibStrings.sol"; | ||
import "src/layer1/based/ITaikoInbox.sol"; | ||
import "./IForcedInclusionStore.sol"; | ||
|
||
/// @title ForcedInclusionStore | ||
/// @dev A contract for storing and managing forced inclusion requests. Forced inclusions allow | ||
/// users to pay a fee | ||
/// to ensure their transactions are included in a block. The contract maintains a FIFO queue | ||
/// of inclusion requests. | ||
/// @custom:security-contact | ||
contract ForcedInclusionStore is EssentialContract, IForcedInclusionStore { | ||
using LibAddress for address; | ||
using LibMath for uint256; | ||
|
||
uint256 private constant SECONDS_PER_BLOCK = 12; | ||
|
||
uint8 public immutable inclusionDelay; | ||
uint64 public immutable feeInGwei; | ||
|
||
mapping(uint256 id => ForcedInclusion inclusion) public queue; // slot 1 | ||
uint64 public head; // slot 2 | ||
uint64 public tail; | ||
uint64 public lastProcessedAtBatchId; | ||
uint64 private __reserved1; | ||
|
||
uint256[48] private __gap; | ||
|
||
constructor( | ||
address _resolver, | ||
uint8 _inclusionDelay, | ||
uint64 _feeInGwei | ||
) | ||
EssentialContract(_resolver) | ||
{ | ||
require(_inclusionDelay != 0 && _inclusionDelay % SECONDS_PER_BLOCK == 0, InvalidParams()); | ||
require(_feeInGwei != 0, InvalidParams()); | ||
|
||
inclusionDelay = _inclusionDelay; | ||
feeInGwei = _feeInGwei; | ||
} | ||
|
||
function init(address _owner) external initializer { | ||
__Essential_init(_owner); | ||
} | ||
|
||
function storeForcedInclusion( | ||
uint8 blobIndex, | ||
uint32 blobByteOffset, | ||
uint32 blobByteSize | ||
) | ||
external | ||
payable | ||
nonReentrant | ||
{ | ||
bytes32 blobHash = _blobHash(blobIndex); | ||
require(blobHash != bytes32(0), BlobNotFound()); | ||
require(msg.value == feeInGwei * 1 gwei, IncorrectFee()); | ||
|
||
ITaikoInbox inbox = ITaikoInbox(resolve(LibStrings.B_TAIKO, false)); | ||
|
||
ForcedInclusion memory inclusion = ForcedInclusion({ | ||
blobHash: blobHash, | ||
feeInGwei: uint64(msg.value / 1 gwei), | ||
createdAtBatchId: inbox.getStats2().numBatches, | ||
blobByteOffset: blobByteOffset, | ||
blobByteSize: blobByteSize | ||
}); | ||
|
||
queue[tail++] = inclusion; | ||
|
||
emit ForcedInclusionStored(inclusion); | ||
} | ||
|
||
function consumeOldestForcedInclusion(address _feeRecipient) | ||
external | ||
nonReentrant | ||
onlyFromNamed(LibStrings.B_TAIKO_WRAPPER) | ||
returns (ForcedInclusion memory inclusion_) | ||
{ | ||
// we only need to check the first one, since it will be the oldest. | ||
uint64 _head = head; | ||
ForcedInclusion storage inclusion = queue[_head]; | ||
require(inclusion.createdAtBatchId != 0, NoForcedInclusionFound()); | ||
|
||
ITaikoInbox inbox = ITaikoInbox(resolve(LibStrings.B_TAIKO, false)); | ||
|
||
inclusion_ = inclusion; | ||
delete queue[_head]; | ||
|
||
unchecked { | ||
lastProcessedAtBatchId = inbox.getStats2().numBatches; | ||
head = _head + 1; | ||
} | ||
|
||
emit ForcedInclusionConsumed(inclusion_); | ||
_feeRecipient.sendEtherAndVerify(inclusion_.feeInGwei * 1 gwei); | ||
} | ||
|
||
function getForcedInclusion(uint256 index) external view returns (ForcedInclusion memory) { | ||
return queue[index]; | ||
} | ||
|
||
function getOldestForcedInclusionDeadline() public view returns (uint256) { | ||
unchecked { | ||
ForcedInclusion storage inclusion = queue[head]; | ||
return inclusion.createdAtBatchId == 0 | ||
? type(uint64).max | ||
: uint256(lastProcessedAtBatchId).max(inclusion.createdAtBatchId) + inclusionDelay; | ||
} | ||
} | ||
|
||
function isOldestForcedInclusionDue() external view returns (bool) { | ||
ITaikoInbox inbox = ITaikoInbox(resolve(LibStrings.B_TAIKO, false)); | ||
return inbox.getStats2().numBatches >= getOldestForcedInclusionDeadline(); | ||
} | ||
|
||
// @dev Override this function for easier testing blobs | ||
function _blobHash(uint8 blobIndex) internal view virtual returns (bytes32) { | ||
return blobhash(blobIndex); | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
packages/protocol/contracts/layer1/forced-inclusion/IForcedInclusionStore.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.24; | ||
|
||
/// @title IForcedInclusionStore | ||
/// @custom:security-contact [email protected] | ||
interface IForcedInclusionStore { | ||
/// @dev Error thrown when a blob is not found. | ||
error BlobNotFound(); | ||
/// @dev Error thrown when the parameters are invalid. | ||
error InvalidParams(); | ||
/// @dev Error thrown when the fee is incorrect. | ||
error IncorrectFee(); | ||
|
||
error NoForcedInclusionFound(); | ||
|
||
/// @dev Event emitted when a forced inclusion is stored. | ||
event ForcedInclusionStored(ForcedInclusion forcedInclusion); | ||
/// @dev Event emitted when a forced inclusion is consumed. | ||
event ForcedInclusionConsumed(ForcedInclusion forcedInclusion); | ||
|
||
struct ForcedInclusion { | ||
bytes32 blobHash; | ||
uint64 feeInGwei; | ||
uint64 createdAtBatchId; | ||
uint32 blobByteOffset; | ||
uint32 blobByteSize; | ||
} | ||
|
||
/// @dev Retrieve a forced inclusion request by its index. | ||
/// @param index The index of the forced inclusion request in the queue. | ||
/// @return The forced inclusion request at the specified index. | ||
function getForcedInclusion(uint256 index) external view returns (ForcedInclusion memory); | ||
|
||
/// @dev Get the deadline for the oldest forced inclusion. | ||
/// @return The deadline for the oldest forced inclusion. | ||
function getOldestForcedInclusionDeadline() external view returns (uint256); | ||
|
||
/// @dev Check if the oldest forced inclusion is due. | ||
/// @return True if the oldest forced inclusion is due, false otherwise. | ||
function isOldestForcedInclusionDue() external view returns (bool); | ||
|
||
/// @dev Consume a forced inclusion request. | ||
/// The inclusion request must be marked as processed and the priority fee must be paid to the | ||
/// caller. | ||
/// @param _feeRecipient The address to receive the priority fee. | ||
/// @return inclusion_ The forced inclusion request. | ||
function consumeOldestForcedInclusion(address _feeRecipient) | ||
external | ||
returns (ForcedInclusion memory); | ||
|
||
/// @dev Store a forced inclusion request. | ||
/// The priority fee must be paid to the contract. | ||
/// @param blobIndex The index of the blob that contains the transaction data. | ||
/// @param blobByteOffset The byte offset in the blob | ||
/// @param blobByteSize The size of the blob in bytes | ||
function storeForcedInclusion( | ||
uint8 blobIndex, | ||
uint32 blobByteOffset, | ||
uint32 blobByteSize | ||
) | ||
external | ||
payable; | ||
} |
113 changes: 113 additions & 0 deletions
113
packages/protocol/contracts/layer1/forced-inclusion/TaikoWrapper.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.24; | ||
|
||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import "src/shared/common/EssentialContract.sol"; | ||
import "src/shared/based/ITaiko.sol"; | ||
import "src/shared/libs/LibMath.sol"; | ||
import "src/shared/libs/LibNetwork.sol"; | ||
import "src/shared/libs/LibStrings.sol"; | ||
import "src/shared/signal/ISignalService.sol"; | ||
import "src/layer1/verifiers/IVerifier.sol"; | ||
import "src/layer1/based/TaikoInbox.sol"; | ||
import "./ForcedInclusionStore.sol"; | ||
|
||
/// @title TaikoWrapper | ||
/// @dev This contract is part of a delayed inbox implementation to enforce the inclusion of | ||
/// transactions. | ||
/// The current design is a simplified and can be improved with the following ideas: | ||
/// 1. **Fee-Based Request Prioritization**: | ||
/// - Proposers can selectively fulfill pending requests based on transaction fees. | ||
/// - Requests not yet due can be processed earlier if fees are attractive, incentivizing timely | ||
/// execution. | ||
/// | ||
/// 2. **Rate Limit Control**: | ||
/// - A rate-limiting mechanism ensures a minimum interval of 12*N seconds between request | ||
/// fulfillments. | ||
/// - Prevents proposers from being overwhelmed during high request volume, ensuring system | ||
/// stability. | ||
/// | ||
/// 3. **Calldata and Blob Support**: | ||
/// - Supports both calldata and blobs in the transaction list. | ||
/// | ||
/// 4. **Gas-Efficient Request Storage**: | ||
/// - Avoids storing full request data in contract storage. | ||
/// - Saves only the request hash and its timestamp. | ||
/// - Leverages Ethereum events to store request details off-chain. | ||
/// - Proposers can reconstruct requests as needed, minimizing on-chain storage and gas | ||
/// consumption. | ||
/// | ||
/// @custom:security-contact [email protected] | ||
|
||
contract TaikoWrapper is EssentialContract { | ||
using LibMath for uint256; | ||
|
||
/// @dev Event emitted when a forced inclusion is processed. | ||
event ForcedInclusionProcessed(IForcedInclusionStore.ForcedInclusion); | ||
/// @dev Error thrown when the oldest forced inclusion is due. | ||
|
||
error OldestForcedInclusionDue(); | ||
|
||
uint16 public constant MAX_FORCED_TXS_PER_FORCED_INCLUSION = 512; | ||
|
||
uint256[50] private __gap; | ||
|
||
constructor(address _resolver) EssentialContract(_resolver) { } | ||
|
||
function init(address _owner) external initializer { | ||
__Essential_init(_owner); | ||
} | ||
|
||
/// @notice Proposes a batch of blocks with forced inclusion. | ||
/// @param _forcedInclusionParams An optional ABI-encoded BlockParams for the forced inclusion | ||
/// batch. | ||
/// @param _params ABI-encoded BlockParams. | ||
/// @param _txList The transaction list in calldata. If the txList is empty, blob will be used | ||
/// for data availability. | ||
/// @return info_ The info of the proposed batch. | ||
/// @return meta_ The metadata of the proposed batch. | ||
function proposeBatchWithForcedInclusion( | ||
bytes calldata _forcedInclusionParams, | ||
bytes calldata _params, | ||
bytes calldata _txList | ||
) | ||
external | ||
nonReentrant | ||
returns (ITaikoInbox.BatchInfo memory info_, ITaikoInbox.BatchMetadata memory meta_) | ||
{ | ||
ITaikoInbox inbox = ITaikoInbox(resolve(LibStrings.B_TAIKO, false)); | ||
|
||
IForcedInclusionStore store = | ||
IForcedInclusionStore(resolve(LibStrings.B_FORCED_INCLUSION_STORE, false)); | ||
|
||
if (_forcedInclusionParams.length == 0) { | ||
require(!store.isOldestForcedInclusionDue(), OldestForcedInclusionDue()); | ||
} else { | ||
IForcedInclusionStore.ForcedInclusion memory inclusion = | ||
store.consumeOldestForcedInclusion(msg.sender); | ||
|
||
ITaikoInbox.BatchParams memory params = | ||
abi.decode(_forcedInclusionParams, (ITaikoInbox.BatchParams)); | ||
|
||
// Overwrite the batch params to have only 1 block and up to | ||
// MAX_FORCED_TXS_PER_FORCED_INCLUSION transactions | ||
if (params.blocks.length == 0) { | ||
params.blocks = new ITaikoInbox.BlockParams[](1); | ||
} | ||
|
||
if (params.blocks[0].numTransactions < MAX_FORCED_TXS_PER_FORCED_INCLUSION) { | ||
params.blocks[0].numTransactions = MAX_FORCED_TXS_PER_FORCED_INCLUSION; | ||
} | ||
|
||
params.blobParams.blobHashes = new bytes32[](1); | ||
params.blobParams.blobHashes[0] = inclusion.blobHash; | ||
params.blobParams.byteOffset = inclusion.blobByteOffset; | ||
params.blobParams.byteSize = inclusion.blobByteSize; | ||
|
||
inbox.proposeBatch(abi.encode(params), ""); | ||
emit ForcedInclusionProcessed(inclusion); | ||
} | ||
|
||
(info_, meta_) = inbox.proposeBatch(_params, _txList); | ||
} | ||
} |
60 changes: 60 additions & 0 deletions
60
packages/protocol/contracts/layer1/fork-router/ForkRouter.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.24; | ||
|
||
import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; | ||
import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; | ||
|
||
/// @title ForkRouter | ||
/// @custom:security-contact [email protected] | ||
/// @notice This contract routes calls to the current fork. | ||
/// | ||
/// +--> newFork | ||
/// PROXY -> FORK_ROUTER--| | ||
/// +--> oldFork | ||
contract ForkRouter is UUPSUpgradeable, Ownable2StepUpgradeable { | ||
address public immutable oldFork; | ||
address public immutable newFork; | ||
|
||
error InvalidParams(); | ||
error ZeroForkAddress(); | ||
|
||
constructor(address _oldFork, address _newFork) { | ||
require(_newFork != address(0) && _newFork != _oldFork, InvalidParams()); | ||
|
||
oldFork = _oldFork; | ||
newFork = _newFork; | ||
|
||
_disableInitializers(); | ||
} | ||
|
||
fallback() external payable virtual { | ||
_fallback(); | ||
} | ||
|
||
receive() external payable virtual { | ||
_fallback(); | ||
} | ||
|
||
/// @notice Returns true if a function should be routed to the old fork | ||
/// @dev This function should be overridden by the implementation contract | ||
function shouldRouteToOldFork(bytes4) public pure virtual returns (bool) { | ||
return false; | ||
} | ||
|
||
function _fallback() internal virtual { | ||
address fork = shouldRouteToOldFork(msg.sig) ? oldFork : newFork; | ||
require(fork != address(0), ZeroForkAddress()); | ||
|
||
assembly { | ||
calldatacopy(0, 0, calldatasize()) | ||
let result := delegatecall(gas(), fork, 0, calldatasize(), 0, 0) | ||
returndatacopy(0, 0, returndatasize()) | ||
|
||
switch result | ||
case 0 { revert(0, returndatasize()) } | ||
default { return(0, returndatasize()) } | ||
} | ||
} | ||
|
||
function _authorizeUpgrade(address) internal virtual override onlyOwner { } | ||
} |
Oops, something went wrong.