Skip to content

Commit

Permalink
initial commit (#49)
Browse files Browse the repository at this point in the history
Added RescuableACL specially for ACL Role implementation, because we can't return address of RescueGuardian in this case.
Removed whoCanRescue function from interface.
  • Loading branch information
pavelvm5 authored Nov 18, 2024
1 parent 55d9b81 commit 7722a9d
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 13 deletions.
1 change: 0 additions & 1 deletion src/contracts/utils/Rescuable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,5 @@ abstract contract Rescuable is RescuableBase, IRescuable {
_emergencyEtherTransfer(to, amount);
}

/// @inheritdoc IRescuable
function whoCanRescue() public view virtual returns (address);
}
36 changes: 36 additions & 0 deletions src/contracts/utils/RescuableACL.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;

import {IERC20} from '../oz-common/interfaces/IERC20.sol';
import {RescuableBase} from './RescuableBase.sol';
import {IRescuable} from './interfaces/IRescuable.sol';

/**
* @title Rescuable specially for ACL models
* @author BGD Labs
* @notice abstract contract with the methods to rescue tokens (ERC20 and native) from a contract
*/
abstract contract RescuableACL is RescuableBase, IRescuable {
/// @notice modifier that checks that caller is allowed address
modifier onlyRescueGuardian() {
_checkRescueGuardian();
_;
}

/// @inheritdoc IRescuable
function emergencyTokenTransfer(
address erc20Token,
address to,
uint256 amount
) external onlyRescueGuardian {
_emergencyTokenTransfer(erc20Token, to, amount);
}

/// @inheritdoc IRescuable
function emergencyEtherTransfer(address to, uint256 amount) external onlyRescueGuardian {
_emergencyEtherTransfer(to, amount);
}

/// @notice function, that should revert if `msg.sender` isn't allowed to rescue funds
function _checkRescueGuardian() internal view virtual;
}
8 changes: 2 additions & 6 deletions src/contracts/utils/RescuableBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,15 @@ abstract contract RescuableBase is IRescuableBase {
/// @inheritdoc IRescuableBase
function maxRescue(address erc20Token) public view virtual returns (uint256);

function _emergencyTokenTransfer(
address erc20Token,
address to,
uint256 amount
) internal virtual {
function _emergencyTokenTransfer(address erc20Token, address to, uint256 amount) internal {
uint256 max = maxRescue(erc20Token);
amount = max > amount ? amount : max;
IERC20(erc20Token).safeTransfer(to, amount);

emit ERC20Rescued(msg.sender, erc20Token, to, amount);
}

function _emergencyEtherTransfer(address to, uint256 amount) internal virtual {
function _emergencyEtherTransfer(address to, uint256 amount) internal {
(bool success, ) = to.call{value: amount}(new bytes(0));
if (!success) {
revert EthTransferFailed();
Expand Down
6 changes: 0 additions & 6 deletions src/contracts/utils/interfaces/IRescuable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,4 @@ interface IRescuable is IRescuableBase {
* @param amount of eth to rescue
*/
function emergencyEtherTransfer(address to, uint256 amount) external;

/**
* @notice method that defines the address that is allowed to rescue tokens
* @return the allowed address
*/
function whoCanRescue() external view returns (address);
}
1 change: 1 addition & 0 deletions test/Rescuable.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.0;

import 'forge-std/Test.sol';

import {IERC20} from '../src/contracts/oz-common/interfaces/IERC20.sol';
import {Address} from '../src/contracts/oz-common/Address.sol';
import {ERC20} from '../src/mocks/ERC20.sol';
Expand Down
117 changes: 117 additions & 0 deletions test/RescuableACL.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import 'forge-std/Test.sol';

import {IERC20} from '../src/contracts/oz-common/interfaces/IERC20.sol';
import {Address} from '../src/contracts/oz-common/Address.sol';
import {ERC20} from '../src/mocks/ERC20.sol';
import {RescuableACL as AbstractRescuableACL, IRescuable} from '../src/contracts/utils/RescuableACL.sol';
import {RescuableBase, IRescuableBase} from '../src/contracts/utils/RescuableBase.sol';

contract RescuableACL is AbstractRescuableACL {
address public immutable ALLOWED;

constructor(address allowedAddress) {
ALLOWED = allowedAddress;
}

function _checkRescueGuardian() internal view override {
if (msg.sender != ALLOWED) {
revert OnlyRescueGuardian();
}
}

function maxRescue(
address
) public pure override(RescuableBase, IRescuableBase) returns (uint256) {
return type(uint256).max;
}

receive() external payable {}
}

contract RescueACLTest is Test {
address public constant ALLOWED = address(1023579);

IERC20 public testToken;
RescuableACL public tokensReceiver;

event ERC20Rescued(
address indexed caller,
address indexed token,
address indexed to,
uint256 amount
);
event NativeTokensRescued(address indexed caller, address indexed to, uint256 amount);

function setUp() public {
testToken = new ERC20('Test', 'TST');
tokensReceiver = new RescuableACL(ALLOWED);
}

function testEmergencyEtherTransfer() public {
address randomWallet = address(1239516);
hoax(randomWallet, 50 ether);
Address.sendValue(payable(address(tokensReceiver)), 5 ether);

assertEq(address(tokensReceiver).balance, 5 ether);

address recipient = address(1230123519);

hoax(ALLOWED);
vm.expectEmit(true, true, false, true);
emit NativeTokensRescued(ALLOWED, recipient, 5 ether);
tokensReceiver.emergencyEtherTransfer(recipient, 5 ether);

assertEq(address(tokensReceiver).balance, 0 ether);
assertEq(address(recipient).balance, 5 ether);
}

function testEmergencyEtherTransferWhenNotOwner() public {
address randomWallet = address(1239516);

hoax(randomWallet, 50 ether);
Address.sendValue(payable(address(tokensReceiver)), 5 ether);

assertEq(address(tokensReceiver).balance, 5 ether);

address recipient = address(1230123519);

vm.expectRevert(abi.encodeWithSelector(IRescuable.OnlyRescueGuardian.selector));
tokensReceiver.emergencyEtherTransfer(recipient, 5 ether);
}

function testEmergencyTokenTransfer() public {
address randomWallet = address(1239516);
deal(address(testToken), randomWallet, 10 ether);
hoax(randomWallet);
testToken.transfer(address(tokensReceiver), 3 ether);

assertEq(testToken.balanceOf(address(tokensReceiver)), 3 ether);

address recipient = address(1230123519);

hoax(ALLOWED);
vm.expectEmit(true, true, false, true);
emit ERC20Rescued(ALLOWED, address(testToken), recipient, 3 ether);
tokensReceiver.emergencyTokenTransfer(address(testToken), recipient, 3 ether);

assertEq(testToken.balanceOf(address(tokensReceiver)), 0);
assertEq(testToken.balanceOf(address(recipient)), 3 ether);
}

function testEmergencyTokenTransferWhenNotOwner() public {
address randomWallet = address(1239516);
deal(address(testToken), randomWallet, 10 ether);
hoax(randomWallet);
testToken.transfer(address(tokensReceiver), 3 ether);

assertEq(testToken.balanceOf(address(tokensReceiver)), 3 ether);

address recipient = address(1230123519);

vm.expectRevert(abi.encodeWithSelector(IRescuable.OnlyRescueGuardian.selector));
tokensReceiver.emergencyTokenTransfer(address(testToken), recipient, 3 ether);
}
}

0 comments on commit 7722a9d

Please sign in to comment.