Skip to content

Commit fb031c4

Browse files
committed
Crosschain version
1 parent 97ce0ab commit fb031c4

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-0
lines changed

src/CrosschainNubilaNetwork.sol

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
5+
import "openzeppelin-contracts/contracts/access/Ownable.sol";
6+
7+
contract CrosschainNubilaNetwork is ERC20, Ownable {
8+
modifier onlyMinter {
9+
if (!minters[msg.sender]) {
10+
revert ErrNotAuthorizedMinter(msg.sender);
11+
}
12+
_;
13+
}
14+
error ErrZeroAddress();
15+
error ErrAlreadyMinter(address account);
16+
error ErrNotAuthorizedMinter(address account);
17+
error ErrNotMinter(address account);
18+
19+
event MinterAdded(address indexed account);
20+
event MinterRemoved(address indexed account);
21+
event CrosschainMinted(address indexed receiver, uint256 amount);
22+
event CrosschainBurned(address indexed sender, address indexed receiver, uint256 amount);
23+
24+
mapping (address => bool) private minters;
25+
26+
constructor() ERC20("Nubila Network", "NB") Ownable(msg.sender) {
27+
}
28+
29+
function addMinter(address account) public onlyOwner {
30+
if (account == address(0)) {
31+
revert ErrZeroAddress();
32+
}
33+
if (minters[account]) {
34+
revert ErrAlreadyMinter(account);
35+
}
36+
minters[account] = true;
37+
emit MinterAdded(account);
38+
}
39+
40+
function removeMinter(address account) public onlyOwner {
41+
if (!minters[account]) {
42+
revert ErrNotMinter(account);
43+
}
44+
minters[account] = false;
45+
emit MinterRemoved(account);
46+
}
47+
48+
function isMinter(address account) public view returns (bool) {
49+
return minters[account];
50+
}
51+
52+
function crosschainMint(address to, uint256 amount) public onlyMinter {
53+
_mint(to, amount);
54+
emit CrosschainMinted(to, amount);
55+
}
56+
57+
function crosschainBurn(uint256 amount, address receiver) public {
58+
address sender = msg.sender;
59+
if (receiver == address(0)) {
60+
receiver = sender;
61+
}
62+
_burn(sender, amount);
63+
emit CrosschainBurned(sender, receiver, amount);
64+
}
65+
}

