Skip to content

Commit 932c535

Browse files
committed
create RescueAirdrop and Factory
1 parent 3da92a5 commit 932c535

File tree

5 files changed

+157
-0
lines changed

5 files changed

+157
-0
lines changed

.env.sample

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ MAINNET_RPC_URL=
55
SEPOLIA_RPC_URL=
66
GOERLI_RPC_URL=
77
ARBITRUM_RPC_URL=
8+
BASE_RPC_URL=
89
ETHERSCAN_API_KEY=
910
ARBISCAN_API_KEY=
1011
DEPLOYER_PRIVATE_KEY=0x......

src/RescueAirdropFactory.sol

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity =0.8.15;
3+
4+
// inheriting
5+
import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
6+
import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
7+
8+
// interfaces
9+
import {IRescueAirdrop} from "@src/interfaces/IRescueAirdrop.sol";
10+
11+
contract RescueAirdropFactory is UpgradeableBeacon {
12+
uint256 public proxyCount;
13+
14+
event ProxyDeployed(uint256 indexed proxyCount, address proxy);
15+
16+
constructor(
17+
address beaconImplementation
18+
) UpgradeableBeacon(beaconImplementation) {}
19+
20+
function deployNewProxy() external onlyOwner {
21+
address proxy = address(new BeaconProxy(address(this), ""));
22+
IRescueAirdrop(proxy).__RescueAirdrop_init();
23+
24+
emit ProxyDeployed(proxyCount, proxy);
25+
26+
proxyCount++;
27+
}
28+
29+
function rescueTokens(
30+
address proxy,
31+
address token,
32+
address to,
33+
uint256 amount
34+
) external onlyOwner {
35+
IRescueAirdrop(proxy).rescueTokens(token, to, amount);
36+
}
37+
}

src/RescueAirdropUpgradeable.sol

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity =0.8.15;
3+
4+
// inheriting
5+
import {OwnableUpgradeable} from "@src/custom/OwnableUpgradeable.sol";
6+
import {IRescueAirdrop} from "@src/interfaces/IRescueAirdrop.sol";
7+
8+
// libs
9+
import {SafeERC20Upgradeable, IERC20Upgradeable} from "@openzeppelin-upgradeable/contracts/token/ERC20/utils/SafeERC20Upgradeable.sol";
10+
11+
/**
12+
* @title Rescue Airdrop
13+
* @author @apoorvlathey
14+
*
15+
* @notice Beacon Proxy implementation that allows to transfer ERC20 tokens stuck in a address, where this contract would eventually be deployed.
16+
*/
17+
contract RescueAirdropUpgradeable is OwnableUpgradeable, IRescueAirdrop {
18+
using SafeERC20Upgradeable for IERC20Upgradeable;
19+
20+
function __RescueAirdrop_init() external override initializer {
21+
// owner is the factory that'll deploy this contract
22+
__Ownable_init();
23+
}
24+
25+
function rescueTokens(
26+
address token,
27+
address to,
28+
uint256 amount
29+
) external override onlyOwner {
30+
IERC20Upgradeable(token).safeTransfer(to, amount);
31+
}
32+
}

src/interfaces/IRescueAirdrop.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity =0.8.15;
3+
4+
interface IRescueAirdrop {
5+
function __RescueAirdrop_init() external;
6+
7+
function rescueTokens(address token, address to, uint256 amount) external;
8+
}

test/RescueAirdrop.t.sol

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity =0.8.15;
3+
4+
import {Test, console} from "forge-std/Test.sol";
5+
6+
import {RescueAirdropFactory} from "@src/RescueAirdropFactory.sol";
7+
import {RescueAirdropUpgradeable} from "@src/RescueAirdropUpgradeable.sol";
8+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
9+
import {MockERC20} from "@mocks/MockERC20.sol";
10+
11+
contract RescueAirdropTests is Test {
12+
uint256 baseFork;
13+
// uint256 BLOCK_NUMBER = 12683328;
14+
uint256 CHAIN_ID = 8453;
15+
16+
address FACTORY_DEPLOYER = 0x6ce798Bc8C8C93F3C312644DcbdD2ad6698622C5;
17+
uint256 deployerNonce = 9;
18+
address NFTX_FACTORY = 0xBE86f647b167567525cCAAfcd6f881F1Ee558216;
19+
uint256 factoryNonce = 307;
20+
address MFER_VAULT = 0xDF7A2593A70fF1e69b5a20644Ae848558A2F7C86;
21+
address MFER_AIRDROP_TOKEN = 0xE3086852A4B125803C815a158249ae468A3254Ca;
22+
uint256 airdropAmount = 670_000 ether;
23+
24+
RescueAirdropUpgradeable rescueAirdropImpl;
25+
RescueAirdropFactory rescueAidropFactory;
26+
27+
function setUp() public {
28+
// Generate a base fork
29+
baseFork = vm.createFork(vm.envString("BASE_RPC_URL"));
30+
// Select our fork for the VM
31+
vm.selectFork(baseFork);
32+
33+
// the MFER token on Base uses solc ^0.8.20 so the test will fail here with EvmError: NotActivated
34+
// so instead etching MockERC20 here
35+
MockERC20 token = new MockERC20(airdropAmount);
36+
vm.etch(MFER_AIRDROP_TOKEN, address(token).code);
37+
38+
// transfer tokens to the vault address
39+
token.transfer(MFER_VAULT, airdropAmount);
40+
}
41+
42+
function test_claim_MFER() public {
43+
// send some ETH to the deployer
44+
vm.deal(FACTORY_DEPLOYER, 10 ether);
45+
vm.startPrank(FACTORY_DEPLOYER);
46+
47+
uint256 mferAirdropAmount = IERC20(MFER_AIRDROP_TOKEN).balanceOf(
48+
MFER_VAULT
49+
);
50+
51+
rescueAirdropImpl = new RescueAirdropUpgradeable();
52+
53+
for (uint256 i; i < deployerNonce; ++i) {
54+
rescueAidropFactory = new RescueAirdropFactory(
55+
address(rescueAirdropImpl)
56+
);
57+
}
58+
assertEq(address(rescueAidropFactory), NFTX_FACTORY);
59+
60+
for (uint256 i; i <= factoryNonce; ++i) {
61+
rescueAidropFactory.deployNewProxy();
62+
}
63+
64+
uint256 preMFERBalance = IERC20(MFER_AIRDROP_TOKEN).balanceOf(
65+
FACTORY_DEPLOYER
66+
);
67+
rescueAidropFactory.rescueTokens(
68+
MFER_VAULT,
69+
MFER_AIRDROP_TOKEN,
70+
FACTORY_DEPLOYER,
71+
mferAirdropAmount
72+
);
73+
uint256 postMFERBalance = IERC20(MFER_AIRDROP_TOKEN).balanceOf(
74+
FACTORY_DEPLOYER
75+
);
76+
77+
assertEq(postMFERBalance - preMFERBalance, mferAirdropAmount);
78+
}
79+
}

0 commit comments

Comments
 (0)