Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 184 additions & 0 deletions contracts/test/Chainvoice.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,42 @@ import {Test} from "forge-std/Test.sol";
import {console} from "forge-std/console.sol";
import "../src/Chainvoice.sol";

contract MockERC20 {
string public name = "Mock ERC20";
string public symbol = "MOCK";
uint8 public decimals = 18;

mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
uint256 public totalSupply;

event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);

function mint(address to, uint256 amount) public {
balanceOf[to] += amount;
totalSupply += amount;
emit Transfer(address(0), to, amount);
}

function approve(address spender, uint256 amount) public returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}

function transferFrom(address from, address to, uint256 amount) public returns (bool) {
if (allowance[from][msg.sender] < amount) return false;
if (balanceOf[from] < amount) return false;

allowance[from][msg.sender] -= amount;
balanceOf[from] -= amount;
balanceOf[to] += amount;
emit Transfer(from, to, amount);
return true;
}
}

contract ChainvoiceTest is Test {
Chainvoice chainvoice;

Expand Down Expand Up @@ -452,4 +488,152 @@ contract ChainvoiceTest is Test {
vm.expectRevert(Chainvoice.InvalidInvoiceHash.selector);
chainvoice.createInvoicesBatch(tos, amounts, address(0), hashes);
}

/* ------------------------------------------------------------ */
/* ERC20 OPERATIONS */
/* ------------------------------------------------------------ */

function testCreateInvoice_ERC20() public {
MockERC20 token = new MockERC20();

vm.prank(alice);
chainvoice.createInvoice(
bob,
100 * 10**18,
address(token),
keccak256("erc20Data")
);

Chainvoice.InvoiceDetails memory inv = chainvoice.getInvoice(0);
assertEq(inv.from, alice);
assertEq(inv.to, bob);
assertEq(inv.amountDue, 100 * 10**18);
assertEq(inv.tokenAddress, address(token));
assertFalse(inv.isPaid);
}

function testPayInvoice_ERC20() public {
MockERC20 token = new MockERC20();
token.mint(bob, 100 * 10**18);

vm.prank(alice);
chainvoice.createInvoice(bob, 100 * 10**18, address(token), keccak256("erc20Data"));

uint256 fee = chainvoice.fee();

vm.startPrank(bob);
token.approve(address(chainvoice), 100 * 10**18);
chainvoice.payInvoice{value: fee}(0);
vm.stopPrank();

Chainvoice.InvoiceDetails memory inv = chainvoice.getInvoice(0);
assertTrue(inv.isPaid);
assertEq(token.balanceOf(bob), 0);
assertEq(token.balanceOf(alice), 100 * 10**18);
assertEq(chainvoice.accumulatedFees(), fee);
}

function testPayInvoice_ERC20_RevertInsufficientBalance() public {
MockERC20 token = new MockERC20();
token.mint(bob, 50 * 10**18); // Insufficient balance

vm.prank(alice);
chainvoice.createInvoice(bob, 100 * 10**18, address(token), keccak256("erc20Data"));

uint256 fee = chainvoice.fee();

vm.startPrank(bob);
token.approve(address(chainvoice), 100 * 10**18); // Sufficient allowance
vm.expectRevert(Chainvoice.TokenTransferFailed.selector);
chainvoice.payInvoice{value: fee}(0);
vm.stopPrank();

Chainvoice.InvoiceDetails memory inv = chainvoice.getInvoice(0);
assertFalse(inv.isPaid);
assertEq(chainvoice.accumulatedFees(), 0);
}

function testPayInvoice_ERC20_RevertInsufficientAllowance() public {
MockERC20 token = new MockERC20();
token.mint(bob, 100 * 10**18);

vm.prank(alice);
chainvoice.createInvoice(bob, 100 * 10**18, address(token), keccak256("erc20Data"));

uint256 fee = chainvoice.fee();

vm.prank(bob);
vm.expectRevert(Chainvoice.InsufficientAllowance.selector);
chainvoice.payInvoice{value: fee}(0);
}

function testPayInvoice_ERC20_RevertFeeMustBeNative() public {
MockERC20 token = new MockERC20();
token.mint(bob, 100 * 10**18);

vm.prank(alice);
chainvoice.createInvoice(bob, 100 * 10**18, address(token), keccak256("erc20Data"));

vm.startPrank(bob);
token.approve(address(chainvoice), 100 * 10**18);
vm.expectRevert(Chainvoice.FeeMustBeNative.selector);
chainvoice.payInvoice(0); // value: 0
vm.stopPrank();
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

function testPayInvoicesBatch_ERC20() public {
MockERC20 token = new MockERC20();
token.mint(bob, 300 * 10**18);

vm.startPrank(alice);
chainvoice.createInvoice(bob, 100 * 10**18, address(token), keccak256("b1"));
chainvoice.createInvoice(bob, 200 * 10**18, address(token), keccak256("b2"));
vm.stopPrank();

uint256 fee = chainvoice.fee();
uint256 totalFee = fee * 2;

uint256[] memory ids = new uint256[](2);
ids[0] = 0;
ids[1] = 1;

vm.startPrank(bob);
token.approve(address(chainvoice), 300 * 10**18);
chainvoice.payInvoicesBatch{value: totalFee}(ids);
vm.stopPrank();

assertTrue(chainvoice.getInvoice(0).isPaid);
assertTrue(chainvoice.getInvoice(1).isPaid);
assertEq(token.balanceOf(bob), 0);
assertEq(token.balanceOf(alice), 300 * 10**18);
assertEq(chainvoice.accumulatedFees(), totalFee);
}

function testPayInvoicesBatch_ERC20_RevertInsufficientBalance() public {
MockERC20 token = new MockERC20();
token.mint(bob, 150 * 10**18); // Insufficient for both (100 + 200 = 300)

vm.startPrank(alice);
chainvoice.createInvoice(bob, 100 * 10**18, address(token), keccak256("b1"));
chainvoice.createInvoice(bob, 200 * 10**18, address(token), keccak256("b2"));
vm.stopPrank();

uint256 fee = chainvoice.fee();
uint256 totalFee = fee * 2;

uint256[] memory ids = new uint256[](2);
ids[0] = 0;
ids[1] = 1;

vm.startPrank(bob);
token.approve(address(chainvoice), 300 * 10**18); // Sufficient allowance
vm.expectRevert(Chainvoice.TokenTransferFailed.selector);
chainvoice.payInvoicesBatch{value: totalFee}(ids);
vm.stopPrank();

assertFalse(chainvoice.getInvoice(0).isPaid);
assertFalse(chainvoice.getInvoice(1).isPaid);
assertEq(chainvoice.accumulatedFees(), 0);
}
}