test/CrosschainNubilaNetwork.t.sol

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.13;
3+
4+
import {Test, console} from "forge-std/Test.sol";
5+
import {IERC20Errors} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
6+
import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol";
7+
import {CrosschainNubilaNetwork} from "../src/CrosschainNubilaNetwork.sol";
8+
9+
contract CrosschainNubilaNetworkTest is Test {
10+
CrosschainNubilaNetwork public token;
11+
address public owner;
12+
address public minter1;
13+
address public minter2;
14+
address public user1;
15+
address public user2;
16+
17+
event MinterAdded(address indexed account);
18+
event MinterRemoved(address indexed account);
19+
event CrosschainMinted(address indexed receiver, uint256 amount);
20+
event CrosschainBurned(address indexed sender, address indexed receiver, uint256 amount);
21+
22+
function setUp() public {
23+
owner = makeAddr("owner");
24+
minter1 = makeAddr("minter1");
25+
minter2 = makeAddr("minter2");
26+
user1 = makeAddr("user1");
27+
user2 = makeAddr("user2");
28+
29+
vm.startPrank(owner);
30+
token = new CrosschainNubilaNetwork();
31+
vm.stopPrank();
32+
}
33+
34+
function testInitialState() public view {
35+
assertEq(token.name(), "Nubila Network");
36+
assertEq(token.symbol(), "NB");
37+
assertEq(token.totalSupply(), 0);
38+
assertEq(token.owner(), owner);
39+
assertFalse(token.isMinter(owner));
40+
assertFalse(token.isMinter(minter1));
41+
}
42+
43+
function testAddMinter() public {
44+
vm.expectEmit(true, true, true, true);
45+
emit MinterAdded(minter1);
46+
vm.startPrank(owner);
47+
token.addMinter(minter1);
48+
assertTrue(token.isMinter(minter1));
49+
vm.expectRevert(abi.encodeWithSelector(CrosschainNubilaNetwork.ErrAlreadyMinter.selector, minter1));
50+
token.addMinter(minter1); // Should revert if already a minter
51+
vm.expectRevert(CrosschainNubilaNetwork.ErrZeroAddress.selector);
52+
token.addMinter(address(0)); // Should revert for zero address
53+
vm.stopPrank();
54+
55+
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, user1));
56+
vm.prank(user1);
57+
token.addMinter(minter2); // Only owner can add minters
58+
}
59+
60+
function testRemoveMinter() public {
61+
vm.startPrank(owner);
62+
token.addMinter(minter1);
63+
assertTrue(token.isMinter(minter1));
64+
65+
token.removeMinter(minter1);
66+
assertFalse(token.isMinter(minter1));
67+
vm.expectRevert(abi.encodeWithSelector(CrosschainNubilaNetwork.ErrNotMinter.selector, minter1));
68+
token.removeMinter(minter1); // Should revert if not a minter
69+
vm.stopPrank();
70+
71+
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, user1));
72+
vm.prank(user1);
73+
token.removeMinter(minter2); // Only owner can remove minters
74+
}
75+
76+
function testMint() public {
77+
vm.startPrank(owner);
78+
token.addMinter(minter1);
79+
vm.stopPrank();
80+
81+
vm.expectEmit(true, true, true, true);
82+
emit CrosschainMinted(user1, 100 ether);
83+
vm.prank(minter1);
84+
token.crosschainMint(user1, 100 ether);
85+
assertEq(token.balanceOf(user1), 100 ether);
86+
assertEq(token.totalSupply(), 100 ether);
87+
88+
vm.expectRevert(abi.encodeWithSelector(CrosschainNubilaNetwork.ErrNotAuthorizedMinter.selector, user2));
89+
vm.prank(user2);
90+
token.crosschainMint(user2, 50 ether); // Only minters can mint
91+
}
92+
93+
function testCrosschainBurn() public {
94+
vm.startPrank(owner);
95+
token.addMinter(minter1);
96+
vm.stopPrank();
97+
98+
vm.expectEmit(true, true, true, true);
99+
emit CrosschainMinted(user1, 200 ether);
100+
vm.prank(minter1);
101+
token.crosschainMint(user1, 200 ether);
102+
assertEq(token.balanceOf(user1), 200 ether);
103+
104+
vm.startPrank(user1);
105+
vm.expectEmit(true, true, true, true);
106+
emit CrosschainBurned(user1, user2, 50 ether);
107+
token.crosschainBurn(50 ether, user2);
108+
assertEq(token.balanceOf(user1), 150 ether);
109+
assertEq(token.balanceOf(user2), 0); // Bridged tokens are burned, not transferred to receiver
110+
assertEq(token.totalSupply(), 150 ether);
111+
vm.expectEmit(true, true, true, true);
112+
emit CrosschainBurned(user1, user1, 25 ether);
113+
token.crosschainBurn(25 ether, address(0)); // Test with zero address receiver, should burn from sender
114+
assertEq(token.balanceOf(user1), 125 ether); // This line is duplicated, but it's fine for the test
115+
assertEq(token.totalSupply(), 125 ether);
116+
117+
vm.expectRevert(abi.encodeWithSelector(IERC20Errors.ERC20InsufficientBalance.selector, user1, 125 ether, 200 ether));
118+
token.crosschainBurn(200 ether, user2); // Should revert if amount exceeds balance
119+
vm.stopPrank();
120+
}
121+
}

0 commit comments

Comments
 (0)