Skip to content

Commit 2340e7e

Browse files
committed
Initial commit
1 parent 76ebeaa commit 2340e7e

5 files changed

Lines changed: 279 additions & 27 deletions

File tree

src/ProposalManager.sol

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.28;
3+
4+
5+
error Not_Admin();
6+
error INVALID_PROPOSAL_STATUS();
7+
8+
contract ProposalManager {
9+
10+
address public admin;
11+
12+
constructor(){
13+
admin = msg.sender;
14+
}
15+
16+
enum ProposalStatus {
17+
CREATED,
18+
APPROVED,
19+
EXECUTED
20+
}
21+
22+
struct Proposal {
23+
uint256 id;
24+
address proposer;
25+
address recipient;
26+
uint256 amount;
27+
ProposalStatus status;
28+
}
29+
30+
uint256 public proposalCount;
31+
32+
// 1 => {0x23, 0x24, 2, false}
33+
// proposal #1, 0x23 says sends 2eth to 0x24
34+
mapping(uint256 => Proposal) public proposals;
35+
36+
modifier onlyAdmin(){
37+
if (admin != msg.sender) revert Not_Admin();
38+
_;
39+
}
40+
41+
function createProposal(address recipient, uint256 amount) external {
42+
proposalCount++;
43+
44+
proposals[proposalCount] = Proposal({
45+
id: proposalCount,
46+
proposer: msg.sender,
47+
recipient: recipient,
48+
amount: amount,
49+
status: ProposalStatus.CREATED
50+
});
51+
}
52+
53+
function approveProposal(uint _id)external onlyAdmin {
54+
Proposal storage proposal = proposals[_id];
55+
if(proposal.status != ProposalStatus.CREATED) revert INVALID_PROPOSAL_STATUS();
56+
proposals[_id].status = ProposalStatus.APPROVED;
57+
}
58+
59+
function executeProposal(uint _id)external onlyAdmin {
60+
Proposal storage proposal = proposals[_id];
61+
if(proposal.status != ProposalStatus.APPROVED) revert INVALID_PROPOSAL_STATUS();
62+
proposals[_id].status = ProposalStatus.EXECUTED;
63+
}
64+
65+
function getProposalByid(uint _id) external view returns (Proposal memory) {
66+
return proposals[_id];
67+
}
68+
}

src/mocks/ReentrantAttacker.sol

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.28;
3+
4+
import "../../src/treasury.sol";
5+
6+
contract ReentrantAttacker {
7+
Treasury public treasury;
8+
9+
constructor(address payable _treasury) {
10+
treasury = Treasury(_treasury);
11+
}
12+
13+
function attack() external {
14+
treasury.sendFunds(address(this), 1 ether);
15+
}
16+
17+
function getAttackerBalance() public returns (uint) {
18+
return address(this).balance;
19+
}
20+
21+
receive() external payable {
22+
if(address(this).balance < 0){
23+
treasury.sendFunds(address(this), 1 ether);
24+
}
25+
}
26+
}

src/treasury.sol

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,35 @@
11
// SPDX-License-Identifier: UNLICENSED
2-
pragma solidity ^0.8.13;
2+
pragma solidity ^0.8.28;
33

4+
error Not_Admin();
5+
error Not_Enough_Balance();
46

