Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
D
deploy
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
deploy
Commits
6b9e6161
Commit
6b9e6161
authored
Apr 17, 2025
by
vicotor
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
update contract for withdrawal
parent
742f056f
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
311 additions
and
149 deletions
+311
-149
Deploy.s.sol
layer2/contracts-bedrock/scripts/deploy/Deploy.s.sol
+36
-36
OptimismPortal.sol
layer2/contracts-bedrock/src/L1/OptimismPortal.sol
+253
-113
Hashing.sol
layer2/contracts-bedrock/src/libraries/Hashing.sol
+7
-0
Types.sol
layer2/contracts-bedrock/src/libraries/Types.sol
+15
-0
No files found.
layer2/contracts-bedrock/scripts/deploy/Deploy.s.sol
View file @
6b9e6161
...
@@ -1424,46 +1424,46 @@ contract Deploy is Deployer {
...
@@ -1424,46 +1424,46 @@ contract Deploy is Deployer {
/// @notice Sets the implementation for the `CANNON` game type in the `DisputeGameFactory`
/// @notice Sets the implementation for the `CANNON` game type in the `DisputeGameFactory`
function setCannonFaultGameImplementation(bool _allowUpgrade) public broadcast {
function setCannonFaultGameImplementation(bool _allowUpgrade) public broadcast {
//
console.log("Setting Cannon FaultDisputeGame implementation");
//
console.log("Setting Cannon FaultDisputeGame implementation");
//
DisputeGameFactory factory = DisputeGameFactory(mustGetAddress("DisputeGameFactoryProxy"));
//
DisputeGameFactory factory = DisputeGameFactory(mustGetAddress("DisputeGameFactoryProxy"));
//
DelayedWETH weth = DelayedWETH(mustGetAddress("DelayedWETHProxy"));
//
DelayedWETH weth = DelayedWETH(mustGetAddress("DelayedWETHProxy"));
//
//
// Set the Cannon FaultDisputeGame implementation in the factory.
//
// Set the Cannon FaultDisputeGame implementation in the factory.
//
_setFaultGameImplementation({
//
_setFaultGameImplementation({
//
_factory: factory,
//
_factory: factory,
//
_allowUpgrade: _allowUpgrade,
//
_allowUpgrade: _allowUpgrade,
//
_params: FaultDisputeGameParams({
//
_params: FaultDisputeGameParams({
//
anchorStateRegistry: AnchorStateRegistry(mustGetAddress("AnchorStateRegistryProxy")),
//
anchorStateRegistry: AnchorStateRegistry(mustGetAddress("AnchorStateRegistryProxy")),
//
weth: weth,
//
weth: weth,
//
gameType: GameTypes.CANNON,
//
gameType: GameTypes.CANNON,
//
absolutePrestate: loadMipsAbsolutePrestate(),
//
absolutePrestate: loadMipsAbsolutePrestate(),
//
faultVm: IBigStepper(mustGetAddress("Mips")),
//
faultVm: IBigStepper(mustGetAddress("Mips")),
//
maxGameDepth: cfg.faultGameMaxDepth(),
//
maxGameDepth: cfg.faultGameMaxDepth(),
//
maxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration()))
//
maxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration()))
//
})
//
})
//
});
//
});
}
}
/// @notice Sets the implementation for the `PERMISSIONED_CANNON` game type in the `DisputeGameFactory`
/// @notice Sets the implementation for the `PERMISSIONED_CANNON` game type in the `DisputeGameFactory`
function setPermissionedCannonFaultGameImplementation(bool _allowUpgrade) public broadcast {
function setPermissionedCannonFaultGameImplementation(bool _allowUpgrade) public broadcast {
//
console.log("Setting Cannon PermissionedDisputeGame implementation");
//
console.log("Setting Cannon PermissionedDisputeGame implementation");
//
DisputeGameFactory factory = DisputeGameFactory(mustGetAddress("DisputeGameFactoryProxy"));
//
DisputeGameFactory factory = DisputeGameFactory(mustGetAddress("DisputeGameFactoryProxy"));
//
DelayedWETH weth = DelayedWETH(mustGetAddress("PermissionedDelayedWETHProxy"));
//
DelayedWETH weth = DelayedWETH(mustGetAddress("PermissionedDelayedWETHProxy"));
//
//
// Set the Cannon FaultDisputeGame implementation in the factory.
//
// Set the Cannon FaultDisputeGame implementation in the factory.
//
_setFaultGameImplementation({
//
_setFaultGameImplementation({
//
_factory: factory,
//
_factory: factory,
//
_allowUpgrade: _allowUpgrade,
//
_allowUpgrade: _allowUpgrade,
//
_params: FaultDisputeGameParams({
//
_params: FaultDisputeGameParams({
//
anchorStateRegistry: AnchorStateRegistry(mustGetAddress("AnchorStateRegistryProxy")),
//
anchorStateRegistry: AnchorStateRegistry(mustGetAddress("AnchorStateRegistryProxy")),
//
weth: weth,
//
weth: weth,
//
gameType: GameTypes.PERMISSIONED_CANNON,
//
gameType: GameTypes.PERMISSIONED_CANNON,
//
absolutePrestate: loadMipsAbsolutePrestate(),
//
absolutePrestate: loadMipsAbsolutePrestate(),
//
faultVm: IBigStepper(mustGetAddress("Mips")),
//
faultVm: IBigStepper(mustGetAddress("Mips")),
//
maxGameDepth: cfg.faultGameMaxDepth(),
//
maxGameDepth: cfg.faultGameMaxDepth(),
//
maxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration()))
//
maxClockDuration: Duration.wrap(uint64(cfg.faultGameMaxClockDuration()))
//
})
//
})
//
});
//
});
}
}
/// @notice Sets the implementation for the `ALPHABET` game type in the `DisputeGameFactory`
/// @notice Sets the implementation for the `ALPHABET` game type in the `DisputeGameFactory`
...
...
layer2/contracts-bedrock/src/L1/OptimismPortal.sol
View file @
6b9e6161
...
@@ -240,45 +240,227 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
...
@@ -240,45 +240,227 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
external
external
whenNotPaused
whenNotPaused
{
{
// Prevent users from creating a deposit transaction where this address is the message
revert("function has been disabled");
// sender on L2. Because this is checked here, we do not need to check again in
// // Prevent users from creating a deposit transaction where this address is the message
// `finalizeWithdrawalTransaction`.
// // sender on L2. Because this is checked here, we do not need to check again in
if (_tx.target == address(this)) revert BadTarget();
// // `finalizeWithdrawalTransaction`.
// if (_tx.target == address(this)) revert BadTarget();
//
// // Get the output root and load onto the stack to prevent multiple mloads. This will
// // revert if there is no output root for the given block number.
// bytes32 outputRoot = l2Oracle.getL2Output(_l2OutputIndex).outputRoot;
//
// // Verify that the output root can be generated with the elements in the proof.
// require(
// outputRoot == Hashing.hashOutputRootProof(_outputRootProof), "OptimismPortal: invalid output root proof"
// );
//
// // Load the ProvenWithdrawal into memory, using the withdrawal hash as a unique identifier.
// bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);
// ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash];
//
// // We generally want to prevent users from proving the same withdrawal multiple times
// // because each successive proof will update the timestamp. A malicious user can take
// // advantage of this to prevent other users from finalizing their withdrawal. However,
// // since withdrawals are proven before an output root is finalized, we need to allow users
// // to re-prove their withdrawal only in the case that the output root for their specified
// // output index has been updated.
// require(
// provenWithdrawal.timestamp == 0
// || l2Oracle.getL2Output(provenWithdrawal.l2OutputIndex).outputRoot != provenWithdrawal.outputRoot,
// "OptimismPortal: withdrawal hash has already been proven"
// );
//
// // Compute the storage slot of the withdrawal hash in the L2ToL1MessagePasser contract.
// // Refer to the Solidity documentation for more information on how storage layouts are
// // computed for mappings.
// bytes32 storageKey = keccak256(
// abi.encode(
// withdrawalHash,
// uint256(0) // The withdrawals mapping is at the first slot in the layout.
// )
// );
//
// // Verify that the hash of this withdrawal was stored in the L2toL1MessagePasser contract
// // on L2. If this is true, under the assumption that the SecureMerkleTrie does not have
// // bugs, then we know that this withdrawal was actually triggered on L2 and can therefore
// // be relayed on L1.
// require(
// SecureMerkleTrie.verifyInclusionProof({
// _key: abi.encode(storageKey),
// _value: hex"01",
// _proof: _withdrawalProof,
// _root: _outputRootProof.messagePasserStorageRoot
// }),
// "OptimismPortal: invalid withdrawal inclusion proof"
// );
//
// // Designate the withdrawalHash as proven by storing the `outputRoot`, `timestamp`, and
// // `l2BlockNumber` in the `provenWithdrawals` mapping. A `withdrawalHash` can only be
// // proven once unless it is submitted again with a different outputRoot.
// provenWithdrawals[withdrawalHash] = ProvenWithdrawal({
// outputRoot: outputRoot,
// timestamp: uint128(block.timestamp),
// l2OutputIndex: uint128(_l2OutputIndex)
// });
//
// // Emit a `WithdrawalProven` event.
// emit WithdrawalProven(withdrawalHash, _tx.sender, _tx.target);
}
// Get the output root and load onto the stack to prevent multiple mloads. This will
/// @notice Finalizes a withdrawal transaction.
// revert if there is no output root for the given block number.
/// @param _tx Withdrawal transaction to finalize.
bytes32 outputRoot = l2Oracle.getL2Output(_l2OutputIndex).outputRoot;
function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external whenNotPaused {
revert("function has been disabled");
// // Make sure that the l2Sender has not yet been set. The l2Sender is set to a value other
// // than the default value when a withdrawal transaction is being finalized. This check is
// // a defacto reentrancy guard.
// if (l2Sender != Constants.DEFAULT_L2_SENDER) revert NonReentrant();
//
// // Grab the proven withdrawal from the `provenWithdrawals` map.
// bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);
// ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash];
//
// // A withdrawal can only be finalized if it has been proven. We know that a withdrawal has
// // been proven at least once when its timestamp is non-zero. Unproven withdrawals will have
// // a timestamp of zero.
// require(provenWithdrawal.timestamp != 0, "OptimismPortal: withdrawal has not been proven yet");
//
// // As a sanity check, we make sure that the proven withdrawal's timestamp is greater than
// // starting timestamp inside the L2OutputOracle. Not strictly necessary but extra layer of
// // safety against weird bugs in the proving step.
// require(
// provenWithdrawal.timestamp >= l2Oracle.startingTimestamp(),
// "OptimismPortal: withdrawal timestamp less than L2 Oracle starting timestamp"
// );
//
// // A proven withdrawal must wait at least the finalization period before it can be
// // finalized. This waiting period can elapse in parallel with the waiting period for the
// // output the withdrawal was proven against. In effect, this means that the minimum
// // withdrawal time is proposal submission time + finalization period.
// require(
// _isFinalizationPeriodElapsed(provenWithdrawal.timestamp),
// "OptimismPortal: proven withdrawal finalization period has not elapsed"
// );
//
// // Grab the OutputProposal from the L2OutputOracle, will revert if the output that
// // corresponds to the given index has not been proposed yet.
// Types.OutputProposal memory proposal = l2Oracle.getL2Output(provenWithdrawal.l2OutputIndex);
//
// // Check that the output root that was used to prove the withdrawal is the same as the
// // current output root for the given output index. An output root may change if it is
// // deleted by the challenger address and then re-proposed.
// require(
// proposal.outputRoot == provenWithdrawal.outputRoot,
// "OptimismPortal: output root proven is not the same as current output root"
// );
//
// // Check that the output proposal has also been finalized.
// require(
// _isFinalizationPeriodElapsed(proposal.timestamp),
// "OptimismPortal: output proposal finalization period has not elapsed"
// );
//
// // Check that this withdrawal has not already been finalized, this is replay protection.
// require(finalizedWithdrawals[withdrawalHash] == false, "OptimismPortal: withdrawal has already been finalized");
//
// // Mark the withdrawal as finalized so it can't be replayed.
// finalizedWithdrawals[withdrawalHash] = true;
//
// // Set the l2Sender so contracts know who triggered this withdrawal on L2.
// // This acts as a reentrancy guard.
// l2Sender = _tx.sender;
//
// bool success;
// (address token,) = gasPayingToken();
// if (token == Constants.ETHER) {
// // Trigger the call to the target contract. We use a custom low level method
// // SafeCall.callWithMinGas to ensure two key properties
// // 1. Target contracts cannot force this call to run out of gas by returning a very large
// // amount of data (and this is OK because we don't care about the returndata here).
// // 2. The amount of gas provided to the execution context of the target is at least the
// // gas limit specified by the user. If there is not enough gas in the current context
// // to accomplish this, `callWithMinGas` will revert.
// success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, _tx.value, _tx.data);
// } else {
// // Cannot call the token contract directly from the portal. This would allow an attacker
// // to call approve from a withdrawal and drain the balance of the portal.
// if (_tx.target == token) revert BadTarget();
//
// // Only transfer value when a non zero value is specified. This saves gas in the case of
// // using the standard bridge or arbitrary message passing.
// if (_tx.value != 0) {
// // Update the contracts internal accounting of the amount of native asset in L2.
// _balance -= _tx.value;
//
// // Read the balance of the target contract before the transfer so the consistency
// // of the transfer can be checked afterwards.
// uint256 startBalance = IERC20(token).balanceOf(address(this));
//
// // Transfer the ERC20 balance to the target, accounting for non standard ERC20
// // implementations that may not return a boolean. This reverts if the low level
// // call is not successful.
// IERC20(token).safeTransfer({ to: _tx.target, value: _tx.value });
//
// // The balance must be transferred exactly.
// if (IERC20(token).balanceOf(address(this)) != startBalance - _tx.value) {
// revert TransferFailed();
// }
// }
//
// // Make a call to the target contract only if there is calldata.
// if (_tx.data.length != 0) {
// success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, 0, _tx.data);
// } else {
// success = true;
// }
// }
//
// // Reset the l2Sender back to the default value.
// l2Sender = Constants.DEFAULT_L2_SENDER;
//
// // All withdrawals are immediately finalized. Replayability can
// // be achieved through contracts built on top of this contract
// emit WithdrawalFinalized(withdrawalHash, success);
//
// // Reverting here is useful for determining the exact gas cost to successfully execute the
// // sub call to the target contract if the minimum gas limit specified by the user would not
// // be sufficient to execute the sub call.
// if (success == false && tx.origin == Constants.ESTIMATION_ADDRESS) {
// revert GasEstimation();
// }
}
event Println(uint256 line);
// Verify that the output root can be generated with the elements in the proof.
require(
outputRoot == Hashing.hashOutputRootProof(_outputRootProof), "OptimismPortal: invalid output root proof"
);
// Load the ProvenWithdrawal into memory, using the withdrawal hash as a unique identifier.
/// @notice withdrawal for a user.
bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);
/// @param _param Withdrawal param to execute.
/// @param _l2OutputIndex L2 output index to prove against.
/// @param _outputRootProof Inclusion proof of the withdrawal's storage root.
/// @param _withdrawalProof Inclusion proof of the withdrawal.
function exChainWithdrawal(
Types.ExChainWithdrawalParam memory _param,
uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof,
bytes[] calldata _withdrawalProof
)
external
whenNotPaused
{
emit Println(1);
if (_param.user == address(this)) revert BadTarget();
emit Println(2);
bytes32 outputRoot = l2Oracle.getL2Output(_l2OutputIndex).outputRoot;
emit Println(3);
bytes32 withdrawalHash = Hashing.hashExChainWithdrawal(_param);
emit Println(4);
ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash];
ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash];
// We generally want to prevent users from proving the same withdrawal multiple times
// because each successive proof will update the timestamp. A malicious user can take
// advantage of this to prevent other users from finalizing their withdrawal. However,
// since withdrawals are proven before an output root is finalized, we need to allow users
// to re-prove their withdrawal only in the case that the output root for their specified
// output index has been updated.
require(
require(
provenWithdrawal.timestamp == 0
provenWithdrawal.timestamp == 0
|| l2Oracle.getL2Output(provenWithdrawal.l2OutputIndex).outputRoot != provenWithdrawal.outputRoot,
|| l2Oracle.getL2Output(provenWithdrawal.l2OutputIndex).outputRoot != provenWithdrawal.outputRoot,
"OptimismPortal: withdrawal hash has already been proven"
"OptimismPortal: withdrawal hash has already been proven"
);
);
emit Println(5);
// Compute the storage slot of the withdrawal hash in the L2ToL1MessagePasser contract.
// Refer to the Solidity documentation for more information on how storage layouts are
// computed for mappings.
bytes32 storageKey = keccak256(
abi.encode(
withdrawalHash,
uint256(0) // The withdrawals mapping is at the first slot in the layout.
)
);
// Verify that the hash of this withdrawal was stored in the L2toL1MessagePasser contract
// Verify that the hash of this withdrawal was stored in the L2toL1MessagePasser contract
// on L2. If this is true, under the assumption that the SecureMerkleTrie does not have
// on L2. If this is true, under the assumption that the SecureMerkleTrie does not have
...
@@ -286,13 +468,14 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
...
@@ -286,13 +468,14 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
// be relayed on L1.
// be relayed on L1.
require(
require(
SecureMerkleTrie.verifyInclusionProof({
SecureMerkleTrie.verifyInclusionProof({
_key: abi.encode(
storageKey
),
_key: abi.encode(
withdrawalHash
),
_value: hex"01",
_value: hex"01",
_proof: _withdrawalProof,
_proof: _withdrawalProof,
_root: _outputRootProof.messagePasserStorageRoot
_root: _outputRootProof.messagePasserStorageRoot
}),
}),
"OptimismPortal: invalid withdrawal inclusion proof"
"OptimismPortal: invalid withdrawal inclusion proof"
);
);
emit Println(6);
// Designate the withdrawalHash as proven by storing the `outputRoot`, `timestamp`, and
// Designate the withdrawalHash as proven by storing the `outputRoot`, `timestamp`, and
// `l2BlockNumber` in the `provenWithdrawals` mapping. A `withdrawalHash` can only be
// `l2BlockNumber` in the `provenWithdrawals` mapping. A `withdrawalHash` can only be
...
@@ -302,94 +485,36 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
...
@@ -302,94 +485,36 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
timestamp: uint128(block.timestamp),
timestamp: uint128(block.timestamp),
l2OutputIndex: uint128(_l2OutputIndex)
l2OutputIndex: uint128(_l2OutputIndex)
});
});
emit Println(7);
// Emit a `WithdrawalProven` event.
emit WithdrawalProven(withdrawalHash, _tx.sender, _tx.target);
}
/// @notice Finalizes a withdrawal transaction.
/// @param _tx Withdrawal transaction to finalize.
function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external whenNotPaused {
// Make sure that the l2Sender has not yet been set. The l2Sender is set to a value other
// than the default value when a withdrawal transaction is being finalized. This check is
// a defacto reentrancy guard.
if (l2Sender != Constants.DEFAULT_L2_SENDER) revert NonReentrant();
// Grab the proven withdrawal from the `provenWithdrawals` map.
bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);
ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash];
// A withdrawal can only be finalized if it has been proven. We know that a withdrawal has
// been proven at least once when its timestamp is non-zero. Unproven withdrawals will have
// a timestamp of zero.
require(provenWithdrawal.timestamp != 0, "OptimismPortal: withdrawal has not been proven yet");
// As a sanity check, we make sure that the proven withdrawal's timestamp is greater than
// starting timestamp inside the L2OutputOracle. Not strictly necessary but extra layer of
// safety against weird bugs in the proving step.
require(
provenWithdrawal.timestamp >= l2Oracle.startingTimestamp(),
"OptimismPortal: withdrawal timestamp less than L2 Oracle starting timestamp"
);
// A proven withdrawal must wait at least the finalization period before it can be
// finalized. This waiting period can elapse in parallel with the waiting period for the
// output the withdrawal was proven against. In effect, this means that the minimum
// withdrawal time is proposal submission time + finalization period.
require(
_isFinalizationPeriodElapsed(provenWithdrawal.timestamp),
"OptimismPortal: proven withdrawal finalization period has not elapsed"
);
// Grab the OutputProposal from the L2OutputOracle, will revert if the output that
// corresponds to the given index has not been proposed yet.
Types.OutputProposal memory proposal = l2Oracle.getL2Output(provenWithdrawal.l2OutputIndex);
// Check that the output root that was used to prove the withdrawal is the same as the
// current output root for the given output index. An output root may change if it is
// deleted by the challenger address and then re-proposed.
require(
proposal.outputRoot == provenWithdrawal.outputRoot,
"OptimismPortal: output root proven is not the same as current output root"
);
// Check that the output proposal has also been finalized.
require(
_isFinalizationPeriodElapsed(proposal.timestamp),
"OptimismPortal: output proposal finalization period has not elapsed"
);
// Check that this withdrawal has not already been finalized, this is replay protection.
// Check that this withdrawal has not already been finalized, this is replay protection.
require(finalizedWithdrawals[withdrawalHash] == false, "OptimismPortal: withdrawal has already been finalized");
require(finalizedWithdrawals[withdrawalHash] == false, "OptimismPortal: withdrawal has already been finalized");
emit Println(8);
// Mark the withdrawal as finalized so it can't be replayed.
// Mark the withdrawal as finalized so it can't be replayed.
finalizedWithdrawals[withdrawalHash] = true;
finalizedWithdrawals[withdrawalHash] = true;
emit Println(9);
// Set the l2Sender so contracts know who triggered this withdrawal on L2.
// Set the l2Sender so contracts know who triggered this withdrawal on L2.
// This acts as a reentrancy guard.
// This acts as a reentrancy guard.
l2Sender =
_tx
.sender;
l2Sender =
msg
.sender;
bool success;
bool success;
emit Println(10);
(address token,) = gasPayingToken();
(address token,) = gasPayingToken();
if (token == Constants.ETHER) {
if (token == Constants.ETHER) {
// Trigger the call to the target contract. We use a custom low level method
emit Println(11);
// SafeCall.callWithMinGas to ensure two key properties
success = SafeCall.send(_param.user, _param.value);
// 1. Target contracts cannot force this call to run out of gas by returning a very large
// amount of data (and this is OK because we don't care about the returndata here).
// 2. The amount of gas provided to the execution context of the target is at least the
// gas limit specified by the user. If there is not enough gas in the current context
// to accomplish this, `callWithMinGas` will revert.
success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, _tx.value, _tx.data);
} else {
} else {
// Cannot call the token contract directly from the portal. This would allow an attacker
// Cannot call the token contract directly from the portal. This would allow an attacker
// to call approve from a withdrawal and drain the balance of the portal.
// to call approve from a withdrawal and drain the balance of the portal.
if (_tx.target == token) revert BadTarget();
emit Println(12);
if (_param.user == token) revert BadTarget();
// Only transfer value when a non zero value is specified. This saves gas in the case of
// Only transfer value when a non zero value is specified. This saves gas in the case of
// using the standard bridge or arbitrary message passing.
// using the standard bridge or arbitrary message passing.
if (_
tx
.value != 0) {
if (_
param
.value != 0) {
// Update the contracts internal accounting of the amount of native asset in L2.
// Update the contracts internal accounting of the amount of native asset in L2.
_balance -= _
tx
.value;
_balance -= _
param
.value;
// Read the balance of the target contract before the transfer so the consistency
// Read the balance of the target contract before the transfer so the consistency
// of the transfer can be checked afterwards.
// of the transfer can be checked afterwards.
...
@@ -398,20 +523,16 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
...
@@ -398,20 +523,16 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
// Transfer the ERC20 balance to the target, accounting for non standard ERC20
// Transfer the ERC20 balance to the target, accounting for non standard ERC20
// implementations that may not return a boolean. This reverts if the low level
// implementations that may not return a boolean. This reverts if the low level
// call is not successful.
// call is not successful.
IERC20(token).safeTransfer({ to: _
tx.target, value: _tx
.value });
IERC20(token).safeTransfer({ to: _
param.user, value: _param
.value });
// The balance must be transferred exactly.
// The balance must be transferred exactly.
if (IERC20(token).balanceOf(address(this)) != startBalance - _
tx
.value) {
if (IERC20(token).balanceOf(address(this)) != startBalance - _
param
.value) {
revert TransferFailed();
revert TransferFailed();
}
}
}
}
// Make a call to the target contract only if there is calldata.
// Make a call to the target contract only if there is calldata.
if (_tx.data.length != 0) {
success = true;
success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, 0, _tx.data);
} else {
success = true;
}
}
}
// Reset the l2Sender back to the default value.
// Reset the l2Sender back to the default value.
...
@@ -420,12 +541,31 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
...
@@ -420,12 +541,31 @@ contract OptimismPortal is Initializable, ResourceMetering, ISemver {
// All withdrawals are immediately finalized. Replayability can
// All withdrawals are immediately finalized. Replayability can
// be achieved through contracts built on top of this contract
// be achieved through contracts built on top of this contract
emit WithdrawalFinalized(withdrawalHash, success);
emit WithdrawalFinalized(withdrawalHash, success);
}
/// @notice withdrawal for multi users.
/// @param _params Withdrawal param to execute.
/// @param _l2OutputIndex L2 output index to prove against.
/// @param _outputRootProof Inclusion proof of the withdrawal's storage root.
function batchExChainWithdrawal(
Types.BatchExChainWithdrawalParam [] memory _params,
uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof
)
external
whenNotPaused
{
// loop _params to do exChainWithdrawal()
for (uint256 i = 0; i < _params.length; i++) {
Types.ExChainWithdrawalParam memory param;
param.coin = _params[i].coin;
param.txHash = _params[i].txHash;
param.user = _params[i].user;
param.value = _params[i].value;
bytes[] memory withdrawalProof = _params[i]._withdrawalProof;
// Reverting here is useful for determining the exact gas cost to successfully execute the
this.exChainWithdrawal(param, _l2OutputIndex, _outputRootProof, withdrawalProof);
// sub call to the target contract if the minimum gas limit specified by the user would not
// be sufficient to execute the sub call.
if (success == false && tx.origin == Constants.ESTIMATION_ADDRESS) {
revert GasEstimation();
}
}
}
}
...
...
layer2/contracts-bedrock/src/libraries/Hashing.sol
View file @
6b9e6161
...
@@ -107,6 +107,13 @@ library Hashing {
...
@@ -107,6 +107,13 @@ library Hashing {
return keccak256(abi.encode(_tx.nonce, _tx.sender, _tx.target, _tx.value, _tx.gasLimit, _tx.data));
return keccak256(abi.encode(_tx.nonce, _tx.sender, _tx.target, _tx.value, _tx.gasLimit, _tx.data));
}
}
/// @notice Derives the withdrawal hash according to the encoding in the L2.
/// @param _param Withdrawal param to hash.
/// @return Hashed withdrawal param.
function hashExChainWithdrawal(Types.ExChainWithdrawalParam memory _param) internal pure returns (bytes32) {
return keccak256(abi.encode(_param.user, _param.coin, _param.value, _param.txHash));
}
/// @notice Hashes the various elements of an output root proof into an output root hash which
/// @notice Hashes the various elements of an output root proof into an output root hash which
/// can be used to check if the proof is valid.
/// can be used to check if the proof is valid.
/// @param _outputRootProof Output root proof which should hash to an output root.
/// @param _outputRootProof Output root proof which should hash to an output root.
...
...
layer2/contracts-bedrock/src/libraries/Types.sol
View file @
6b9e6161
...
@@ -67,4 +67,19 @@ library Types {
...
@@ -67,4 +67,19 @@ library Types {
uint256 gasLimit;
uint256 gasLimit;
bytes data;
bytes data;
}
}
struct ExChainWithdrawalParam {
uint256 value;
address user;
bytes coin;
bytes32 txHash;
}
struct BatchExChainWithdrawalParam {
uint256 value;
address user;
bytes coin;
bytes32 txHash;
bytes[] _withdrawalProof;
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment