Commit 6b9e6161 authored by vicotor's avatar vicotor

update contract for withdrawal

parent 742f056f
...@@ -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`
......
...@@ -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();
} }
} }
......
...@@ -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.
......
...@@ -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;
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment