Skip to content

Commit 19fda34

Browse files
authored
Merge pull request #2 from geovgy/geovgy-edits
Add batch mint and other functions + unit tests
2 parents 793e2dd + 2008440 commit 19fda34

2 files changed

Lines changed: 304 additions & 7 deletions

File tree

src/ShellToken.sol

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,82 @@ import "openzeppelin-contracts/contracts/access/Ownable.sol";
66

77
contract ShellToken is ERC20, Ownable {
88

9-
mapping(address => bool) public allowedToTransfer;
9+
struct Recipient {
10+
address to;
11+
uint256 amount;
12+
}
1013

11-
constructor() ERC20("ShellToken", "SHELL") Ownable(msg.sender) {
14+
struct Multiplier {
15+
address activity;
16+
uint256 multiplier;
1217
}
1318

14-
function mintShells(address to, uint256 amount) public onlyOwner {
19+
mapping(address => bool) public isAdmin;
20+
21+
// address of activity -> multiplier
22+
// Activities are the contract addresses of Trove Managers, Stability Pools, LP tokens, etc.
23+
mapping(address => uint) public multiplier; //percentage out of 100
24+
25+
mapping(address => bool) public allowedToTransfer;
26+
27+
constructor() ERC20("ShellToken", "SHELL") Ownable(msg.sender) {}
28+
29+
function mintShells(address to, uint256 amount) public {
30+
require(isAdmin[msg.sender], "Not an admin");
1531
_mint(to, amount);
1632
}
1733

18-
function deleteShells(address from, uint256 amount) public onlyOwner {
34+
function mintBatchShells(Recipient[] calldata recipients) public {
35+
require(isAdmin[msg.sender], "Not an admin");
36+
for (uint i; i < recipients.length; ) {
37+
_mint(recipients[i].to, recipients[i].amount);
38+
unchecked { ++i; }
39+
}
40+
}
41+
42+
function deleteShells(address from, uint256 amount) public {
43+
require(isAdmin[msg.sender], "Not an admin");
1944
_burn(from, amount);
2045
}
2146

2247
function transfer(address to, uint256 amount) public override returns (bool) {
23-
if (allowedToTransfer[msg.sender]) {
24-
return super.transfer(to, amount);
48+
if (!allowedToTransfer[msg.sender]) {
49+
revert("Not allowed to transfer");
50+
}
51+
return super.transfer(to, amount);
52+
}
53+
54+
function transferFrom(address from, address to, uint256 amount) public override returns (bool) {
55+
if (!allowedToTransfer[from]) {
56+
revert("Not allowed to transfer");
57+
}
58+
return super.transferFrom(from, to, amount);
59+
}
60+
61+
62+
function getMultipliers(address[] calldata contracts) external view returns (Multiplier[] memory) {
63+
Multiplier[] memory multipliers = new Multiplier[](contracts.length);
64+
for (uint i; i < contracts.length; ) {
65+
multipliers[i] = Multiplier(contracts[i], multiplier[contracts[i]]);
66+
unchecked { ++i; }
2567
}
26-
return false;
68+
return multipliers;
2769
}
2870

71+
//////////////////////////
72+
// ONLY OWNER FUNCTIONS //
73+
//////////////////////////
74+
2975
function updateAllowedToTransfer(address user, bool allowed) public onlyOwner {
3076
allowedToTransfer[user] = allowed;
3177
}
78+
79+
function updateIsAdmin(address user, bool _isAdmin) public onlyOwner {
80+
isAdmin[user] = _isAdmin;
81+
}
82+
83+
function setMultiplier(address activity, uint perc) public onlyOwner {
84+
multiplier[activity] = perc;
85+
}
3286
}
3387

test/ShellToken.t.sol

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import "forge-std/Test.sol";
5+
import "../src/ShellToken.sol";
6+
import "openzeppelin-contracts/contracts/access/Ownable.sol";
7+
8+
contract ShellTokenTest is Test {
9+
ShellToken internal shellToken;
10+
address internal admin = makeAddr("admin");
11+
address internal owner = makeAddr("owner");
12+
13+
function setUp() public {
14+
vm.startPrank(owner);
15+
shellToken = new ShellToken();
16+
shellToken.updateIsAdmin(admin, true);
17+
vm.stopPrank();
18+
}
19+
20+
function test_updateIsAdmin() public {
21+
address tempAdmin = makeAddr("tempAdmin");
22+
23+
assertEq(shellToken.isAdmin(tempAdmin), false);
24+
25+
vm.prank(owner);
26+
shellToken.updateIsAdmin(tempAdmin, true);
27+
28+
assertEq(shellToken.isAdmin(tempAdmin), true);
29+
30+
vm.prank(owner);
31+
shellToken.updateIsAdmin(tempAdmin, false);
32+
33+
assertEq(shellToken.isAdmin(tempAdmin), false);
34+
}
35+
36+
function test_mintShells() public {
37+
address user = makeAddr("user");
38+
39+
vm.startPrank(admin);
40+
shellToken.mintShells(user, 100);
41+
vm.stopPrank();
42+
43+
assertEq(shellToken.balanceOf(user), 100);
44+
}
45+
46+
function test_revert_mintShells_notAdmin() public {
47+
address user = makeAddr("user");
48+
49+
vm.startPrank(makeAddr("notAdmin"));
50+
vm.expectRevert("Not an admin");
51+
shellToken.mintShells(user, 100);
52+
vm.stopPrank();
53+
54+
assertEq(shellToken.balanceOf(user), 0);
55+
}
56+
57+
function test_mintBatchShells() public {
58+
address user1 = makeAddr("user1");
59+
address user2 = makeAddr("user2");
60+
61+
ShellToken.Recipient[] memory recipients = new ShellToken.Recipient[](2);
62+
recipients[0] = ShellToken.Recipient(user1, 100);
63+
recipients[1] = ShellToken.Recipient(user2, 200);
64+
65+
vm.startPrank(admin);
66+
shellToken.mintBatchShells(recipients);
67+
vm.stopPrank();
68+
69+
assertEq(shellToken.balanceOf(user1), 100);
70+
assertEq(shellToken.balanceOf(user2), 200);
71+
}
72+
73+
function test_revert_mintBatchShells_notAdmin() public {
74+
address user = makeAddr("user");
75+
76+
ShellToken.Recipient[] memory recipients = new ShellToken.Recipient[](1);
77+
recipients[0] = ShellToken.Recipient(user, 100);
78+
79+
vm.startPrank(makeAddr("notAdmin"));
80+
vm.expectRevert("Not an admin");
81+
shellToken.mintBatchShells(recipients);
82+
vm.stopPrank();
83+
84+
assertEq(shellToken.balanceOf(user), 0);
85+
}
86+
87+
function test_deleteShells() public {
88+
address user = makeAddr("user");
89+
90+
vm.startPrank(admin);
91+
shellToken.mintShells(user, 100);
92+
vm.stopPrank();
93+
94+
assertEq(shellToken.balanceOf(user), 100);
95+
96+
vm.startPrank(admin);
97+
shellToken.deleteShells(user, 100);
98+
vm.stopPrank();
99+
100+
assertEq(shellToken.balanceOf(user), 0);
101+
}
102+
103+
function test_revert_deleteShells_notAdmin() public {
104+
address user = makeAddr("user");
105+
106+
vm.startPrank(admin);
107+
shellToken.mintShells(user, 100);
108+
vm.stopPrank();
109+
110+
assertEq(shellToken.balanceOf(user), 100);
111+
112+
vm.startPrank(makeAddr("notAdmin"));
113+
vm.expectRevert("Not an admin");
114+
shellToken.deleteShells(user, 100);
115+
vm.stopPrank();
116+
117+
assertEq(shellToken.balanceOf(user), 100);
118+
}
119+
120+
function test_revert_transfer_notAllowed() public {
121+
address user = makeAddr("user");
122+
address to = makeAddr("to");
123+
124+
vm.startPrank(admin);
125+
shellToken.mintShells(user, 100);
126+
vm.stopPrank();
127+
128+
assertEq(shellToken.balanceOf(user), 100);
129+
130+
vm.startPrank(user);
131+
vm.expectRevert("Not allowed to transfer");
132+
shellToken.transfer(to, 100);
133+
vm.stopPrank();
134+
135+
assertEq(shellToken.balanceOf(user), 100);
136+
assertEq(shellToken.balanceOf(to), 0);
137+
}
138+
139+
function test_transfer_allowed() public {
140+
address user = makeAddr("user");
141+
address to = makeAddr("to");
142+
143+
vm.prank(admin);
144+
shellToken.mintShells(user, 100);
145+
146+
assertEq(shellToken.balanceOf(user), 100);
147+
148+
vm.prank(owner);
149+
shellToken.updateAllowedToTransfer(user, true);
150+
151+
vm.prank(user);
152+
shellToken.transfer(to, 100);
153+
154+
assertEq(shellToken.balanceOf(user), 0);
155+
assertEq(shellToken.balanceOf(to), 100);
156+
}
157+
158+
function test_revert_transferFrom_notAllowed() public {
159+
address user = makeAddr("user");
160+
address to = makeAddr("to");
161+
162+
vm.startPrank(admin);
163+
shellToken.mintShells(user, 100);
164+
vm.stopPrank();
165+
166+
assertEq(shellToken.balanceOf(user), 100);
167+
168+
vm.prank(user);
169+
shellToken.approve(address(this), 100);
170+
171+
vm.expectRevert("Not allowed to transfer");
172+
shellToken.transferFrom(user, to, 100);
173+
174+
assertEq(shellToken.balanceOf(user), 100);
175+
assertEq(shellToken.balanceOf(to), 0);
176+
}
177+
178+
function test_transferFrom_allowed() public {
179+
address user = makeAddr("user");
180+
address to = makeAddr("to");
181+
182+
vm.prank(admin);
183+
shellToken.mintShells(user, 100);
184+
185+
assertEq(shellToken.balanceOf(user), 100);
186+
187+
vm.prank(owner);
188+
shellToken.updateAllowedToTransfer(user, true);
189+
190+
vm.prank(user);
191+
shellToken.approve(address(this), 100);
192+
193+
shellToken.transferFrom(user, to, 100);
194+
195+
assertEq(shellToken.balanceOf(user), 0);
196+
assertEq(shellToken.balanceOf(to), 100);
197+
}
198+
199+
function test_setMultiplier() public {
200+
address activity = makeAddr("activity");
201+
202+
vm.startPrank(owner);
203+
shellToken.setMultiplier(activity, 100);
204+
vm.stopPrank();
205+
}
206+
207+
function test_revert_setMultiplier_notOwner() public {
208+
address activity = makeAddr("activity");
209+
address notOwner = makeAddr("notOwner");
210+
211+
vm.startPrank(notOwner);
212+
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, notOwner));
213+
shellToken.setMultiplier(activity, 100);
214+
vm.stopPrank();
215+
216+
vm.startPrank(admin);
217+
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, admin));
218+
shellToken.setMultiplier(activity, 100);
219+
vm.stopPrank();
220+
}
221+
222+
function test_getMultipliers() public {
223+
address[] memory contracts = new address[](3);
224+
contracts[0] = makeAddr("activity1");
225+
contracts[1] = makeAddr("activity2");
226+
contracts[2] = makeAddr("activity3");
227+
228+
vm.startPrank(owner);
229+
for (uint i; i < contracts.length; ) {
230+
shellToken.setMultiplier(contracts[i], 100 + i);
231+
unchecked { ++i; }
232+
}
233+
vm.stopPrank();
234+
235+
ShellToken.Multiplier[] memory multipliers = shellToken.getMultipliers(contracts);
236+
assertEq(multipliers.length, contracts.length);
237+
for (uint i; i < multipliers.length; ) {
238+
assertEq(multipliers[i].activity, contracts[i]);
239+
assertEq(multipliers[i].multiplier, 100 + i);
240+
unchecked { ++i; }
241+
}
242+
}
243+
}

0 commit comments

Comments
 (0)