diff --git a/.gas-snapshot b/.gas-snapshot index a8d1f5d2b9..63e7868abf 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -898,25 +898,28 @@ LibZipTest:testFlzCompressDecompress() (gas: 1250953) LibZipTest:testFlzCompressDecompress(bytes) (runs: 256, μ: 759759, ~: 674769) LibZipTest:testFlzCompressDecompress2() (gas: 760133) LibZipTest:test__codesize() (gas: 20369) -LifebouyTest:testConstructorSendsETHToContractCaller() (gas: 336437) -LifebouyTest:testConstructorSendsETHToEOACaller() (gas: 246101) -LifebouyTest:testConstructorSendsETHToOrigin() (gas: 566713) +LifebouyTest:testConstructorSendsETHToContractCaller() (gas: 338859) +LifebouyTest:testConstructorSendsETHToEOACaller() (gas: 248501) +LifebouyTest:testConstructorSendsETHToOrigin() (gas: 571526) LifebouyTest:testLockRescue() (gas: 28588) -LifebouyTest:testLockRescueNotDeployer(address) (runs: 256, μ: 13866, ~: 13866) -LifebouyTest:testLockRescueOwned() (gas: 415870) -LifebouyTest:testRescueERC20(uint256) (runs: 256, μ: 53047, ~: 54998) +LifebouyTest:testLockRescueNotDeployer(address) (runs: 256, μ: 13888, ~: 13888) +LifebouyTest:testLockRescueOwned() (gas: 418305) +LifebouyTest:testRescueERC20(uint256) (runs: 256, μ: 53092, ~: 55043) LifebouyTest:testRescueERC20LockedRescue(uint256) (runs: 256, μ: 21137, ~: 21137) LifebouyTest:testRescueERC20NotAllowed(address,uint256) (runs: 256, μ: 16262, ~: 16262) -LifebouyTest:testRescueERC20Owned(uint256) (runs: 256, μ: 85121, ~: 86909) -LifebouyTest:testRescueERC721(uint256) (runs: 256, μ: 77442, ~: 77442) +LifebouyTest:testRescueERC20Owned(uint256) (runs: 256, μ: 85143, ~: 86931) +LifebouyTest:testRescueERC20TransferFails(uint256) (runs: 256, μ: 24591, ~: 24591) +LifebouyTest:testRescueERC721(uint256) (runs: 256, μ: 77454, ~: 77454) LifebouyTest:testRescueERC721LockedRescue(uint256) (runs: 256, μ: 20877, ~: 20877) LifebouyTest:testRescueERC721NotAllowed(address,uint256) (runs: 256, μ: 70268, ~: 70268) -LifebouyTest:testRescueERC721Owned(uint256) (runs: 256, μ: 139658, ~: 139658) -LifebouyTest:testRescueETH(uint256) (runs: 256, μ: 23627, ~: 24203) +LifebouyTest:testRescueERC721Owned(uint256) (runs: 256, μ: 139637, ~: 139637) +LifebouyTest:testRescueERC721TransferFails() (gas: 21563) +LifebouyTest:testRescueETH(uint256) (runs: 256, μ: 23601, ~: 24203) LifebouyTest:testRescueETHLockedRescue(uint256) (runs: 256, μ: 18919, ~: 18919) LifebouyTest:testRescueETHNotAllowed(address,uint256) (runs: 256, μ: 13992, ~: 13992) -LifebouyTest:testRescueETHOwned(uint256) (runs: 256, μ: 65725, ~: 66327) -LifebouyTest:test__codesize() (gas: 32607) +LifebouyTest:testRescueETHOwned(uint256) (runs: 256, μ: 65703, ~: 66305) +LifebouyTest:testRescueETHTransferFails() (gas: 309360) +LifebouyTest:test__codesize() (gas: 33976) MerkleProofLibTest:testEmptyCalldataHelpers() (gas: 985) MerkleProofLibTest:testVerifyMultiProof(bool,bool,bool,bool,bytes32) (runs: 256, μ: 773091, ~: 622833) MerkleProofLibTest:testVerifyMultiProofForHeightOneTree(bool,bool,bool,bool,bool,bool[]) (runs: 256, μ: 37908, ~: 38470) diff --git a/src/auth/Lifebouy.sol b/src/auth/Lifebouy.sol index ea84999256..c5e93be681 100644 --- a/src/auth/Lifebouy.sol +++ b/src/auth/Lifebouy.sol @@ -6,6 +6,8 @@ abstract contract Lifebouy { error ETHTransferFailed(); /// @dev The ERC20 `transfer` has failed. error ERC20TransferFailed(); + /// @dev The ERC721 `transfer` has failed. + error ERC721TransferFailed(); /// @dev `rescueLocked` was set to true. error LockedRescue(); /// @dev Not owner or deployer. @@ -123,7 +125,11 @@ abstract contract Lifebouy { // `safeTransferFrom(address from, address to, uint256 tokenId)`. mstore(0x00, 0x42842e0e000000000000000000000000) // Perform the safe transfer from, reverting upon failure. - pop(call(gas(), erc721, 0x00, 0x10, 0x64, codesize(), 0x00)) + + if iszero(call(gas(), erc721, 0x00, 0x10, 0x64, codesize(), 0x00)) { + mstore(0x00, 0xdff14c1e) // `ERC721TransferFailed()`. + revert(0x1c, 0x04) + } } } diff --git a/test/Lifebouy.t.sol b/test/Lifebouy.t.sol index 806b953cff..78852b8681 100644 --- a/test/Lifebouy.t.sol +++ b/test/Lifebouy.t.sol @@ -210,6 +210,15 @@ contract LifebouyTest is SoladyTest { assertEq(balanceBefore + secondAmount, owner.balance); } + function testRescueETHTransferFails() public { + MockLifebouyDeployerNoFallback mock = new MockLifebouyDeployerNoFallback(); + + vm.prank(deployer); + vm.expectRevert(0xb12d13eb); + + mockLifebouy.rescueETH(address(mock), address(mockLifebouy).balance); + } + function testRescueETHNotAllowed(address caller, uint256 amount) public { vm.assume(caller != deployer); @@ -277,6 +286,14 @@ contract LifebouyTest is SoladyTest { assertEq(balanceBefore + secondAmount, mockERC20.balanceOf(owner)); } + function testRescueERC20TransferFails(uint256 amount) public { + vm.assume(amount > mockERC20.balanceOf(address(mockLifebouy))); + vm.prank(deployer); + vm.expectRevert(0xf27f64e4); + + mockLifebouy.rescueERC20(erc20, deployer, amount); + } + function testRescueERC20NotAllowed(address caller, uint256 amount) public { vm.assume(caller != deployer); @@ -334,6 +351,13 @@ contract LifebouyTest is SoladyTest { assertEq(owner, mockERC721.ownerOf(id + 1)); } + function testRescueERC721TransferFails() public { + vm.prank(deployer); + vm.expectRevert(0xdff14c1e); + + mockLifebouy.rescueERC721(erc721, deployer, 999); + } + function testRescueERC721NotAllowed(address caller, uint256 id) public { vm.assume(caller != deployer && id < 99999);