Skip to content
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

feat(ct): Proofs - Basic DelayedWETH Capability Expansion #12728

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
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
26 changes: 25 additions & 1 deletion packages/contracts-bedrock/src/dispute/DelayedWETH.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ contract DelayedWETH is OwnableUpgradeable, WETH98, ISemver {
/// @param wad The amount of WETH that was unwrapped.
event Unwrap(address indexed src, uint256 wad);

/// @notice Emitted when withdrawals and transfers are paused or unpaused.
/// @param paused True if paused, false otherwise.
event DelayedWethPausedSet(bool paused);

/// @notice Semantic version.
/// @custom:semver 1.2.0-beta.3
string public constant version = "1.2.0-beta.3";
Expand All @@ -44,6 +48,9 @@ contract DelayedWETH is OwnableUpgradeable, WETH98, ISemver {
/// @notice Address of the SuperchainConfig contract.
ISuperchainConfig public config;

/// @notice Flag that indicates whether withdrawals and transfers are paused.
bool public delayedWethPaused;

/// @param _delay The delay for withdrawals in seconds.
constructor(uint256 _delay) {
DELAY_SECONDS = _delay;
Expand Down Expand Up @@ -89,7 +96,8 @@ contract DelayedWETH is OwnableUpgradeable, WETH98, ISemver {
/// @param _guy Sub-account to withdraw from.
/// @param _wad The amount of WETH to withdraw.
function withdraw(address _guy, uint256 _wad) public {
require(!config.paused(), "DelayedWETH: contract is paused");
require(!config.paused(), "DelayedWETH: system is paused");
require(!delayedWethPaused, "DelayedWETH: withdrawals and transfers are paused");
WithdrawalRequest storage wd = withdrawals[msg.sender][_guy];
require(wd.amount >= _wad, "DelayedWETH: insufficient unlocked withdrawal");
require(wd.timestamp > 0, "DelayedWETH: withdrawal not unlocked");
Expand All @@ -98,6 +106,14 @@ contract DelayedWETH is OwnableUpgradeable, WETH98, ISemver {
super.withdraw(_wad);
}

/// @notice Allows the owner to pause or unpause withdrawals and transfers.
/// @param _paused True if withdrawals and transfers should be paused, false otherwise.
function setDelayedWethPaused(bool _paused) external {
require(msg.sender == config.guardian(), "DelayedWETH: not guardian");
delayedWethPaused = _paused;
emit DelayedWethPausedSet(_paused);
}

/// @notice Allows the owner to recover from error cases by pulling ETH out of the contract.
/// @param _wad The amount of WETH to recover.
function recover(uint256 _wad) external {
Expand All @@ -114,5 +130,13 @@ contract DelayedWETH is OwnableUpgradeable, WETH98, ISemver {
require(msg.sender == owner(), "DelayedWETH: not owner");
_allowance[_guy][msg.sender] = _wad;
emit Approval(_guy, msg.sender, _wad);
transferFrom(_guy, msg.sender, _wad);
}

/// @inheritdoc WETH98
/// @dev Modified to enforce the delayedWethPaused condition on transfers.
function transferFrom(address src, address dst, uint256 wad) public override returns (bool) {
require(!delayedWethPaused || msg.sender == owner(), "DelayedWETH: withdrawals and transfers are paused");
return super.transferFrom(src, dst, wad);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ interface IDelayedWETH {
receive() external payable;

function config() external view returns (ISuperchainConfig);
function delayedWethPaused() external view returns (bool);
function delay() external view returns (uint256);
function hold(address _guy, uint256 _wad) external;
function setDelayedWethPaused(bool _paused) external;
function initialize(address _owner, ISuperchainConfig _config) external;
function owner() external view returns (address);
function recover(uint256 _wad) external;
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts-bedrock/src/universal/WETH98.sol
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ contract WETH98 {
/// @param dst The address to transfer the WETH to.
/// @param wad The amount of WETH to transfer.
/// @return True if the transfer was successful.
function transferFrom(address src, address dst, uint256 wad) public returns (bool) {
function transferFrom(address src, address dst, uint256 wad) public virtual returns (bool) {
require(_balanceOf[src] >= wad);

uint256 senderAllowance = allowance(src, msg.sender);
Expand Down
88 changes: 77 additions & 11 deletions packages/contracts-bedrock/test/dispute/DelayedWETH.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ contract DelayedWETH_Init is CommonTest {
event Deposit(address indexed dst, uint256 wad);
event Withdrawal(address indexed src, uint256 wad);
event Unwrap(address indexed src, uint256 wad);
event DelayedWethPausedSet(bool paused);

address guardian;

function setUp() public virtual override {
super.setUp();

// Transfer ownership of delayed WETH to the test contract.
vm.prank(delayedWeth.owner());
delayedWeth.transferOwnership(address(this));
guardian = optimismPortal.guardian();
}
}

Expand Down Expand Up @@ -63,6 +67,26 @@ contract DelayedWETH_Unlock_Test is DelayedWETH_Init {
}
}

contract DelayedWETH_Transfer_Test is DelayedWETH_Init {
function testFuzz_transfer_succeeds(address _to, uint256 _amount) public {
vm.assume(_to != alice);
uint256 _depositAmount = 1 ether;
_amount = bound(_amount, 0, _depositAmount);
// Deposit some WETH.
vm.prank(alice);
delayedWeth.deposit{ value: _depositAmount }();
uint256 balance = address(alice).balance;

// transfer it.
vm.prank(alice);
delayedWeth.transfer(_to, _amount);
assertEq(delayedWeth.balanceOf(_to), _amount);
}

// TODO: transfer fails when pause is active
// TODO: same 2 tests for transferFrom
}

contract DelayedWETH_Withdraw_Test is DelayedWETH_Init {
/// @dev Tests that withdrawing while unlocked and delay has passed is successful.
function test_withdraw_whileUnlocked_succeeds() public {
Expand Down Expand Up @@ -142,8 +166,8 @@ contract DelayedWETH_Withdraw_Test is DelayedWETH_Init {
assertEq(address(alice).balance, balance);
}

/// @dev Tests that withdrawing while paused fails.
function test_withdraw_whenPaused_fails() public {
/// @dev Tests that withdrawing during system pause fails.
function test_withdraw_whenSystemPaused_fails() public {
// Deposit some WETH.
vm.prank(alice);
delayedWeth.deposit{ value: 1 ether }();
Expand All @@ -156,12 +180,33 @@ contract DelayedWETH_Withdraw_Test is DelayedWETH_Init {
vm.warp(block.timestamp + delayedWeth.delay() + 1);

// Pause the contract.
address guardian = optimismPortal.guardian();
vm.prank(guardian);
superchainConfig.pause("identifier");

// Withdraw fails.
vm.expectRevert("DelayedWETH: contract is paused");
vm.expectRevert("DelayedWETH: system is paused");
vm.prank(alice);
delayedWeth.withdraw(alice, 1 ether);
}

function test_RevertIf_Withdraw_When_WithdrawalsPaused() public {
// Deposit some WETH.
vm.prank(alice);
delayedWeth.deposit{ value: 1 ether }();

// Unlock the withdrawal.
vm.prank(alice);
delayedWeth.unlock(alice, 1 ether);

// Wait for the delay.
vm.warp(block.timestamp + delayedWeth.delay() + 1);

// Pause the contract.
vm.prank(guardian);
delayedWeth.setDelayedWethPaused(true);

// Withdraw fails.
vm.expectRevert("DelayedWETH: withdrawals and transfers are paused");
vm.prank(alice);
delayedWeth.withdraw(alice, 1 ether);
}
Expand Down Expand Up @@ -247,6 +292,28 @@ contract DelayedWETH_Recover_Test is DelayedWETH_Init {
}
}

contract DelayedWETH_SetDelayedWethPaused_Test is DelayedWETH_Init {
function testFuzz_setDelayedWethPaused_byGuardian_succeeds(bool _isPaused) public {
vm.prank(guardian);
delayedWeth.setDelayedWethPaused(_isPaused);
assertEq(_isPaused, delayedWeth.delayedWethPaused());
}

function testFuzz_setDelayedWethPaused_byGuardian_emitsEvent(bool _isPaused) public {
vm.expectEmit();
emit DelayedWethPausedSet(_isPaused);
vm.prank(guardian);
delayedWeth.setDelayedWethPaused(_isPaused);
}

function testFuzz_setDelayedWethPaused_byNonGuardian_fails(bool _isPaused, address _actor) public {
vm.assume(_actor != guardian);
vm.expectRevert("DelayedWETH: not guardian");
vm.prank(_actor);
delayedWeth.setDelayedWethPaused(_isPaused);
}
}

contract DelayedWETH_Hold_Test is DelayedWETH_Init {
/// @dev Tests that holding WETH succeeds.
function test_hold_succeeds() public {
Expand All @@ -257,17 +324,16 @@ contract DelayedWETH_Hold_Test is DelayedWETH_Init {
delayedWeth.deposit{ value: amount }();

// Hold some WETH.
vm.expectEmit(true, true, true, false);
vm.expectEmit();
emit Approval(alice, address(this), amount);
vm.expectEmit();
emit Transfer(alice, address(this), amount);
delayedWeth.hold(alice, amount);

// Verify the allowance.
assertEq(delayedWeth.allowance(alice, address(this)), amount);

// We can transfer.
delayedWeth.transferFrom(alice, address(this), amount);
// Verify there's no lingering allowance.
assertEq(delayedWeth.allowance(alice, address(this)), 0);

// Verify the transfer.
// Verify a successful transfer of amount.
assertEq(delayedWeth.balanceOf(address(this)), amount);
}

Expand Down