5-
contract SimpleTreasury{
6-
7+
contract Treasury {
78
address admin;
89

9-
constructor(){
10+
constructor() {
1011
admin = msg.sender;
1112
}
1213

13-
modifier onlyAdmin(){
14-
14+
modifier onlyAdmin(uint _amount) {
15+
if(admin != msg.sender) revert Not_Admin();
16+
17+
if (address(this).balance < _amount) revert Not_Enough_Balance();
18+
_;
1519
}
1620

21+
function sendFunds(
22+
address recipient,
23+
uint amount
24+
) external onlyAdmin(amount) returns (bool) {
25+
26+
(bool success, ) = payable(recipient).call{value: amount}("");
27+
28+
return success;
29+
}
1730

31+
1832

19-
function receive() external payable{}
20-
}
33+
receive() external payable {}
34+
// fallback()external{}
35+
}

test/Counter.t.sol

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
1-
// SPDX-License-Identifier: UNLICENSED
2-
pragma solidity ^0.8.13;
1+
// // SPDX-License-Identifier: UNLICENSED
2+
// pragma solidity ^0.8.13;
33

4-
import {Test} from "forge-std/Test.sol";
5-
import {Counter} from "../src/Counter.sol";
4+
// import {Test} from "forge-std/Test.sol";
5+
// import {Counter} from "../src/Counter.sol";
66

7-
contract CounterTest is Test {
8-
Counter public counter;
7+
// contract CounterTest is Test {
8+
// Counter public counter;
99

10-
function setUp() public {
11-
counter = new Counter();
12-
counter.setNumber(0);
13-
}
10+
// function setUp() public {
11+
// counter = new Counter();
12+
// counter.setNumber(0);
13+
// }
1414

15-
function test_Increment() public {
16-
counter.increment();
17-
assertEq(counter.number(), 1);
18-
}
15+
// function test_Increment() public {
16+
// counter.increment();
17+
// assertEq(counter.number(), 1);
18+
// }
1919

20-
function testFuzz_SetNumber(uint256 x) public {
21-
counter.setNumber(x);
22-
assertEq(counter.number(), x);
23-
}
24-
}
20+
// function testFuzz_SetNumber(uint256 x) public {
21+
// counter.setNumber(x);
22+
// assertEq(counter.number(), x);
23+
// }
24+
// }

test/t.sol

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.28;
3+
4+
import "forge-std/Script.sol";
5+
// import "forge-std/console.sol";
6+
import {Test} from "forge-std/Test.sol";
7+
import {Treasury} from "../src/treasury.sol";
8+
import {ReentrantAttacker} from "../src/mocks/ReentrantAttacker.sol";
9+
import {ProposalManager} from "../src/ProposalManager.sol";
10+
11+
contract CounterTest is Test {
12+
Treasury public treasury;
13+
14+
address admin = address(1);
15+
address user = address(2);
16+
address recipient = address(3);
17+
18+
function setUp() public {
19+
vm.prank(admin);
20+
21+
treasury = new Treasury();
22+
23+
vm.deal(address(treasury), 10 ether);
24+
25+
// vm.deal(address(treasury), 10 ether);
26+
}
27+
28+
function test_Admin_Send() public {
29+
uint balB = recipient.balance;
30+
31+
vm.prank(admin);
32+
33+
treasury.sendFunds(recipient, 2 ether);
34+
35+
// treasury.sendFunds(recipient, 2 ether);
36+
uint balA = recipient.balance;
37+
38+
assertEq(balA, balB + 2 ether);
39+
}
40+
41+
function test_RevertIfNotAdmin() public {
42+
vm.prank(user);
43+
44+
vm.expectRevert();
45+
46+
treasury.sendFunds(recipient, 2 ether);
47+
}
48+
49+
function test_TreasuryReceiveFunds() public {
50+
// uint balB = recipient.balance;
51+
vm.deal(user, 4 ether);
52+
53+
vm.prank(user);
54+
55+
(bool success, ) = payable(address(treasury)).call{value: 1 ether}("");
56+
57+
assertEq(address(treasury).balance, 11 ether);
58+
}
59+
60+
function test_ReentrancyAttackFails() public {
61+
ReentrantAttacker attacker = new ReentrantAttacker(
62+
payable(address(treasury))
63+
);
64+
vm.prank((admin));
65+
66+
// vm.deal(address(treasury), 10 ether);
67+
68+
treasury.sendFunds(address(attacker), 1 ether);
69+
70+
assertEq(address(attacker).balance, 1 ether);
71+
72+
assertEq(address(treasury).balance, 9 ether);
73+
}
74+
75+
// function test_ReentrancyAttackDrainsSuccessfully() public {
76+
// console.log("Treasury bal before", address(treasury).balance / 1e18);
77+
78+
// ReentrantAttacker attacker = new ReentrantAttacker(
79+
// payable(address(treasury))
80+
// );
81+
// console.log("Attacker bal before", address(attacker).balance / 1e18);
82+
83+
// attacker.attack();
84+
// // vm.expectRevert();
85+
86+
// console.log("Attacker bal after", address(attacker).balance / 1e18);
87+
// console.log("Treasury bal after", address(treasury).balance / 1e18);
88+
// }
89+
90+
// ========Proposal Manager =============
91+
92+
enum ProposalStatus {
93+
CREATED,
94+
APPROVED,
95+
EXECUTED
96+
}
97+
98+
function test_ProposalManagerCreateProposal() public {
99+
ProposalManager proposalManager = new ProposalManager();
100+
101+
vm.prank(user);
102+
proposalManager.createProposal(recipient, 2);
103+
104+
assertEq(proposalManager.getProposalByid(1).recipient, recipient);
105+
assertEq(proposalManager.getProposalByid(1).amount, 2);
106+
107+
assertEq(uint(proposalManager.getProposalByid(1).status), uint(ProposalStatus.CREATED));
108+
}
109+
110+
function test_ProposalManagerApproveProposal() public {
111+
ProposalManager proposalManager = new ProposalManager();
112+
113+
vm.prank(user);
114+
proposalManager.createProposal(recipient, 2);
115+
116+
117+
proposalManager.approveProposal(1);
118+
119+
assertEq(uint(proposalManager.getProposalByid(1).status), uint(ProposalStatus.APPROVED));
120+
}
121+
122+
function test_ProposalManagerExecutedProposal() public {
123+
ProposalManager proposalManager = new ProposalManager();
124+
125+
vm.prank(user);
126+
proposalManager.createProposal(recipient, 2);
127+
128+
129+
proposalManager.executeProposal(1);
130+
131+
// console.log("bbb", proposalManager.getProposalByid(1));
132+
assertEq(uint(proposalManager.getProposalByid(1).status), uint(ProposalStatus.EXECUTED));
133+
}
134+
135+
// function testProposalStatus() public {
136+
137+
// ProposalManager proposalManager = new ProposalManager();
138+
// ProposalManager.Proposal memory proposal = proposalManager
139+
// .getProposalByid(1);
140+
141+
// assertEq(proposal.status, 0);
142+
// }
143+
}

0 commit comments

Comments
 (0)