-
Notifications
You must be signed in to change notification settings - Fork 278
/
recoverERC20.sol
154 lines (131 loc) · 4.89 KB
/
recoverERC20.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "forge-std/Test.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/*
Name: Incorrect implementation of the recoverERC20() function in the StakingRewards
Description:
The recoverERC20() function in StakingRewards.sol can potentially serve as a backdoor for the owner to retrieve rewardsToken.
There is no corresponding check against the rewardsToken. This creates an administrative privilege where the owner can sweep the rewards tokens, potentially using it as a means to exploit depositors.
It's similar to a forked issue if you forked vulnerable code.
Mitigation:
disallowing recovery of the rewardToken within the recoverErc20 function
REF:
https://twitter.com/1nf0s3cpt/status/1680806251482189824
https://github.com/code-423n4/2022-02-concur-findings/issues/210
https://github.com/code-423n4/2022-09-y2k-finance-findings/issues/49
https://github.com/code-423n4/2022-10-paladin-findings/issues/40
https://blog.openzeppelin.com/across-token-and-token-distributor-audit#anyone-can-prevent-stakers-from-getting-their-rewards
*/
contract ContractTest is Test {
RewardToken RewardTokenContract;
VulnStakingRewards VulnStakingRewardsContract;
FixedtakingRewards FixedtakingRewardsContract;
address alice = vm.addr(1);
function setUp() public {
RewardTokenContract = new RewardToken();
VulnStakingRewardsContract = new VulnStakingRewards(
address(RewardTokenContract)
);
RewardTokenContract.transfer(address(alice), 10000 ether);
FixedtakingRewardsContract = new FixedtakingRewards(
address(RewardTokenContract)
);
//RewardTokenContract.transfer(address(alice),10000 ether);
}
function testVulnStakingRewards() public {
console.log(
"Before rug RewardToken balance in VulnStakingRewardsContract",
RewardTokenContract.balanceOf(address(this))
);
vm.prank(alice);
//If alice transfer reward token to VulnStakingRewardsContract
RewardTokenContract.transfer(
address(VulnStakingRewardsContract),
10000 ether
);
//admin can rug reward token over recoverERC20()
VulnStakingRewardsContract.recoverERC20(
address(RewardTokenContract),
1000 ether
);
console.log(
"After rug RewardToken balance in VulnStakingRewardsContract",
RewardTokenContract.balanceOf(address(this))
);
}
function testFixedStakingRewards() public {
console.log(
"Before rug RewardToken balance in VulnStakingRewardsContract",
RewardTokenContract.balanceOf(address(this))
);
vm.prank(alice);
//If alice transfer reward token to VulnStakingRewardsContract
RewardTokenContract.transfer(
address(FixedtakingRewardsContract),
10000 ether
);
FixedtakingRewardsContract.recoverERC20(
address(RewardTokenContract),
1000 ether
);
console.log(
"After rug RewardToken balance in VulnStakingRewardsContract",
RewardTokenContract.balanceOf(address(this))
);
}
receive() external payable {}
}
contract VulnStakingRewards {
using SafeERC20 for IERC20;
IERC20 public rewardsToken;
address public owner;
event Recovered(address token, uint256 amount);
constructor(address _rewardsToken) {
rewardsToken = IERC20(_rewardsToken);
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}
function recoverERC20(
address tokenAddress,
uint256 tokenAmount
) public onlyOwner {
IERC20(tokenAddress).safeTransfer(owner, tokenAmount);
emit Recovered(tokenAddress, tokenAmount);
}
}
contract FixedtakingRewards {
using SafeERC20 for IERC20;
IERC20 public rewardsToken;
address public owner;
event Recovered(address token, uint256 amount);
constructor(address _rewardsToken) {
rewardsToken = IERC20(_rewardsToken);
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}
function recoverERC20(
address tokenAddress,
uint256 tokenAmount
) external onlyOwner {
require(
tokenAddress != address(rewardsToken),
"Cannot withdraw the rewardsToken"
);
IERC20(tokenAddress).safeTransfer(owner, tokenAmount);
emit Recovered(tokenAddress, tokenAmount);
}
}
contract RewardToken is ERC20, Ownable {
constructor() ERC20("Rewardoken", "Reward") {
_mint(msg.sender, 10000 * 10 ** decimals());
}
}