-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
6 changed files
with
156 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |