Skip to content

Commit

Permalink
Merge pull request #8 from markdavid000/main
Browse files Browse the repository at this point in the history
Record Transaction History Function Implementation and Testing
  • Loading branch information
Dprof-in-tech authored Dec 16, 2024
2 parents 886aaf1 + 7914e15 commit 9697f50
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 40 deletions.
32 changes: 0 additions & 32 deletions src/Transfer.sol

This file was deleted.

23 changes: 15 additions & 8 deletions src/Wallet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ contract Wallet {
IWorldID public worldID;
IERC20 public usdt;

mapping(address => Transaction[]) transactions;
mapping(address => Transaction[]) public transactions;
mapping(address => mapping(address => uint256)) token_balance;

struct Transaction {
Expand Down Expand Up @@ -39,18 +39,20 @@ contract Wallet {

}

function transfer(address recipient, uint256 amount) external onlyVerified(msg.sender) {

require(usdt.balanceOf(msg.sender) >= amount, "Insufficient balance");
require(amount > 0, "Transfer amount must be greater than zero");
require(usdt.transferFrom(msg.sender, recipient, amount), "Transfer failed");
function transfer(address _recipient, uint256 _amount) external onlyVerified(msg.sender) {
require(_recipient != address(0), "Zero address detected");
require(usdt.balanceOf(msg.sender) >= _amount, "Insufficient balance");
require(_amount > 0, "Transfer amount must be greater than zero");
require(usdt.transferFrom(msg.sender, _recipient, _amount), "Transfer failed");

recordTransactionHistory(msg.sender, _amount, address(usdt));
}

//////////////////////////////////////////////
// View Functions //
////////////////////////////////////////////


function getTransactionHistory(address _user) external view returns (Transaction[] memory) {
return transactions[_user];
}
Expand All @@ -59,7 +61,12 @@ contract Wallet {
// Private Function //
//////////////////////////////////////////////

function recordTransactionHistory() private {

function recordTransactionHistory(address _user, uint256 _amount, address _token) private {
Transaction memory newTransaction = Transaction({
amount: _amount,
token: _token
});

transactions[_user].push(newTransaction);
}
}
115 changes: 115 additions & 0 deletions test/Wallet.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "forge-std/console.sol";
import "../src/Wallet.sol";
import "../src/WalletFactory.sol";
import "./mocks/MockERC20.sol";
import "./mocks/MockWorldID.sol";

/// @title Wallet Contract Test
/// @notice This contract contains unit tests for the recordTransactionHistory function in the Wallet contract
contract WalletTest is Test {
Wallet public wallet;
WalletFactory public factory;
address public owner;
address public user1;
address public user2;
MockERC20 public usdt;
MockWorldID public worldID;

/// @notice Set up the test environment before each test
function setUp() public {
owner = address(this);
user1 = address(0x1);
user2 = address(0x2);
usdt = new MockERC20("USDT", "USDT");
worldID = new MockWorldID();

factory = new WalletFactory();
(wallet, ) = factory.createWallet(address(worldID), address(usdt));

// Fund users
usdt.mint(user1, 1000);
usdt.mint(user2, 1000);

// Approve wallet for transfers
vm.prank(user1);
usdt.approve(address(wallet), type(uint256).max);
vm.prank(user2);
usdt.approve(address(wallet), type(uint256).max);

// Set users as verified in MockWorldID
worldID.setVerified(user1, true);
worldID.setVerified(user2, true);
}

/// @notice Test recording a single transaction
function testRecordSingleTransaction() public {
vm.prank(user1);
wallet.transfer(user2, 100);

vm.prank(user1);
Wallet.Transaction[] memory history = wallet.getTransactionHistory();
assertEq(history.length, 1);
assertEq(history[0].amount, 100);
assertEq(history[0].token, address(usdt));
}

/// @notice Test recording multiple transactions
function testRecordMultipleTransactions() public {
vm.startPrank(user1);
wallet.transfer(user2, 100);
wallet.transfer(user2, 200);

Wallet.Transaction[] memory history = wallet.getTransactionHistory();
vm.stopPrank();

assertEq(history.length, 2);
assertEq(history[0].amount, 100);
assertEq(history[0].token, address(usdt));
assertEq(history[1].amount, 200);
assertEq(history[1].token, address(usdt));
}

/// @notice Test recording transactions for different users
function testRecordTransactionsForDifferentUsers() public {
vm.prank(user1);
wallet.transfer(user2, 100);

vm.prank(user1);
wallet.transfer(user2, 50);

vm.prank(user2);
wallet.transfer(user1, 50);

vm.prank(user1);
Wallet.Transaction[] memory user1History = wallet.getTransactionHistory();

vm.prank(user2);
Wallet.Transaction[] memory user2History = wallet.getTransactionHistory();

assertEq(user1History.length, 2);
assertEq(user1History[0].amount, 100);
assertEq(user2History.length, 1);
assertEq(user2History[0].amount, 50);
}

/// @notice Test recording a large amount transaction
function testRecordLargeAmountTransaction() public {
uint256 largeAmount = type(uint256).max / 2; // Use half of max to avoid overflow
usdt.mint(user1, largeAmount);

vm.startPrank(user1);
usdt.approve(address(wallet), largeAmount);
wallet.transfer(user2, largeAmount);

Wallet.Transaction[] memory history = wallet.getTransactionHistory();
vm.stopPrank();

assertEq(history.length, 1, "Transaction was not recorded");
assertEq(history[0].amount, largeAmount, "Recorded amount does not match");
assertEq(history[0].token, address(usdt), "Recorded token address does not match");
}
}
106 changes: 106 additions & 0 deletions test/mocks/MockERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "../../src/interfaces/IERC20.sol";

contract MockERC20 is IERC20 {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;

constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}

function name() public view returns (string memory) {
return _name;
}

function symbol() public view returns (string memory) {
return _symbol;
}

function decimals() public pure returns (uint8) {
return 18;
}

function totalSupply() public view override returns (uint256) {
return _totalSupply;
}

function balanceOf(address account) public view override returns (uint256) {
return _balances[account];
}

function transfer(address to, uint256 amount) public override returns (bool) {
address owner = msg.sender;
_transfer(owner, to, amount);
return true;
}

function allowance(address owner, address spender) public view override returns (uint256) {
return _allowances[owner][spender];
}

function approve(address spender, uint256 amount) public override returns (bool) {
address owner = msg.sender;
_approve(owner, spender, amount);
return true;
}

function transferFrom(address from, address to, uint256 amount) public override returns (bool) {
address spender = msg.sender;
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}

function mint(address to, uint256 amount) public {
_mint(to, amount);
}

function _transfer(address from, address to, uint256 amount) internal {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");

uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
_balances[to] += amount;
}

emit Transfer(from, to, amount);
}

function _mint(address account, uint256 amount) internal {
require(account != address(0), "ERC20: mint to the zero address");

_totalSupply += amount;
unchecked {
_balances[account] += amount;
}
emit Transfer(address(0), account, amount);
}

function _approve(address owner, address spender, uint256 amount) internal {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");

_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}

function _spendAllowance(address owner, address spender, uint256 amount) internal {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
}
16 changes: 16 additions & 0 deletions test/mocks/MockWorldID.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "../../src/IWorldID.sol";

contract MockWorldID is IWorldID {
mapping(address => bool) private _verified;

function verifyIdentity(address user) external view override returns (bool) {
return _verified[user];
}

function setVerified(address user, bool status) external {
_verified[user] = status;
}
}

0 comments on commit 9697f50

Please sign in to comment.