@@ -7,28 +7,28 @@ import {RecoveryContract} from "../src/RecoveryContract.sol";
77
88contract RecoveryContractTest is Test {
99 address constant OWNER = address (0x1 );
10- address constant RECOVERER = address (0x2 );
1110 address constant ATTACKER = address (0x3 );
1211 address payable constant RECIPIENT = payable (address (0x4 ));
1312
1413 RecoveryContract rc;
1514 MockERC20 token;
15+ MockERC721 nft;
1616
1717 function setUp () public {
18- rc = new RecoveryContract (OWNER, RECOVERER );
18+ rc = new RecoveryContract (OWNER);
1919 token = new MockERC20 ();
20+ nft = new MockERC721 ();
2021 }
2122
2223 // ──────────────────── Construction ────────────────────
2324
2425 function test_constructor () public view {
2526 assertEq (rc.owner (), OWNER);
26- assertEq (rc.recoverer (), RECOVERER);
2727 }
2828
2929 // ──────────────────── ETH recovery ────────────────────
3030
31- function test_recoverETH_owner () public {
31+ function test_recoverETH () public {
3232 vm.deal (address (rc), 1 ether);
3333
3434 vm.prank (OWNER);
@@ -38,19 +38,10 @@ contract RecoveryContractTest is Test {
3838 assertEq (address (rc).balance, 0 );
3939 }
4040
41- function test_recoverETH_recoverer () public {
42- vm.deal (address (rc), 1 ether);
43-
44- vm.prank (RECOVERER);
45- rc.recoverETH (RECIPIENT, 1 ether);
46-
47- assertEq (RECIPIENT.balance, 1 ether);
48- }
49-
5041 function test_recoverETH_partial () public {
5142 vm.deal (address (rc), 2 ether);
5243
53- vm.prank (RECOVERER );
44+ vm.prank (OWNER );
5445 rc.recoverETH (RECIPIENT, 0.5 ether);
5546
5647 assertEq (RECIPIENT.balance, 0.5 ether);
@@ -61,13 +52,13 @@ contract RecoveryContractTest is Test {
6152 vm.deal (address (rc), 1 ether);
6253
6354 vm.prank (ATTACKER);
64- vm.expectRevert (RecoveryContract.NotAuthorized. selector );
55+ vm.expectRevert (abi.encodeWithSelector (Ownable.OwnableUnauthorizedAccount. selector , ATTACKER) );
6556 rc.recoverETH (payable (ATTACKER), 1 ether);
6657 }
6758
6859 // ──────────────────── ERC20 recovery ────────────────────
6960
70- function test_recoverERC20_owner () public {
61+ function test_recoverERC20 () public {
7162 token.mint (address (rc), 1000e18 );
7263
7364 vm.prank (OWNER);
@@ -77,21 +68,67 @@ contract RecoveryContractTest is Test {
7768 assertEq (token.balanceOf (address (rc)), 0 );
7869 }
7970
80- function test_recoverERC20_recoverer () public {
71+ function test_recoverERC20_unauthorized () public {
8172 token.mint (address (rc), 1000e18 );
8273
83- vm.prank (RECOVERER);
74+ vm.prank (ATTACKER);
75+ vm.expectRevert (abi.encodeWithSelector (Ownable.OwnableUnauthorizedAccount.selector , ATTACKER));
8476 rc.recoverERC20 (address (token), RECIPIENT, 1000e18 );
77+ }
8578
86- assertEq (token.balanceOf (RECIPIENT), 1000e18 );
79+ // ──────────────────── Arbitrary execute ────────────────────
80+
81+ function test_execute_ETH () public {
82+ vm.deal (address (rc), 1 ether);
83+
84+ vm.prank (OWNER);
85+ rc.execute (RECIPIENT, 1 ether, "" );
86+
87+ assertEq (RECIPIENT.balance, 1 ether);
8788 }
8889
89- function test_recoverERC20_unauthorized () public {
90- token.mint (address (rc), 1000e18 );
90+ function test_execute_ERC721 () public {
91+ nft.mint (address (rc), 42 );
92+
93+ vm.prank (OWNER);
94+ rc.execute (address (nft), 0 , abi.encodeCall (MockERC721.transferFrom, (address (rc), RECIPIENT, 42 )));
95+
96+ assertEq (nft.ownerOf (42 ), RECIPIENT);
97+ }
98+
99+ function test_execute_unauthorized () public {
100+ vm.deal (address (rc), 1 ether);
91101
92102 vm.prank (ATTACKER);
93- vm.expectRevert (RecoveryContract.NotAuthorized.selector );
94- rc.recoverERC20 (address (token), RECIPIENT, 1000e18 );
103+ vm.expectRevert (abi.encodeWithSelector (Ownable.OwnableUnauthorizedAccount.selector , ATTACKER));
104+ rc.execute (RECIPIENT, 1 ether, "" );
105+ }
106+
107+ function test_execute_revertsOnFailure () public {
108+ vm.prank (OWNER);
109+ vm.expectRevert ();
110+ rc.execute (address (token), 0 , abi.encodeCall (MockERC20.transfer, (RECIPIENT, 1 )));
111+ }
112+
113+ // ──────────────────── Ownable ────────────────────
114+
115+ function test_transferOwnership () public {
116+ address newOwner = makeAddr ("newOwner " );
117+
118+ vm.prank (OWNER);
119+ rc.transferOwnership (newOwner);
120+ assertEq (rc.owner (), newOwner);
121+
122+ vm.deal (address (rc), 1 ether);
123+ vm.prank (newOwner);
124+ rc.execute (RECIPIENT, 1 ether, "" );
125+ assertEq (RECIPIENT.balance, 1 ether);
126+ }
127+
128+ function test_transferOwnership_unauthorized () public {
129+ vm.prank (ATTACKER);
130+ vm.expectRevert (abi.encodeWithSelector (Ownable.OwnableUnauthorizedAccount.selector , ATTACKER));
131+ rc.transferOwnership (ATTACKER);
95132 }
96133
97134 // ──────────────────── Receive ────────────────────
@@ -118,3 +155,17 @@ contract MockERC20 {
118155 return true ;
119156 }
120157}
158+
159+ /// @dev Minimal ERC721 for testing
160+ contract MockERC721 {
161+ mapping (uint256 => address ) public ownerOf;
162+
163+ function mint (address to , uint256 tokenId ) external {
164+ ownerOf[tokenId] = to;
165+ }
166+
167+ function transferFrom (address from , address to , uint256 tokenId ) external {
168+ require (ownerOf[tokenId] == from, "not owner " );
169+ ownerOf[tokenId] = to;
170+ }
171+ }
0 commit comments