mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
371 lines
11 KiB
Solidity
371 lines
11 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import {Test, console2} from "forge-std/Test.sol";
|
|
import {WSNR} from "../src/WSNR.sol";
|
|
import {ReentrancyGuard} from "openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
|
|
|
|
contract WSNRTest is Test {
|
|
WSNR public wsnr;
|
|
address public alice = address(0x1);
|
|
address public bob = address(0x2);
|
|
address public charlie = address(0x3);
|
|
|
|
event Deposit(address indexed from, uint256 amount);
|
|
event Withdrawal(address indexed to, uint256 amount);
|
|
event Transfer(address indexed from, address indexed to, uint256 value);
|
|
event Approval(address indexed owner, address indexed spender, uint256 value);
|
|
|
|
function setUp() public {
|
|
wsnr = new WSNR();
|
|
|
|
// Fund test accounts
|
|
vm.deal(alice, 100 ether);
|
|
vm.deal(bob, 100 ether);
|
|
vm.deal(charlie, 100 ether);
|
|
}
|
|
|
|
function testInitialState() public {
|
|
assertEq(wsnr.name(), "Wrapped SNR");
|
|
assertEq(wsnr.symbol(), "WSNR");
|
|
assertEq(wsnr.decimals(), 18);
|
|
assertEq(wsnr.totalSupply(), 0);
|
|
assertEq(wsnr.getReserve(), 0);
|
|
assertTrue(wsnr.isFullyCollateralized());
|
|
}
|
|
|
|
function testDeposit() public {
|
|
uint256 depositAmount = 10 ether;
|
|
|
|
vm.startPrank(alice);
|
|
|
|
// Test deposit event
|
|
vm.expectEmit(true, false, false, true);
|
|
emit Deposit(alice, depositAmount);
|
|
|
|
// Deposit SNR
|
|
wsnr.deposit{value: depositAmount}();
|
|
|
|
// Check balances
|
|
assertEq(wsnr.balanceOf(alice), depositAmount);
|
|
assertEq(wsnr.totalSupply(), depositAmount);
|
|
assertEq(wsnr.getReserve(), depositAmount);
|
|
assertEq(address(wsnr).balance, depositAmount);
|
|
assertTrue(wsnr.isFullyCollateralized());
|
|
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testDepositZeroAmount() public {
|
|
vm.startPrank(alice);
|
|
vm.expectRevert("WSNR: deposit amount must be greater than 0");
|
|
wsnr.deposit{value: 0}();
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testDepositTo() public {
|
|
uint256 depositAmount = 5 ether;
|
|
|
|
vm.startPrank(alice);
|
|
|
|
// Test deposit event
|
|
vm.expectEmit(true, false, false, true);
|
|
emit Deposit(bob, depositAmount);
|
|
|
|
// Deposit SNR to bob's account
|
|
wsnr.depositTo{value: depositAmount}(bob);
|
|
|
|
// Check balances
|
|
assertEq(wsnr.balanceOf(alice), 0);
|
|
assertEq(wsnr.balanceOf(bob), depositAmount);
|
|
assertEq(wsnr.totalSupply(), depositAmount);
|
|
assertEq(address(wsnr).balance, depositAmount);
|
|
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testDepositToZeroAddress() public {
|
|
vm.startPrank(alice);
|
|
vm.expectRevert("WSNR: cannot deposit to zero address");
|
|
wsnr.depositTo{value: 1 ether}(address(0));
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testReceiveFallback() public {
|
|
uint256 depositAmount = 3 ether;
|
|
|
|
vm.startPrank(alice);
|
|
|
|
// Test deposit event through fallback
|
|
vm.expectEmit(true, false, false, true);
|
|
emit Deposit(alice, depositAmount);
|
|
|
|
// Send SNR directly to contract
|
|
(bool success,) = address(wsnr).call{value: depositAmount}("");
|
|
assertTrue(success);
|
|
|
|
// Check balances
|
|
assertEq(wsnr.balanceOf(alice), depositAmount);
|
|
assertEq(wsnr.totalSupply(), depositAmount);
|
|
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testWithdraw() public {
|
|
uint256 depositAmount = 10 ether;
|
|
uint256 withdrawAmount = 6 ether;
|
|
|
|
vm.startPrank(alice);
|
|
|
|
// First deposit
|
|
wsnr.deposit{value: depositAmount}();
|
|
uint256 aliceBalanceBefore = alice.balance;
|
|
|
|
// Test withdrawal event
|
|
vm.expectEmit(true, false, false, true);
|
|
emit Withdrawal(alice, withdrawAmount);
|
|
|
|
// Withdraw
|
|
wsnr.withdraw(withdrawAmount);
|
|
|
|
// Check balances
|
|
assertEq(wsnr.balanceOf(alice), depositAmount - withdrawAmount);
|
|
assertEq(wsnr.totalSupply(), depositAmount - withdrawAmount);
|
|
assertEq(address(wsnr).balance, depositAmount - withdrawAmount);
|
|
assertEq(alice.balance, aliceBalanceBefore + withdrawAmount);
|
|
assertTrue(wsnr.isFullyCollateralized());
|
|
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testWithdrawAll() public {
|
|
uint256 depositAmount = 10 ether;
|
|
|
|
vm.startPrank(alice);
|
|
|
|
// Deposit and withdraw all
|
|
wsnr.deposit{value: depositAmount}();
|
|
uint256 aliceBalanceBefore = alice.balance;
|
|
|
|
wsnr.withdraw(depositAmount);
|
|
|
|
// Check everything is back to zero
|
|
assertEq(wsnr.balanceOf(alice), 0);
|
|
assertEq(wsnr.totalSupply(), 0);
|
|
assertEq(address(wsnr).balance, 0);
|
|
assertEq(alice.balance, aliceBalanceBefore + depositAmount);
|
|
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testWithdrawZeroAmount() public {
|
|
vm.startPrank(alice);
|
|
wsnr.deposit{value: 1 ether}();
|
|
|
|
vm.expectRevert("WSNR: withdrawal amount must be greater than 0");
|
|
wsnr.withdraw(0);
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testWithdrawInsufficientBalance() public {
|
|
vm.startPrank(alice);
|
|
wsnr.deposit{value: 5 ether}();
|
|
|
|
vm.expectRevert("WSNR: insufficient balance");
|
|
wsnr.withdraw(10 ether);
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testWithdrawTo() public {
|
|
uint256 depositAmount = 10 ether;
|
|
uint256 withdrawAmount = 4 ether;
|
|
|
|
vm.startPrank(alice);
|
|
|
|
// Deposit from alice
|
|
wsnr.deposit{value: depositAmount}();
|
|
uint256 bobBalanceBefore = bob.balance;
|
|
|
|
// Test withdrawal event
|
|
vm.expectEmit(true, false, false, true);
|
|
emit Withdrawal(bob, withdrawAmount);
|
|
|
|
// Withdraw to bob
|
|
wsnr.withdrawTo(bob, withdrawAmount);
|
|
|
|
// Check balances
|
|
assertEq(wsnr.balanceOf(alice), depositAmount - withdrawAmount);
|
|
assertEq(bob.balance, bobBalanceBefore + withdrawAmount);
|
|
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testWithdrawToZeroAddress() public {
|
|
vm.startPrank(alice);
|
|
wsnr.deposit{value: 1 ether}();
|
|
|
|
vm.expectRevert("WSNR: cannot withdraw to zero address");
|
|
wsnr.withdrawTo(address(0), 1 ether);
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testERC20Transfer() public {
|
|
uint256 depositAmount = 10 ether;
|
|
uint256 transferAmount = 3 ether;
|
|
|
|
vm.startPrank(alice);
|
|
wsnr.deposit{value: depositAmount}();
|
|
|
|
// Test transfer event
|
|
vm.expectEmit(true, true, false, true);
|
|
emit Transfer(alice, bob, transferAmount);
|
|
|
|
// Transfer WSNR tokens
|
|
assertTrue(wsnr.transfer(bob, transferAmount));
|
|
|
|
// Check balances
|
|
assertEq(wsnr.balanceOf(alice), depositAmount - transferAmount);
|
|
assertEq(wsnr.balanceOf(bob), transferAmount);
|
|
assertEq(wsnr.totalSupply(), depositAmount); // Total supply unchanged
|
|
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testERC20Approve() public {
|
|
uint256 depositAmount = 10 ether;
|
|
uint256 approveAmount = 5 ether;
|
|
|
|
vm.startPrank(alice);
|
|
wsnr.deposit{value: depositAmount}();
|
|
|
|
// Test approval event
|
|
vm.expectEmit(true, true, false, true);
|
|
emit Approval(alice, bob, approveAmount);
|
|
|
|
// Approve bob to spend alice's WSNR
|
|
assertTrue(wsnr.approve(bob, approveAmount));
|
|
assertEq(wsnr.allowance(alice, bob), approveAmount);
|
|
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testERC20TransferFrom() public {
|
|
uint256 depositAmount = 10 ether;
|
|
uint256 approveAmount = 6 ether;
|
|
uint256 transferAmount = 4 ether;
|
|
|
|
// Alice deposits and approves bob
|
|
vm.startPrank(alice);
|
|
wsnr.deposit{value: depositAmount}();
|
|
wsnr.approve(bob, approveAmount);
|
|
vm.stopPrank();
|
|
|
|
// Bob transfers from alice to charlie
|
|
vm.startPrank(bob);
|
|
|
|
// Test transfer event
|
|
vm.expectEmit(true, true, false, true);
|
|
emit Transfer(alice, charlie, transferAmount);
|
|
|
|
assertTrue(wsnr.transferFrom(alice, charlie, transferAmount));
|
|
vm.stopPrank();
|
|
|
|
// Check balances and allowance
|
|
assertEq(wsnr.balanceOf(alice), depositAmount - transferAmount);
|
|
assertEq(wsnr.balanceOf(charlie), transferAmount);
|
|
assertEq(wsnr.allowance(alice, bob), approveAmount - transferAmount);
|
|
}
|
|
|
|
function testMultipleUsersDepositWithdraw() public {
|
|
// Multiple users deposit
|
|
vm.prank(alice);
|
|
wsnr.deposit{value: 5 ether}();
|
|
|
|
vm.prank(bob);
|
|
wsnr.deposit{value: 3 ether}();
|
|
|
|
vm.prank(charlie);
|
|
wsnr.deposit{value: 2 ether}();
|
|
|
|
// Check total supply and reserves
|
|
assertEq(wsnr.totalSupply(), 10 ether);
|
|
assertEq(wsnr.getReserve(), 10 ether);
|
|
assertTrue(wsnr.isFullyCollateralized());
|
|
|
|
// Users withdraw
|
|
vm.prank(alice);
|
|
wsnr.withdraw(2 ether);
|
|
|
|
vm.prank(bob);
|
|
wsnr.withdraw(1 ether);
|
|
|
|
// Check final state
|
|
assertEq(wsnr.totalSupply(), 7 ether);
|
|
assertEq(wsnr.getReserve(), 7 ether);
|
|
assertEq(wsnr.balanceOf(alice), 3 ether);
|
|
assertEq(wsnr.balanceOf(bob), 2 ether);
|
|
assertEq(wsnr.balanceOf(charlie), 2 ether);
|
|
assertTrue(wsnr.isFullyCollateralized());
|
|
}
|
|
|
|
// Fuzz testing
|
|
function testFuzzDeposit(uint256 amount) public {
|
|
vm.assume(amount > 0 && amount <= 100 ether);
|
|
|
|
vm.deal(alice, amount);
|
|
vm.prank(alice);
|
|
wsnr.deposit{value: amount}();
|
|
|
|
assertEq(wsnr.balanceOf(alice), amount);
|
|
assertEq(wsnr.totalSupply(), amount);
|
|
assertEq(address(wsnr).balance, amount);
|
|
}
|
|
|
|
function testFuzzWithdraw(uint256 depositAmount, uint256 withdrawAmount) public {
|
|
vm.assume(depositAmount > 0 && depositAmount <= 100 ether);
|
|
vm.assume(withdrawAmount > 0 && withdrawAmount <= depositAmount);
|
|
|
|
vm.deal(alice, depositAmount);
|
|
vm.startPrank(alice);
|
|
|
|
wsnr.deposit{value: depositAmount}();
|
|
wsnr.withdraw(withdrawAmount);
|
|
|
|
assertEq(wsnr.balanceOf(alice), depositAmount - withdrawAmount);
|
|
assertEq(address(wsnr).balance, depositAmount - withdrawAmount);
|
|
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function testReentrancyProtection() public {
|
|
ReentrantAttacker attacker = new ReentrantAttacker(wsnr);
|
|
vm.deal(address(attacker), 10 ether);
|
|
|
|
// The reentrancy guard prevents the second withdraw, which causes
|
|
// the ETH transfer to fail, resulting in "SNR transfer failed" error
|
|
vm.expectRevert("WSNR: SNR transfer failed");
|
|
attacker.attack{value: 2 ether}();
|
|
}
|
|
}
|
|
|
|
// Reentrancy test helper contract
|
|
contract ReentrantAttacker {
|
|
WSNR public wsnr;
|
|
uint256 public attackCount;
|
|
|
|
constructor(WSNR _wsnr) {
|
|
wsnr = _wsnr;
|
|
}
|
|
|
|
receive() external payable {
|
|
attackCount++;
|
|
if (attackCount < 2 && address(wsnr).balance >= 1 ether) {
|
|
wsnr.withdraw(1 ether);
|
|
}
|
|
}
|
|
|
|
function attack() external payable {
|
|
wsnr.deposit{value: msg.value}();
|
|
wsnr.withdraw(1 ether);
|
|
}
|
|
} |