Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 53 additions & 64 deletions packages/contracts-bedrock/.gas-snapshot

Large diffs are not rendered by default.

110 changes: 90 additions & 20 deletions packages/contracts-bedrock/contracts/L1/OptimismPortal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ import { Semver } from "../universal/Semver.sol";
* Users are encouraged to use the L1CrossDomainMessenger for a higher-level interface.
*/
contract OptimismPortal is Initializable, ResourceMetering, Semver {
/**
* @notice Data stored for proven withdrawals.
*/
struct ProvenWithdrawal {
uint256 timestamp;
uint256 l2BlockNumber;
bytes32 outputRoot;
}

/**
* @notice Version of the deposit event.
*/
Expand Down Expand Up @@ -58,6 +67,11 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
*/
uint256 internal constant FINALIZE_GAS_BUFFER = 20_000;

/**
* @notice A list of withdrawal hashes which have been proven.
*/
mapping(bytes32 => ProvenWithdrawal) public provenWithdrawals;

/**
* @notice A list of withdrawal hashes which have been successfully finalized.
*/
Expand All @@ -79,6 +93,13 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
bytes opaqueData
);

/**
* @notice Emitted when a withdrawal transaction is proven.
*
* @param withdrawalHash Hash of the withdrawal transaction.
*/
event WithdrawalProven(bytes32 indexed withdrawalHash);

/**
* @notice Emitted when a withdrawal transaction is finalized.
*
Expand Down Expand Up @@ -110,44 +131,30 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
}

/**
* @notice Finalizes a withdrawal transaction.
* @notice Proves a withdrawal transaction.
*
* @param _tx Withdrawal transaction to finalize.
* @param _l2BlockNumber L2 block number of the outputRoot.
* @param _outputRootProof Inclusion proof of the L2ToL1MessagePasser contract's storage root.
* @param _withdrawalProof Inclusion proof of the withdrawal in L2ToL1MessagePasser contract.
*/
function finalizeWithdrawalTransaction(
function proveWithdrawalTransaction(
Types.WithdrawalTransaction memory _tx,
uint256 _l2BlockNumber,
Types.OutputRootProof calldata _outputRootProof,
bytes calldata _withdrawalProof
) external {
// Prevent nested withdrawals within withdrawals.
require(
l2Sender == DEFAULT_L2_SENDER,
"OptimismPortal: can only trigger one withdrawal per transaction"
);

// Prevent users from creating a deposit transaction where this address is the message
// sender on L2.
// In the context of the proxy delegate calling to this implementation,
// sender on L2. In the context of the proxy delegate calling to this implementation,
// address(this) will return the address of the proxy.
require(
_tx.target != address(this),
"OptimismPortal: you cannot send messages to the portal contract"
);

// Get the output root. This will fail if there is no
// output root for the given block number.
// Get the output root. This will fail if there is no output root for the given block.
Types.OutputProposal memory proposal = L2_ORACLE.getL2Output(_l2BlockNumber);

// Ensure that enough time has passed since the proposal was submitted before allowing a
// withdrawal. Under the assumption that the fault proof mechanism is operating correctly,
// we can infer that any withdrawal that has passed the finalization period must be valid
// and can therefore be operated on.
require(_isOutputFinalized(proposal), "OptimismPortal: proposal is not yet finalized");

// Verify that the output root can be generated with the elements in the proof.
require(
proposal.outputRoot == Hashing.hashOutputRootProof(_outputRootProof),
Expand All @@ -159,8 +166,8 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);

// Verify that the hash of this withdrawal was stored in the L2toL1MessagePasser contract on
// L2. If this is true, then we know that this withdrawal was actually triggered on L2
// and can therefore be relayed on L1.
// L2. If this is true, then we know that this withdrawal was actually triggered on L2 and
// can therefore be relayed on L1.
require(
_verifyWithdrawalInclusion(
withdrawalHash,
Expand All @@ -170,6 +177,69 @@ contract OptimismPortal is Initializable, ResourceMetering, Semver {
"OptimismPortal: invalid withdrawal inclusion proof"
);

// Mark the withdrawal as proven and mark the timestamp at which the proof happened along
// with the outputRoot that was used to prove the withdrawal. It's fine for the same
// withdrawal to be proven twice, since we prevent replay attacks using the
// finalizedWithdrawals mapping and not the provenWithdrawals mapping. In some cases we
// need to prove the same withdrawal multiple times, for example if the original output
// root that was proven against was replaced with a different output root.
provenWithdrawals[withdrawalHash] = ProvenWithdrawal({
timestamp: block.timestamp,
l2BlockNumber: _l2BlockNumber,
outputRoot: proposal.outputRoot
});

// Tell the world that the withdrawal was proven.
emit WithdrawalProven(withdrawalHash);
}

/**
* @notice Finalizes a withdrawal transaction.
*
* @param _tx Withdrawal transaction to finalize.
*/
function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external {
// Prevent nested withdrawals within withdrawals.
require(
l2Sender == DEFAULT_L2_SENDER,
"OptimismPortal: can only trigger one withdrawal per transaction"
);

// All withdrawals have a unique hash, we'll use this as the identifier for the withdrawal
// and to prevent replay attacks.
bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);

// Load up the withdrawal and check that it's been proven.
ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash];
require(
provenWithdrawal.timestamp != 0,
"OptimismPortal: withdrawal hash has not been proven"
);

// Check that the withdrawal has passed the finalization period.
require(
block.timestamp >= provenWithdrawal.timestamp + FINALIZATION_PERIOD_SECONDS,
"OptimismPortal: withdrawal finalization period has not elapsed"
);

// Check that the output proposal hasn't been updated.
Types.OutputProposal memory proposal = L2_ORACLE.getL2Output(
provenWithdrawal.l2BlockNumber
);
require(
proposal.outputRoot == provenWithdrawal.outputRoot,
"OptimismPortal: output root proven is not the same as current output root"
);

// Ensure that enough time has passed since the proposal was submitted before allowing a
// withdrawal. Under the assumption that the fault proof mechanism is operating correctly,
// we can infer that any withdrawal that has passed the finalization period must be valid
// and can therefore be operated on.
require(
_isOutputFinalized(proposal),
"OptimismPortal: proposal finalization period has not elapsed"
);

// Check that this withdrawal has not already been finalized, this is replay protection.
require(
finalizedWithdrawals[withdrawalHash] == false,
Expand Down
4 changes: 2 additions & 2 deletions packages/contracts-bedrock/contracts/test/CommonTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ contract Bridge_Initializer is Messenger_Initializer {
}

contract FFIInterface is Test {
function getFinalizeWithdrawalTransactionInputs(Types.WithdrawalTransaction memory _tx)
function getProveWithdrawalTransactionInputs(Types.WithdrawalTransaction memory _tx)
external
returns (
bytes32,
Expand All @@ -437,7 +437,7 @@ contract FFIInterface is Test {
string[] memory cmds = new string[](9);
cmds[0] = "node";
cmds[1] = "dist/scripts/differential-testing.js";
cmds[2] = "getFinalizeWithdrawalTransactionInputs";
cmds[2] = "getProveWithdrawalTransactionInputs";
cmds[3] = vm.toString(_tx.nonce);
cmds[4] = vm.toString(_tx.sender);
cmds[5] = vm.toString(_tx.target);
Expand Down
Loading