Skip to content
Merged
Show file tree
Hide file tree
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
122 changes: 122 additions & 0 deletions test/token/ERC20/ERC20Bridgeble/ERC20BridgeableFacet.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30;

import {Test} from "forge-std/Test.sol";
import {ERC20BridgeableFacet} from "../../../../src/token/ERC20/ERC20Bridgeable/ERC20BridgeableFacet.sol";
import {ERC20BridgeableHarness} from "./harnesses/ERC20BridgeableHarness.sol";

contract ERC20BridgeableFacetTest is Test {
ERC20BridgeableHarness public token;

address public alice;
address public bob;

uint256 constant INITIAL_SUPPLY = 1000000e18;

function setUp() public {
alice = makeAddr("alice");
bob = makeAddr("bob");

token = new ERC20BridgeableHarness();
token.setRole(alice, "trusted-bridge", true);
vm.prank(alice);
token.crosschainMint(alice, INITIAL_SUPPLY);
}

// ======================================
// CrossChainMint Tests
// ======================================

function test_CrossChainMintRevertsInvalidCaller(address to, uint256 amount, address invalidCaller) public {
vm.assume(to != address(0));
vm.assume(amount > 0 && amount < INITIAL_SUPPLY);
vm.assume(invalidCaller != alice);
vm.prank(invalidCaller);
vm.expectRevert(
abi.encodeWithSelector(
ERC20BridgeableFacet.AccessControlUnauthorizedAccount.selector, invalidCaller, bytes32("trusted-bridge")
)
);
token.crosschainMint(to, amount);
}

function test_CrossChainMintRevertsInvalidReceiver(uint256 amount) public {
address to = address(0);
vm.assume(amount > 0 && amount < INITIAL_SUPPLY);
vm.expectRevert(abi.encodeWithSelector(ERC20BridgeableFacet.ERC20InvalidReciever.selector, to));
vm.prank(alice);
token.crosschainMint(to, amount);
}

function test_CrossChainMint() public {
vm.prank(alice);
token.crosschainMint(bob, 500e18);
assertEq(token.balanceOf(bob), 500e18);
}

// ======================================
// CrossChainBurn Tests
// ======================================

function test_CrossChainBurnRevertsInvalidCaller(address from, uint256 amount, address invalidCaller) public {
vm.assume(from != address(0));
vm.assume(amount > 0 && amount < INITIAL_SUPPLY);
vm.assume(invalidCaller != alice);
vm.prank(alice);
token.crosschainMint(from, amount);
vm.prank(invalidCaller);
vm.expectRevert(
abi.encodeWithSelector(
ERC20BridgeableFacet.AccessControlUnauthorizedAccount.selector, invalidCaller, bytes32("trusted-bridge")
)
);
token.crosschainBurn(from, amount);
}

function test_CrossChainBurnRevertsInvalidFrom(uint256 amount) public {
address from = address(0);
vm.assume(amount > 0 && amount < INITIAL_SUPPLY);
vm.prank(alice);
vm.expectRevert(abi.encodeWithSelector(ERC20BridgeableFacet.ERC20InvalidReciever.selector, from));
token.crosschainBurn(from, amount);
}

function test_CrossChainBurn() public {
vm.prank(alice);
token.crosschainMint(bob, 500e18);
assertEq(token.balanceOf(bob), 500e18);
vm.prank(alice);
token.crosschainBurn(bob, 500e18);
assertEq(token.balanceOf(bob), 0);
}

// ======================================
// checkTokenBridge Tests
// ======================================

function test_CheckTokenBridgeSucceeds(address caller) public {
vm.prank(alice);
token.checkTokenBridge(alice);
}

function test_CheckTokenBridgeReverts(address invalidCaller) public {
vm.assume(invalidCaller != alice);
vm.prank(invalidCaller);
vm.expectRevert(abi.encodeWithSelector(ERC20BridgeableFacet.ERC20InvalidBridgeAccount.selector, invalidCaller));
token.checkTokenBridge(invalidCaller);
}

function test_CheckTokenBridgeRevertsZeroAddress() public {
address invalidCaller = address(0);
vm.prank(invalidCaller);
vm.expectRevert(abi.encodeWithSelector(ERC20BridgeableFacet.ERC20InvalidBridgeAccount.selector, invalidCaller));
token.checkTokenBridge(invalidCaller);
}

function test_CheckTokenBridgeSucceedsAfterRevokingRole() public {
token.setRole(alice, "trusted-bridge", false);
vm.prank(alice);
vm.expectRevert(abi.encodeWithSelector(ERC20BridgeableFacet.ERC20InvalidBridgeAccount.selector, alice));
token.checkTokenBridge(alice);
}
}
117 changes: 117 additions & 0 deletions test/token/ERC20/ERC20Bridgeble/LibERC20Bridgeable.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30;

