Skip to content

Commit

Permalink
Squash
Browse files Browse the repository at this point in the history
  • Loading branch information
Vectorized committed Dec 10, 2024
1 parent 513f581 commit 67c6de5
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 64 deletions.
25 changes: 15 additions & 10 deletions src/utils/Lifebuoy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ pragma solidity ^0.8.4;
/// script misfire / misconfiguration.
/// - Careless dev forgets to add a withdraw function to a NFT sale contract.
///
/// Note: if you are deploying via a untrusted `tx.origin`,
/// you MUST override `_lifebuoyDefaultDeployer` to return a trusted address.
///
/// For best safety:
/// - For non-escrow contracts, inherit Lifebuoy as much as possible,
/// and leave it unlocked.
Expand All @@ -21,7 +24,6 @@ pragma solidity ^0.8.4;
///
/// All rescue and rescue authorization functions require either:
/// - Caller is the deployer
/// AND caller is an EOA
/// AND the contract is not a proxy
/// AND `rescueLocked() & _LIFEBUOY_DEPLOYER_ACCESS_LOCK == 0`.
/// - Caller is `owner()`
Expand Down Expand Up @@ -102,22 +104,26 @@ contract Lifebuoy {

constructor() payable {
bytes32 hash;
uint256 deployer = uint160(_lifebuoyUseTxOriginAsDeployer() ? tx.origin : msg.sender);
uint256 deployer = uint160(_lifebuoyDefaultDeployer());
/// @solidity memory-safe-assembly
assembly {
// I know about EIP7645, and I will stop it if it gets traction.
// Worse case, I will add an `ecrecover` method. But not today.
if iszero(deployer) { deployer := origin() }
mstore(0x00, address())
mstore(0x20, deployer)
hash := keccak256(0x00, 0x40)
}
_lifebuoyDeployerHash = hash;
}

/// @dev Override to return true if the contract is to be deployed via a factory
/// in a transaction initiated by a trusted EOA that is NEVER shared.
/// If your contract is deployed by someone else's EOA (e.g. sponsored transactions),
/// this function MUST return false, as per default.
function _lifebuoyUseTxOriginAsDeployer() internal view virtual returns (bool) {
return false;
/// @dev Override to return a non-zero address if you want to set it as the deployer.
/// Otherwise, the deployer will be set to `tx.origin`.
///
/// Note: If you are deploying via a untrusted `tx.origin` (e.g. ERC4337 bundler)
/// you MUST override this function to return a trusted address.
function _lifebuoyDefaultDeployer() internal view virtual returns (address) {
return address(0);
}

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
Expand Down Expand Up @@ -292,12 +298,11 @@ contract Lifebuoy {
// If the `modeLock` flag is true, set all bits in `locks` to true.
locks := or(sub(0, iszero(iszero(and(modeLock, locks)))), locks)
// Caller is the deployer
// AND caller is an EOA
// AND the contract is not a proxy
// AND `locks & _LIFEBUOY_DEPLOYER_ACCESS_LOCK` is false.
mstore(0x20, caller())
mstore(and(locks, _LIFEBUOY_DEPLOYER_ACCESS_LOCK), address())
if iszero(or(extcodesize(caller()), xor(keccak256(0x00, 0x40), h))) { break }
if eq(keccak256(0x00, 0x40), h) { break }
// If the caller is `owner()`
// AND `locks & _LIFEBUOY_OWNER_ACCESS_LOCK` is false.
mstore(0x08, 0x8da5cb5b0a0362e0) // `owner()` and `RescueUnauthorizedOrLocked()`.
Expand Down
62 changes: 8 additions & 54 deletions test/Lifebuoy.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ contract LifebuoyTest is SoladyTest {
if (_randomChance(32)) t.owner = t.deployer;
if (_randomChance(2)) vm.etch(t.deployer, " ");

vm.prank(t.deployer);
vm.prank(t.deployer, t.deployer);
t.lifebuoyOwned = new MockLifebuoyOwned(t.owner);
vm.deal(address(t.lifebuoyOwned), 1 ether);
}
Expand All @@ -128,11 +128,11 @@ contract LifebuoyTest is SoladyTest {
function _testTempsForRescuePermissions() internal returns (_TestTemps memory t) {
t = _testTempsBase();

vm.prank(t.deployer);
vm.prank(t.deployer, t.deployer);
t.lifebuoy = new MockLifebuoy();
vm.deal(address(t.lifebuoy), 1 ether);

vm.prank(t.deployer);
vm.prank(t.deployer, t.deployer);
t.lifebuoyOwnedClone = MockLifebuoyOwned(LibClone.clone(address(t.lifebuoyOwned)));
t.lifebuoyOwnedClone.initializeOwner(t.owner);
vm.deal(address(t.lifebuoyOwnedClone), 1 ether);
Expand All @@ -144,71 +144,25 @@ contract LifebuoyTest is SoladyTest {
t.lifebuoy.rescueETH(t.recipient, 1);

vm.prank(t.deployer);
if (t.deployer.code.length == 0) {
t.lifebuoy.rescueETH(t.recipient, 1);
} else {
vm.expectRevert(Lifebuoy.RescueUnauthorizedOrLocked.selector);
t.lifebuoy.rescueETH(t.recipient, 1);
}
t.lifebuoy.rescueETH(t.recipient, 1);

vm.prank(t.owner);
if (t.deployer != t.owner || t.deployer.code.length != 0) {
if (t.deployer != t.owner) {
vm.expectRevert(Lifebuoy.RescueUnauthorizedOrLocked.selector);
}
t.lifebuoy.rescueETH(t.recipient, 1);

if (_randomChance(2) && t.deployer.code.length == 0) {
vm.startPrank(t.deployer);
if (_randomChance(2)) {
t.lifebuoy.rescueETH(t.recipient, 1);
}
t.lifebuoy.lockRescue(_LIFEBUOY_DEPLOYER_ACCESS_LOCK);
vm.expectRevert(Lifebuoy.RescueUnauthorizedOrLocked.selector);
t.lifebuoy.rescueETH(t.recipient, 1);
vm.stopPrank();
}
}

function testLifebuoyOwnedRescuePermissions(bytes32) public {
_TestTemps memory t = _testTempsForRescuePermissions();
vm.expectRevert(Lifebuoy.RescueUnauthorizedOrLocked.selector);
t.lifebuoy.rescueETH(t.recipient, 1);

vm.prank(t.deployer);
if (t.deployer != t.owner && t.deployer.code.length != 0) {
vm.expectRevert(Lifebuoy.RescueUnauthorizedOrLocked.selector);
}
t.lifebuoyOwned.rescueETH(t.recipient, 1);

vm.prank(t.owner);
t.lifebuoyOwned.rescueETH(t.recipient, 1);

if (_randomChance(2) && t.deployer.code.length == 0) {
if (_randomChance(2)) {
vm.prank(t.deployer);
t.lifebuoyOwned.rescueETH(t.recipient, 1);
}
if (_randomChance(2)) {
vm.prank(t.owner);
t.lifebuoyOwned.rescueETH(t.recipient, 1);
}

if (_randomChance(2)) {
vm.prank(t.deployer);
t.lifebuoyOwned.lockRescue(_LIFEBUOY_DEPLOYER_ACCESS_LOCK);
vm.prank(t.deployer);
if (t.deployer != t.owner) {
vm.expectRevert(Lifebuoy.RescueUnauthorizedOrLocked.selector);
}
t.lifebuoyOwned.rescueETH(t.recipient, 1);
} else {
vm.prank(t.deployer);
t.lifebuoyOwned.lockRescue(_LIFEBUOY_OWNER_ACCESS_LOCK);

vm.prank(t.owner);
if (t.deployer != t.owner) {
vm.expectRevert(Lifebuoy.RescueUnauthorizedOrLocked.selector);
}
t.lifebuoyOwned.rescueETH(t.recipient, 1);
}
}
}

function testLifebuoyOwnedCloneRescuePermissions(bytes32) public {
Expand Down

0 comments on commit 67c6de5

Please sign in to comment.