154 lines
5.0 KiB
Solidity
154 lines
5.0 KiB
Solidity
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
pragma solidity ^0.8.19;
|
||
|
|
|
||
|
|
import "forge-std/Test.sol";
|
||
|
|
import "../../../contracts/bridge/trustless/Lockbox138.sol";
|
||
|
|
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||
|
|
|
||
|
|
contract MockERC20 is ERC20 {
|
||
|
|
constructor() ERC20("Mock Token", "MOCK") {
|
||
|
|
// Mint to deployer, but we'll mint to test users as needed
|
||
|
|
}
|
||
|
|
|
||
|
|
function mint(address to, uint256 amount) external {
|
||
|
|
_mint(to, amount);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
contract Lockbox138Test is Test {
|
||
|
|
Lockbox138 public lockbox;
|
||
|
|
MockERC20 public mockToken;
|
||
|
|
address public user = address(0x1);
|
||
|
|
address public recipient = address(0x2);
|
||
|
|
|
||
|
|
event Deposit(
|
||
|
|
uint256 indexed depositId,
|
||
|
|
address indexed asset,
|
||
|
|
uint256 amount,
|
||
|
|
address indexed recipient,
|
||
|
|
bytes32 nonce,
|
||
|
|
address depositor,
|
||
|
|
uint256 timestamp
|
||
|
|
);
|
||
|
|
|
||
|
|
function setUp() public {
|
||
|
|
lockbox = new Lockbox138();
|
||
|
|
mockToken = new MockERC20();
|
||
|
|
vm.deal(user, 100 ether);
|
||
|
|
}
|
||
|
|
|
||
|
|
function testDepositNative() public {
|
||
|
|
uint256 amount = 1 ether;
|
||
|
|
bytes32 nonce = keccak256("test-nonce");
|
||
|
|
|
||
|
|
vm.prank(user);
|
||
|
|
uint256 depositId = lockbox.depositNative{value: amount}(recipient, nonce);
|
||
|
|
|
||
|
|
assertGt(depositId, 0);
|
||
|
|
assertEq(address(lockbox).balance, amount);
|
||
|
|
assertTrue(lockbox.isDepositProcessed(depositId));
|
||
|
|
assertEq(lockbox.getNonce(user), 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
function testDepositERC20() public {
|
||
|
|
uint256 amount = 100 ether;
|
||
|
|
bytes32 nonce = keccak256("test-nonce-erc20");
|
||
|
|
|
||
|
|
// Mint tokens to user first
|
||
|
|
mockToken.mint(user, amount);
|
||
|
|
|
||
|
|
vm.startPrank(user);
|
||
|
|
mockToken.approve(address(lockbox), amount);
|
||
|
|
uint256 depositId = lockbox.depositERC20(address(mockToken), amount, recipient, nonce);
|
||
|
|
vm.stopPrank();
|
||
|
|
|
||
|
|
assertGt(depositId, 0);
|
||
|
|
assertEq(mockToken.balanceOf(address(lockbox)), amount);
|
||
|
|
assertTrue(lockbox.isDepositProcessed(depositId));
|
||
|
|
assertEq(lockbox.getNonce(user), 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
function testDepositNativeEmitsEvent() public {
|
||
|
|
uint256 amount = 1 ether;
|
||
|
|
bytes32 nonce = keccak256("test-nonce");
|
||
|
|
|
||
|
|
vm.prank(user);
|
||
|
|
// Check only non-indexed data fields (amount, nonce, depositor, timestamp)
|
||
|
|
// Skip indexed fields (depositId, asset, recipient) since depositId is unpredictable
|
||
|
|
vm.expectEmit(false, false, false, true); // Only check data, skip all topics
|
||
|
|
emit Deposit(
|
||
|
|
uint256(0), // Ignored (topic)
|
||
|
|
address(0), // Ignored (topic)
|
||
|
|
amount, // Checked (data)
|
||
|
|
recipient, // Ignored (topic)
|
||
|
|
nonce, // Checked (data)
|
||
|
|
user, // Checked (data)
|
||
|
|
block.timestamp // Checked (data)
|
||
|
|
);
|
||
|
|
lockbox.depositNative{value: amount}(recipient, nonce);
|
||
|
|
}
|
||
|
|
|
||
|
|
function testDepositNativeZeroAmount() public {
|
||
|
|
bytes32 nonce = keccak256("test-nonce");
|
||
|
|
|
||
|
|
vm.prank(user);
|
||
|
|
vm.expectRevert(Lockbox138.ZeroAmount.selector);
|
||
|
|
lockbox.depositNative{value: 0}(recipient, nonce);
|
||
|
|
}
|
||
|
|
|
||
|
|
function testDepositNativeZeroRecipient() public {
|
||
|
|
uint256 amount = 1 ether;
|
||
|
|
bytes32 nonce = keccak256("test-nonce");
|
||
|
|
|
||
|
|
vm.prank(user);
|
||
|
|
vm.expectRevert(Lockbox138.ZeroRecipient.selector);
|
||
|
|
lockbox.depositNative{value: amount}(address(0), nonce);
|
||
|
|
}
|
||
|
|
|
||
|
|
function testDepositERC20ZeroAmount() public {
|
||
|
|
bytes32 nonce = keccak256("test-nonce");
|
||
|
|
|
||
|
|
vm.startPrank(user);
|
||
|
|
vm.expectRevert(Lockbox138.ZeroAmount.selector);
|
||
|
|
lockbox.depositERC20(address(mockToken), 0, recipient, nonce);
|
||
|
|
vm.stopPrank();
|
||
|
|
}
|
||
|
|
|
||
|
|
function testDepositERC20ZeroAsset() public {
|
||
|
|
uint256 amount = 100 ether;
|
||
|
|
bytes32 nonce = keccak256("test-nonce");
|
||
|
|
|
||
|
|
vm.prank(user);
|
||
|
|
vm.expectRevert(Lockbox138.ZeroAsset.selector);
|
||
|
|
lockbox.depositERC20(address(0), amount, recipient, nonce);
|
||
|
|
}
|
||
|
|
|
||
|
|
function testDepositNativeReplayProtection() public {
|
||
|
|
uint256 amount = 1 ether;
|
||
|
|
bytes32 nonce = keccak256("test-nonce");
|
||
|
|
|
||
|
|
vm.startPrank(user);
|
||
|
|
uint256 depositId = lockbox.depositNative{value: amount}(recipient, nonce);
|
||
|
|
|
||
|
|
// Try to deposit again with same parameters (should fail due to nonce increment)
|
||
|
|
// Actually, the depositId is different each time due to timestamp/block.number
|
||
|
|
// But the nonce increments, so this is more of a user-side protection
|
||
|
|
vm.stopPrank();
|
||
|
|
|
||
|
|
assertTrue(lockbox.isDepositProcessed(depositId));
|
||
|
|
}
|
||
|
|
|
||
|
|
function testMultipleDepositsIncrementNonce() public {
|
||
|
|
bytes32 nonce1 = keccak256("nonce-1");
|
||
|
|
bytes32 nonce2 = keccak256("nonce-2");
|
||
|
|
|
||
|
|
vm.startPrank(user);
|
||
|
|
lockbox.depositNative{value: 1 ether}(recipient, nonce1);
|
||
|
|
assertEq(lockbox.getNonce(user), 1);
|
||
|
|
|
||
|
|
lockbox.depositNative{value: 1 ether}(recipient, nonce2);
|
||
|
|
assertEq(lockbox.getNonce(user), 2);
|
||
|
|
vm.stopPrank();
|
||
|
|
}
|
||
|
|
}
|