import {Test} from "forge-std/Test.sol";
import {LibERC20Bridgeable} from "../../../../src/token/ERC20/ERC20Bridgeable/LibERC20Bridgeable.sol";
import {LibERC20BridgeableHarness} from "./harnesses/LibERC20BridgeableHarness.sol";

contract LibERC20BridgeableTest is Test {
LibERC20BridgeableHarness public token;

address public alice;
address public bob;

uint256 constant INITIAL_SUPPLY = 1000000e18;

function setUp() public {
alice = makeAddr("alice");
bob = makeAddr("bob");

token = new LibERC20BridgeableHarness();
token.setRole(alice, "trusted-bridge", true);
vm.prank(alice);
token.crosschainMint(alice, INITIAL_SUPPLY);
}

// ======================================
// CrossChainMint Tests
// ======================================

function test_CrossChainMintRevertsInvalidCaller(address to, uint256 amount, address invalidCaller) public {
vm.assume(to != address(0));
vm.assume(amount > 0 && amount < INITIAL_SUPPLY);
vm.assume(invalidCaller != alice);
vm.prank(invalidCaller);
vm.expectRevert(
abi.encodeWithSelector(
LibERC20Bridgeable.AccessControlUnauthorizedAccount.selector, invalidCaller, bytes32("trusted-bridge")
)
);
token.crosschainMint(to, amount);
}

function test_CrossChainMintRevertsInvalidReceiver(uint256 amount) public {
address to = address(0);
vm.assume(amount > 0 && amount < INITIAL_SUPPLY);
vm.expectRevert(abi.encodeWithSelector(LibERC20Bridgeable.ERC20InvalidReciever.selector, to));
vm.prank(alice);
token.crosschainMint(to, amount);
}

function test_CrossChainMint() public {
vm.prank(alice);
token.crosschainMint(bob, 500e18);
assertEq(token.balanceOf(bob), 500e18);
}

// ======================================
// CrossChainBurn Tests
// ======================================

function test_CrossChainBurnRevertsInvalidCaller(address from, uint256 amount, address invalidCaller) public {
vm.assume(from != address(0));
vm.assume(amount > 0 && amount < INITIAL_SUPPLY);
vm.assume(invalidCaller != alice);
vm.prank(alice);
token.crosschainMint(from, amount);
vm.prank(invalidCaller);
vm.expectRevert(
abi.encodeWithSelector(
LibERC20Bridgeable.AccessControlUnauthorizedAccount.selector, invalidCaller, bytes32("trusted-bridge")
)
);
token.crosschainBurn(from, amount);
}

function test_CrossChainBurnRevertsInvalidFrom(uint256 amount) public {
address from = address(0);
vm.assume(amount > 0 && amount < INITIAL_SUPPLY);
vm.prank(alice);
vm.expectRevert(abi.encodeWithSelector(LibERC20Bridgeable.ERC20InvalidReciever.selector, from));
token.crosschainBurn(from, amount);
}

function test_CrossChainBurn() public {
vm.prank(alice);
token.crosschainMint(bob, 500e18);
assertEq(token.balanceOf(bob), 500e18);

vm.prank(alice);
token.crosschainBurn(bob, 200e18);
assertEq(token.balanceOf(bob), 300e18);
}

// ======================================
// checkTokenBridge Tests
// ======================================

function test_CheckTokenBridgeSucceedsValidCaller() public {
vm.prank(alice);
token.checkTokenBridge(alice);
}

function test_CheckTokenBridgeRevertsInvalidCaller(address invalidCaller) public {
vm.assume(invalidCaller != alice);
vm.assume(invalidCaller != address(0));
vm.prank(invalidCaller);
vm.expectRevert(abi.encodeWithSelector(LibERC20Bridgeable.ERC20InvalidBridgeAccount.selector, invalidCaller));
token.checkTokenBridge(invalidCaller);
}

function test_CheckTokenBridgeRevertsZeroCaller() public {
address zeroAddress = address(0);
vm.prank(zeroAddress);
vm.expectRevert(abi.encodeWithSelector(LibERC20Bridgeable.ERC20InvalidBridgeAccount.selector, zeroAddress));
token.checkTokenBridge(zeroAddress);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30;

import {ERC20BridgeableFacet} from "../../../../../src/token/ERC20/ERC20Bridgeable/ERC20BridgeableFacet.sol";

contract ERC20BridgeableHarness is ERC20BridgeableFacet {
/**
* @notice Sets or unsets a specific role for an account.
* @param account The address of the account to modify.
* @param role The bytes32 identifier of the role.
* @param value Set to true to grant the role, or false to revoke.
*/
function setRole(address account, bytes32 role, bool value) external {
ERC20BridgeableFacet.AccessControlStorage storage acs = ERC20BridgeableFacet.getAccessControlStorage();
acs.hasRole[account][role] = value;
}

/**
* @notice Gets the balance of a specified account.
* @param _account The address of the account to query.
* @return The balance of the specified account.
*/
function balanceOf(address _account) external view returns (uint256) {
ERC20BridgeableFacet.ERC20Storage storage s = ERC20BridgeableFacet.getERC20Storage();
return s.balanceOf[_account];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30;

import {LibERC20Bridgeable} from "../../../../../src/token/ERC20/ERC20Bridgeable/LibERC20Bridgeable.sol";

contract LibERC20BridgeableHarness {
/// @notice Mints tokens to a specified address on behalf of the "trusted-bridge" role.
/// @param _to The address receiving the minted tokens. [NATPSEC: Only trusted bridge callers]
/// @param _amount The amount of tokens to mint. [NATPSEC: Only trusted bridge callers]
function crosschainMint(address _to, uint256 _amount) external {
LibERC20Bridgeable.crosschainMint(_to, _amount);
}

/// @notice Returns the token balance of a specified account.
/// @param _account The account for which to query the balance. [NATPSEC: Read-only]
/// @return The current balance of the account.
function balanceOf(address _account) external view returns (uint256) {
LibERC20Bridgeable.ERC20Storage storage s = LibERC20Bridgeable.getERC20Storage();
return s.balanceOf[_account];
}

/// @notice Burns tokens from a specified address on behalf of the "trusted-bridge" role.
/// @param _from The address whose tokens will be burned. [NATPSEC: Only trusted bridge callers]
/// @param _amount The amount of tokens to burn. [NATPSEC: Only trusted bridge callers]
function crosschainBurn(address _from, uint256 _amount) external {
LibERC20Bridgeable.crosschainBurn(_from, _amount);
}

/// @notice Validates whether the caller has the token bridge role.
/// @param _caller The address to check for the "trusted-bridge" role. [NATPSEC: Internal access control]
function checkTokenBridge(address _caller) external view {
LibERC20Bridgeable.checkTokenBridge(_caller);
}

/// @notice Grants or revokes a role for a given account, for harness/testing only.
/// @param account The account to grant or revoke the role for. [NATPSEC: Test utility only]
/// @param role The bytes32 identifier of the role. [NATPSEC: Test utility only]
/// @param value True to grant the role, false to revoke. [NATPSEC: Test utility only]
function setRole(address account, bytes32 role, bool value) external {
LibERC20Bridgeable.AccessControlStorage storage acs = LibERC20Bridgeable.getAccessControlStorage();
acs.hasRole[account][role] = value;
}
}