Complete archive manual merge follow-ups and secure relay env handling
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m4s
CI/CD Pipeline / Security Scanning (push) Successful in 2m13s
CI/CD Pipeline / Lint and Format (push) Failing after 34s
CI/CD Pipeline / Terraform Validation (push) Failing after 21s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 22s
Validation / validate-genesis (push) Successful in 26s
Validation / validate-terraform (push) Failing after 24s
Validation / validate-kubernetes (push) Failing after 8s
Validation / validate-smart-contracts (push) Failing after 8s
Validation / validate-security (push) Failing after 1m10s
Validation / validate-documentation (push) Failing after 15s
Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m4s
CI/CD Pipeline / Security Scanning (push) Successful in 2m13s
CI/CD Pipeline / Lint and Format (push) Failing after 34s
CI/CD Pipeline / Terraform Validation (push) Failing after 21s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 22s
Validation / validate-genesis (push) Successful in 26s
Validation / validate-terraform (push) Failing after 24s
Validation / validate-kubernetes (push) Failing after 8s
Validation / validate-smart-contracts (push) Failing after 8s
Validation / validate-security (push) Failing after 1m10s
Validation / validate-documentation (push) Failing after 15s
Apply clean archive patches (WETH CREATE2 doc, DeployKeeper script), restore emoney unit tests from .bak, add keeper npm scripts without replacing the Hardhat package.json, untrack relay lane secret env files, and document superseded patch hunks in SIBLING_WIP_IMPORT.md.
This commit is contained in:
115
test/emoney/unit/AccountWalletRegistryTest.t.sol
Normal file
115
test/emoney/unit/AccountWalletRegistryTest.t.sol
Normal file
@@ -0,0 +1,115 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
import {AccountWalletRegistry} from "@emoney/AccountWalletRegistry.sol";
|
||||
import {IAccountWalletRegistry} from "@emoney/interfaces/IAccountWalletRegistry.sol";
|
||||
|
||||
contract AccountWalletRegistryTest is Test {
|
||||
AccountWalletRegistry public registry;
|
||||
address public admin;
|
||||
address public accountManager;
|
||||
|
||||
bytes32 public accountRefId1 = keccak256("account1");
|
||||
bytes32 public walletRefId1 = keccak256("wallet1");
|
||||
bytes32 public walletRefId2 = keccak256("wallet2");
|
||||
bytes32 public provider1 = keccak256("METAMASK");
|
||||
bytes32 public provider2 = keccak256("FIREBLOCKS");
|
||||
|
||||
function setUp() public {
|
||||
admin = address(0x1);
|
||||
accountManager = address(0x2);
|
||||
|
||||
registry = new AccountWalletRegistry(admin);
|
||||
|
||||
vm.startPrank(admin);
|
||||
registry.grantRole(registry.ACCOUNT_MANAGER_ROLE(), accountManager);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function test_linkAccountToWallet() public {
|
||||
vm.expectEmit(true, true, false, true);
|
||||
emit AccountWalletLinked(accountRefId1, walletRefId1, provider1, uint64(block.timestamp));
|
||||
|
||||
vm.prank(accountManager);
|
||||
registry.linkAccountToWallet(accountRefId1, walletRefId1, provider1);
|
||||
|
||||
assertTrue(registry.isLinked(accountRefId1, walletRefId1));
|
||||
assertTrue(registry.isActive(accountRefId1, walletRefId1));
|
||||
|
||||
IAccountWalletRegistry.WalletLink[] memory wallets = registry.getWallets(accountRefId1);
|
||||
assertEq(wallets.length, 1);
|
||||
assertEq(wallets[0].walletRefId, walletRefId1);
|
||||
assertEq(wallets[0].provider, provider1);
|
||||
assertTrue(wallets[0].active);
|
||||
}
|
||||
|
||||
function test_linkMultipleWallets() public {
|
||||
vm.prank(accountManager);
|
||||
registry.linkAccountToWallet(accountRefId1, walletRefId1, provider1);
|
||||
|
||||
vm.prank(accountManager);
|
||||
registry.linkAccountToWallet(accountRefId1, walletRefId2, provider2);
|
||||
|
||||
IAccountWalletRegistry.WalletLink[] memory wallets = registry.getWallets(accountRefId1);
|
||||
assertEq(wallets.length, 2);
|
||||
assertEq(wallets[0].walletRefId, walletRefId1);
|
||||
assertEq(wallets[1].walletRefId, walletRefId2);
|
||||
}
|
||||
|
||||
function test_unlinkAccountFromWallet() public {
|
||||
vm.prank(accountManager);
|
||||
registry.linkAccountToWallet(accountRefId1, walletRefId1, provider1);
|
||||
|
||||
assertTrue(registry.isActive(accountRefId1, walletRefId1));
|
||||
|
||||
vm.expectEmit(true, true, false, false);
|
||||
emit AccountWalletUnlinked(accountRefId1, walletRefId1);
|
||||
|
||||
vm.prank(accountManager);
|
||||
registry.unlinkAccountFromWallet(accountRefId1, walletRefId1);
|
||||
|
||||
assertTrue(registry.isLinked(accountRefId1, walletRefId1)); // Still linked
|
||||
assertFalse(registry.isActive(accountRefId1, walletRefId1)); // But inactive
|
||||
}
|
||||
|
||||
function test_getAccounts() public {
|
||||
bytes32 accountRefId2 = keccak256("account2");
|
||||
|
||||
vm.prank(accountManager);
|
||||
registry.linkAccountToWallet(accountRefId1, walletRefId1, provider1);
|
||||
|
||||
vm.prank(accountManager);
|
||||
registry.linkAccountToWallet(accountRefId2, walletRefId1, provider1);
|
||||
|
||||
bytes32[] memory accounts = registry.getAccounts(walletRefId1);
|
||||
assertEq(accounts.length, 2);
|
||||
}
|
||||
|
||||
function test_linkAccountToWallet_reactivate() public {
|
||||
vm.prank(accountManager);
|
||||
registry.linkAccountToWallet(accountRefId1, walletRefId1, provider1);
|
||||
|
||||
vm.prank(accountManager);
|
||||
registry.unlinkAccountFromWallet(accountRefId1, walletRefId1);
|
||||
|
||||
assertFalse(registry.isActive(accountRefId1, walletRefId1));
|
||||
|
||||
// Reactivate
|
||||
vm.prank(accountManager);
|
||||
registry.linkAccountToWallet(accountRefId1, walletRefId1, provider1);
|
||||
|
||||
assertTrue(registry.isActive(accountRefId1, walletRefId1));
|
||||
}
|
||||
|
||||
// Helper events for testing (match IAccountWalletRegistry events)
|
||||
event AccountWalletLinked(
|
||||
bytes32 indexed accountRefId,
|
||||
bytes32 indexed walletRefId,
|
||||
bytes32 provider,
|
||||
uint64 linkedAt
|
||||
);
|
||||
|
||||
event AccountWalletUnlinked(bytes32 indexed accountRefId, bytes32 indexed walletRefId);
|
||||
}
|
||||
|
||||
127
test/emoney/unit/ISO20022RouterTest.t.sol
Normal file
127
test/emoney/unit/ISO20022RouterTest.t.sol
Normal file
@@ -0,0 +1,127 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
import {ISO20022Router} from "@emoney/ISO20022Router.sol";
|
||||
import {IISO20022Router} from "@emoney/interfaces/IISO20022Router.sol";
|
||||
import {RailTriggerRegistry} from "@emoney/RailTriggerRegistry.sol";
|
||||
import {IRailTriggerRegistry} from "@emoney/interfaces/IRailTriggerRegistry.sol";
|
||||
import {RailTypes} from "@emoney/libraries/RailTypes.sol";
|
||||
import {ISO20022Types} from "@emoney/libraries/ISO20022Types.sol";
|
||||
|
||||
contract ISO20022RouterTest is Test {
|
||||
ISO20022Router public router;
|
||||
RailTriggerRegistry public triggerRegistry;
|
||||
address public admin;
|
||||
address public railOperator;
|
||||
address public token;
|
||||
|
||||
function setUp() public {
|
||||
admin = address(0x1);
|
||||
railOperator = address(0x2);
|
||||
token = address(0x100);
|
||||
|
||||
triggerRegistry = new RailTriggerRegistry(admin);
|
||||
router = new ISO20022Router(admin, address(triggerRegistry));
|
||||
|
||||
vm.startPrank(admin);
|
||||
triggerRegistry.grantRole(triggerRegistry.RAIL_OPERATOR_ROLE(), address(router));
|
||||
router.grantRole(router.RAIL_OPERATOR_ROLE(), railOperator);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function test_submitOutbound() public {
|
||||
IISO20022Router.CanonicalMessage memory m = IISO20022Router.CanonicalMessage({
|
||||
msgType: ISO20022Types.PAIN_001,
|
||||
instructionId: keccak256("instruction1"),
|
||||
endToEndId: keccak256("e2e1"),
|
||||
msgId: bytes32(0),
|
||||
uetr: bytes32(0),
|
||||
accountRefId: keccak256("account1"),
|
||||
counterpartyRefId: keccak256("counterparty1"),
|
||||
debtorId: bytes32(0),
|
||||
creditorId: bytes32(0),
|
||||
purpose: bytes32(0),
|
||||
settlementMethod: bytes32(0),
|
||||
categoryPurpose: bytes32(0),
|
||||
token: token,
|
||||
amount: 1000,
|
||||
currencyCode: keccak256("USD"),
|
||||
payloadHash: keccak256("payload1")
|
||||
});
|
||||
|
||||
vm.expectEmit(true, true, false, true);
|
||||
emit OutboundSubmitted(0, ISO20022Types.PAIN_001, keccak256("instruction1"), keccak256("account1"));
|
||||
|
||||
vm.prank(railOperator);
|
||||
uint256 triggerId = router.submitOutbound(m);
|
||||
|
||||
assertEq(triggerId, 0);
|
||||
IRailTriggerRegistry.Trigger memory trigger = triggerRegistry.getTrigger(triggerId);
|
||||
assertEq(trigger.instructionId, keccak256("instruction1"));
|
||||
assertEq(trigger.msgType, ISO20022Types.PAIN_001);
|
||||
}
|
||||
|
||||
function test_submitInbound() public {
|
||||
IISO20022Router.CanonicalMessage memory m = IISO20022Router.CanonicalMessage({
|
||||
msgType: ISO20022Types.CAMT_054,
|
||||
instructionId: keccak256("instruction2"),
|
||||
endToEndId: keccak256("e2e2"),
|
||||
msgId: bytes32(0),
|
||||
uetr: bytes32(0),
|
||||
accountRefId: keccak256("account2"),
|
||||
counterpartyRefId: keccak256("counterparty2"),
|
||||
debtorId: bytes32(0),
|
||||
creditorId: bytes32(0),
|
||||
purpose: bytes32(0),
|
||||
settlementMethod: bytes32(0),
|
||||
categoryPurpose: bytes32(0),
|
||||
token: token,
|
||||
amount: 2000,
|
||||
currencyCode: keccak256("EUR"),
|
||||
payloadHash: keccak256("payload2")
|
||||
});
|
||||
|
||||
vm.expectEmit(true, true, false, true);
|
||||
emit InboundSubmitted(0, ISO20022Types.CAMT_054, keccak256("instruction2"), keccak256("account2"));
|
||||
|
||||
vm.prank(railOperator);
|
||||
uint256 triggerId = router.submitInbound(m);
|
||||
|
||||
assertEq(triggerId, 0);
|
||||
IRailTriggerRegistry.Trigger memory trigger = triggerRegistry.getTrigger(triggerId);
|
||||
assertEq(trigger.instructionId, keccak256("instruction2"));
|
||||
assertEq(trigger.msgType, ISO20022Types.CAMT_054);
|
||||
}
|
||||
|
||||
function test_getTriggerIdByInstructionId() public {
|
||||
IISO20022Router.CanonicalMessage memory m = IISO20022Router.CanonicalMessage({
|
||||
msgType: ISO20022Types.PAIN_001,
|
||||
instructionId: keccak256("instruction3"),
|
||||
endToEndId: bytes32(0),
|
||||
msgId: bytes32(0),
|
||||
uetr: bytes32(0),
|
||||
accountRefId: keccak256("account3"),
|
||||
counterpartyRefId: bytes32(0),
|
||||
debtorId: bytes32(0),
|
||||
creditorId: bytes32(0),
|
||||
purpose: bytes32(0),
|
||||
settlementMethod: bytes32(0),
|
||||
categoryPurpose: bytes32(0),
|
||||
token: token,
|
||||
amount: 3000,
|
||||
currencyCode: keccak256("USD"),
|
||||
payloadHash: bytes32(0)
|
||||
});
|
||||
|
||||
vm.prank(railOperator);
|
||||
uint256 triggerId = router.submitOutbound(m);
|
||||
|
||||
assertEq(router.getTriggerIdByInstructionId(keccak256("instruction3")), triggerId);
|
||||
}
|
||||
|
||||
// Helper events for testing (match IISO20022Router events)
|
||||
event InboundSubmitted(uint256 indexed triggerId, bytes32 msgType, bytes32 instructionId, bytes32 accountRefId);
|
||||
event OutboundSubmitted(uint256 indexed triggerId, bytes32 msgType, bytes32 instructionId, bytes32 accountRefId);
|
||||
}
|
||||
|
||||
107
test/emoney/unit/RailEscrowVaultTest.t.sol
Normal file
107
test/emoney/unit/RailEscrowVaultTest.t.sol
Normal file
@@ -0,0 +1,107 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
import {RailEscrowVault} from "@emoney/RailEscrowVault.sol";
|
||||
import {IRailEscrowVault} from "@emoney/interfaces/IRailEscrowVault.sol";
|
||||
import {RailTypes} from "@emoney/libraries/RailTypes.sol";
|
||||
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
|
||||
contract MockERC20 is ERC20 {
|
||||
constructor() ERC20("Mock Token", "MOCK") {
|
||||
_mint(msg.sender, 1000000 * 10**18);
|
||||
}
|
||||
|
||||
function mint(address to, uint256 amount) external {
|
||||
_mint(to, amount);
|
||||
}
|
||||
}
|
||||
|
||||
contract RailEscrowVaultTest is Test {
|
||||
RailEscrowVault public vault;
|
||||
MockERC20 public token;
|
||||
address public admin;
|
||||
address public settlementOperator;
|
||||
address public user;
|
||||
|
||||
function setUp() public {
|
||||
admin = address(0x1);
|
||||
settlementOperator = address(0x2);
|
||||
user = address(0x10);
|
||||
|
||||
vault = new RailEscrowVault(admin);
|
||||
token = new MockERC20();
|
||||
|
||||
vm.startPrank(admin);
|
||||
vault.grantRole(vault.SETTLEMENT_OPERATOR_ROLE(), settlementOperator);
|
||||
vm.stopPrank();
|
||||
|
||||
// Give user some tokens
|
||||
token.mint(user, 10000 * 10**18);
|
||||
}
|
||||
|
||||
function test_lock() public {
|
||||
uint256 amount = 1000 * 10**18;
|
||||
uint256 triggerId = 1;
|
||||
|
||||
vm.startPrank(user);
|
||||
token.approve(address(vault), amount);
|
||||
vm.stopPrank();
|
||||
|
||||
vm.expectEmit(true, true, false, true);
|
||||
emit Locked(address(token), user, amount, triggerId, uint8(RailTypes.Rail.SWIFT));
|
||||
|
||||
vm.prank(settlementOperator);
|
||||
vault.lock(address(token), user, amount, triggerId, RailTypes.Rail.SWIFT);
|
||||
|
||||
assertEq(vault.getEscrowAmount(address(token), triggerId), amount);
|
||||
assertEq(vault.getTotalEscrow(address(token)), amount);
|
||||
assertEq(token.balanceOf(address(vault)), amount);
|
||||
}
|
||||
|
||||
function test_release() public {
|
||||
uint256 amount = 1000 * 10**18;
|
||||
uint256 triggerId = 1;
|
||||
address recipient = address(0x20);
|
||||
|
||||
vm.startPrank(user);
|
||||
token.approve(address(vault), amount);
|
||||
vm.stopPrank();
|
||||
|
||||
vm.prank(settlementOperator);
|
||||
vault.lock(address(token), user, amount, triggerId, RailTypes.Rail.SWIFT);
|
||||
|
||||
uint256 recipientBalanceBefore = token.balanceOf(recipient);
|
||||
|
||||
vm.expectEmit(true, true, false, true);
|
||||
emit Released(address(token), recipient, amount, triggerId);
|
||||
|
||||
vm.prank(settlementOperator);
|
||||
vault.release(address(token), recipient, amount, triggerId);
|
||||
|
||||
assertEq(vault.getEscrowAmount(address(token), triggerId), 0);
|
||||
assertEq(vault.getTotalEscrow(address(token)), 0);
|
||||
assertEq(token.balanceOf(recipient), recipientBalanceBefore + amount);
|
||||
}
|
||||
|
||||
function test_release_insufficientEscrow() public {
|
||||
uint256 amount = 1000 * 10**18;
|
||||
uint256 triggerId = 1;
|
||||
|
||||
vm.startPrank(user);
|
||||
token.approve(address(vault), amount);
|
||||
vm.stopPrank();
|
||||
|
||||
vm.prank(settlementOperator);
|
||||
vault.lock(address(token), user, amount, triggerId, RailTypes.Rail.SWIFT);
|
||||
|
||||
vm.prank(settlementOperator);
|
||||
vm.expectRevert("RailEscrowVault: insufficient escrow");
|
||||
vault.release(address(token), address(0x20), amount + 1, triggerId);
|
||||
}
|
||||
|
||||
// Helper events for testing (match IRailEscrowVault events)
|
||||
event Locked(address indexed token, address indexed account, uint256 amount, uint256 indexed triggerId, uint8 rail);
|
||||
event Released(address indexed token, address indexed recipient, uint256 amount, uint256 indexed triggerId);
|
||||
}
|
||||
|
||||
182
test/emoney/unit/RailTriggerRegistryTest.t.sol
Normal file
182
test/emoney/unit/RailTriggerRegistryTest.t.sol
Normal file
@@ -0,0 +1,182 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
import {RailTriggerRegistry} from "@emoney/RailTriggerRegistry.sol";
|
||||
import {IRailTriggerRegistry} from "@emoney/interfaces/IRailTriggerRegistry.sol";
|
||||
import {RailTypes} from "@emoney/libraries/RailTypes.sol";
|
||||
|
||||
contract RailTriggerRegistryTest is Test {
|
||||
RailTriggerRegistry public registry;
|
||||
address public admin;
|
||||
address public railOperator;
|
||||
address public railAdapter;
|
||||
address public token;
|
||||
|
||||
function setUp() public {
|
||||
admin = address(0x1);
|
||||
railOperator = address(0x2);
|
||||
railAdapter = address(0x3);
|
||||
token = address(0x100);
|
||||
|
||||
registry = new RailTriggerRegistry(admin);
|
||||
|
||||
vm.startPrank(admin);
|
||||
registry.grantRole(registry.RAIL_OPERATOR_ROLE(), railOperator);
|
||||
registry.grantRole(registry.RAIL_ADAPTER_ROLE(), railAdapter);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function test_createTrigger() public {
|
||||
IRailTriggerRegistry.Trigger memory t = IRailTriggerRegistry.Trigger({
|
||||
id: 0,
|
||||
rail: RailTypes.Rail.SWIFT,
|
||||
msgType: keccak256("pacs.008"),
|
||||
accountRefId: keccak256("account1"),
|
||||
walletRefId: bytes32(0),
|
||||
token: token,
|
||||
amount: 1000,
|
||||
currencyCode: keccak256("USD"),
|
||||
instructionId: keccak256("instruction1"),
|
||||
state: RailTypes.State.CREATED,
|
||||
createdAt: 0,
|
||||
updatedAt: 0
|
||||
});
|
||||
|
||||
vm.expectEmit(true, true, false, true);
|
||||
emit TriggerCreated(
|
||||
0,
|
||||
uint8(RailTypes.Rail.SWIFT),
|
||||
keccak256("pacs.008"),
|
||||
keccak256("instruction1"),
|
||||
keccak256("account1"),
|
||||
token,
|
||||
1000
|
||||
);
|
||||
|
||||
vm.prank(railOperator);
|
||||
uint256 id = registry.createTrigger(t);
|
||||
|
||||
assertEq(id, 0);
|
||||
IRailTriggerRegistry.Trigger memory retrieved = registry.getTrigger(id);
|
||||
assertEq(uint8(retrieved.rail), uint8(RailTypes.Rail.SWIFT));
|
||||
assertEq(retrieved.msgType, keccak256("pacs.008"));
|
||||
assertEq(retrieved.amount, 1000);
|
||||
assertEq(uint8(retrieved.state), uint8(RailTypes.State.CREATED));
|
||||
}
|
||||
|
||||
function test_createTrigger_duplicateInstructionId() public {
|
||||
IRailTriggerRegistry.Trigger memory t = IRailTriggerRegistry.Trigger({
|
||||
id: 0,
|
||||
rail: RailTypes.Rail.SWIFT,
|
||||
msgType: keccak256("pacs.008"),
|
||||
accountRefId: keccak256("account1"),
|
||||
walletRefId: bytes32(0),
|
||||
token: token,
|
||||
amount: 1000,
|
||||
currencyCode: keccak256("USD"),
|
||||
instructionId: keccak256("instruction1"),
|
||||
state: RailTypes.State.CREATED,
|
||||
createdAt: 0,
|
||||
updatedAt: 0
|
||||
});
|
||||
|
||||
vm.prank(railOperator);
|
||||
registry.createTrigger(t);
|
||||
|
||||
vm.prank(railOperator);
|
||||
vm.expectRevert("RailTriggerRegistry: duplicate instructionId");
|
||||
registry.createTrigger(t);
|
||||
}
|
||||
|
||||
function test_updateState() public {
|
||||
IRailTriggerRegistry.Trigger memory t = IRailTriggerRegistry.Trigger({
|
||||
id: 0,
|
||||
rail: RailTypes.Rail.SWIFT,
|
||||
msgType: keccak256("pacs.008"),
|
||||
accountRefId: keccak256("account1"),
|
||||
walletRefId: bytes32(0),
|
||||
token: token,
|
||||
amount: 1000,
|
||||
currencyCode: keccak256("USD"),
|
||||
instructionId: keccak256("instruction1"),
|
||||
state: RailTypes.State.CREATED,
|
||||
createdAt: 0,
|
||||
updatedAt: 0
|
||||
});
|
||||
|
||||
vm.prank(railOperator);
|
||||
uint256 id = registry.createTrigger(t);
|
||||
|
||||
vm.expectEmit(true, false, false, true);
|
||||
emit TriggerStateUpdated(id, uint8(RailTypes.State.CREATED), uint8(RailTypes.State.VALIDATED), bytes32(0));
|
||||
|
||||
vm.prank(railAdapter);
|
||||
registry.updateState(id, RailTypes.State.VALIDATED, bytes32(0));
|
||||
|
||||
IRailTriggerRegistry.Trigger memory retrieved = registry.getTrigger(id);
|
||||
assertEq(uint8(retrieved.state), uint8(RailTypes.State.VALIDATED));
|
||||
}
|
||||
|
||||
function test_updateState_invalidTransition() public {
|
||||
IRailTriggerRegistry.Trigger memory t = IRailTriggerRegistry.Trigger({
|
||||
id: 0,
|
||||
rail: RailTypes.Rail.SWIFT,
|
||||
msgType: keccak256("pacs.008"),
|
||||
accountRefId: keccak256("account1"),
|
||||
walletRefId: bytes32(0),
|
||||
token: token,
|
||||
amount: 1000,
|
||||
currencyCode: keccak256("USD"),
|
||||
instructionId: keccak256("instruction1"),
|
||||
state: RailTypes.State.CREATED,
|
||||
createdAt: 0,
|
||||
updatedAt: 0
|
||||
});
|
||||
|
||||
vm.prank(railOperator);
|
||||
uint256 id = registry.createTrigger(t);
|
||||
|
||||
vm.prank(railAdapter);
|
||||
vm.expectRevert("RailTriggerRegistry: invalid state transition");
|
||||
registry.updateState(id, RailTypes.State.SETTLED, bytes32(0));
|
||||
}
|
||||
|
||||
function test_instructionIdExists() public {
|
||||
IRailTriggerRegistry.Trigger memory t = IRailTriggerRegistry.Trigger({
|
||||
id: 0,
|
||||
rail: RailTypes.Rail.SWIFT,
|
||||
msgType: keccak256("pacs.008"),
|
||||
accountRefId: keccak256("account1"),
|
||||
walletRefId: bytes32(0),
|
||||
token: token,
|
||||
amount: 1000,
|
||||
currencyCode: keccak256("USD"),
|
||||
instructionId: keccak256("instruction1"),
|
||||
state: RailTypes.State.CREATED,
|
||||
createdAt: 0,
|
||||
updatedAt: 0
|
||||
});
|
||||
|
||||
assertFalse(registry.instructionIdExists(keccak256("instruction1")));
|
||||
|
||||
vm.prank(railOperator);
|
||||
registry.createTrigger(t);
|
||||
|
||||
assertTrue(registry.instructionIdExists(keccak256("instruction1")));
|
||||
}
|
||||
|
||||
// Helper events for testing (match IRailTriggerRegistry events)
|
||||
event TriggerCreated(
|
||||
uint256 indexed id,
|
||||
uint8 rail,
|
||||
bytes32 msgType,
|
||||
bytes32 instructionId,
|
||||
bytes32 accountRefId,
|
||||
address token,
|
||||
uint256 amount
|
||||
);
|
||||
|
||||
event TriggerStateUpdated(uint256 indexed id, uint8 oldState, uint8 newState, bytes32 reason);
|
||||
}
|
||||
|
||||
201
test/emoney/unit/SettlementOrchestratorTest.t.sol
Normal file
201
test/emoney/unit/SettlementOrchestratorTest.t.sol
Normal file
@@ -0,0 +1,201 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.20;
|
||||
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
import {SettlementOrchestrator} from "@emoney/SettlementOrchestrator.sol";
|
||||
import {ISettlementOrchestrator} from "@emoney/interfaces/ISettlementOrchestrator.sol";
|
||||
import {RailTriggerRegistry} from "@emoney/RailTriggerRegistry.sol";
|
||||
import {IRailTriggerRegistry} from "@emoney/interfaces/IRailTriggerRegistry.sol";
|
||||
import {RailEscrowVault} from "@emoney/RailEscrowVault.sol";
|
||||
import {AccountWalletRegistry} from "@emoney/AccountWalletRegistry.sol";
|
||||
import {PolicyManager} from "@emoney/PolicyManager.sol";
|
||||
import {DebtRegistry} from "@emoney/DebtRegistry.sol";
|
||||
import {ComplianceRegistry} from "@emoney/ComplianceRegistry.sol";
|
||||
import {RailTypes} from "@emoney/libraries/RailTypes.sol";
|
||||
import {ReasonCodes} from "@emoney/libraries/ReasonCodes.sol";
|
||||
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
|
||||
contract MockERC20 is ERC20 {
|
||||
constructor() ERC20("Mock Token", "MOCK") {
|
||||
_mint(msg.sender, 1000000 * 10**18);
|
||||
}
|
||||
|
||||
function mint(address to, uint256 amount) external {
|
||||
_mint(to, amount);
|
||||
}
|
||||
}
|
||||
|
||||
contract SettlementOrchestratorTest is Test {
|
||||
SettlementOrchestrator public orchestrator;
|
||||
RailTriggerRegistry public triggerRegistry;
|
||||
RailEscrowVault public escrowVault;
|
||||
AccountWalletRegistry public accountWalletRegistry;
|
||||
PolicyManager public policyManager;
|
||||
DebtRegistry public debtRegistry;
|
||||
ComplianceRegistry public complianceRegistry;
|
||||
MockERC20 public token;
|
||||
|
||||
address public admin;
|
||||
address public settlementOperator;
|
||||
address public railAdapter;
|
||||
address public user;
|
||||
address public issuer;
|
||||
|
||||
bytes32 public accountRefId = keccak256("account1");
|
||||
bytes32 public instructionId = keccak256("instruction1");
|
||||
|
||||
function setUp() public {
|
||||
admin = address(0x1);
|
||||
settlementOperator = address(0x2);
|
||||
railAdapter = address(0x3);
|
||||
user = address(0x10);
|
||||
issuer = address(0x20);
|
||||
|
||||
// Deploy core contracts
|
||||
complianceRegistry = new ComplianceRegistry(admin);
|
||||
debtRegistry = new DebtRegistry(admin);
|
||||
policyManager = new PolicyManager(admin, address(complianceRegistry), address(debtRegistry));
|
||||
triggerRegistry = new RailTriggerRegistry(admin);
|
||||
escrowVault = new RailEscrowVault(admin);
|
||||
accountWalletRegistry = new AccountWalletRegistry(admin);
|
||||
orchestrator = new SettlementOrchestrator(
|
||||
admin,
|
||||
address(triggerRegistry),
|
||||
address(escrowVault),
|
||||
address(accountWalletRegistry),
|
||||
address(policyManager),
|
||||
address(debtRegistry),
|
||||
address(complianceRegistry)
|
||||
);
|
||||
|
||||
token = new MockERC20();
|
||||
token.mint(user, 10000 * 10**18);
|
||||
|
||||
// Set up roles
|
||||
vm.startPrank(admin);
|
||||
triggerRegistry.grantRole(triggerRegistry.RAIL_OPERATOR_ROLE(), settlementOperator);
|
||||
triggerRegistry.grantRole(triggerRegistry.RAIL_ADAPTER_ROLE(), railAdapter);
|
||||
triggerRegistry.grantRole(triggerRegistry.RAIL_ADAPTER_ROLE(), address(orchestrator)); // Orchestrator needs this to call updateState
|
||||
escrowVault.grantRole(escrowVault.SETTLEMENT_OPERATOR_ROLE(), address(orchestrator));
|
||||
orchestrator.grantRole(orchestrator.SETTLEMENT_OPERATOR_ROLE(), settlementOperator);
|
||||
orchestrator.grantRole(orchestrator.RAIL_ADAPTER_ROLE(), railAdapter);
|
||||
debtRegistry.grantRole(debtRegistry.DEBT_AUTHORITY_ROLE(), address(orchestrator));
|
||||
complianceRegistry.grantRole(complianceRegistry.COMPLIANCE_ROLE(), admin);
|
||||
vm.stopPrank();
|
||||
|
||||
// Set up compliance
|
||||
vm.prank(admin);
|
||||
complianceRegistry.setCompliance(user, true, 1, keccak256("US"));
|
||||
}
|
||||
|
||||
function test_validateAndLock_vaultMode() public {
|
||||
// Create trigger
|
||||
IRailTriggerRegistry.Trigger memory t = IRailTriggerRegistry.Trigger({
|
||||
id: 0,
|
||||
rail: RailTypes.Rail.SWIFT,
|
||||
msgType: keccak256("pacs.008"),
|
||||
accountRefId: accountRefId,
|
||||
walletRefId: bytes32(0),
|
||||
token: address(token),
|
||||
amount: 1000 * 10**18,
|
||||
currencyCode: keccak256("USD"),
|
||||
instructionId: instructionId,
|
||||
state: RailTypes.State.CREATED,
|
||||
createdAt: 0,
|
||||
updatedAt: 0
|
||||
});
|
||||
|
||||
vm.prank(settlementOperator);
|
||||
uint256 triggerId = triggerRegistry.createTrigger(t);
|
||||
|
||||
// Approve vault to spend tokens
|
||||
vm.startPrank(user);
|
||||
token.approve(address(escrowVault), 1000 * 10**18);
|
||||
vm.stopPrank();
|
||||
|
||||
// Note: validateAndLock needs account address resolution
|
||||
// This test demonstrates the flow, but in production you'd need to set up account mapping
|
||||
// For now, we'll skip the actual validation test and test the state transitions
|
||||
}
|
||||
|
||||
function test_markSubmitted() public {
|
||||
// Create and validate trigger
|
||||
IRailTriggerRegistry.Trigger memory t = IRailTriggerRegistry.Trigger({
|
||||
id: 0,
|
||||
rail: RailTypes.Rail.SWIFT,
|
||||
msgType: keccak256("pacs.008"),
|
||||
accountRefId: accountRefId,
|
||||
walletRefId: bytes32(0),
|
||||
token: address(token),
|
||||
amount: 1000 * 10**18,
|
||||
currencyCode: keccak256("USD"),
|
||||
instructionId: instructionId,
|
||||
state: RailTypes.State.CREATED,
|
||||
createdAt: 0,
|
||||
updatedAt: 0
|
||||
});
|
||||
|
||||
vm.prank(settlementOperator);
|
||||
uint256 triggerId = triggerRegistry.createTrigger(t);
|
||||
|
||||
// Update to VALIDATED state
|
||||
vm.prank(railAdapter);
|
||||
triggerRegistry.updateState(triggerId, RailTypes.State.VALIDATED, ReasonCodes.OK);
|
||||
|
||||
bytes32 railTxRef = keccak256("railTx1");
|
||||
|
||||
// markSubmitted emits Submitted event, but also calls updateState twice which emits other events
|
||||
// We'll check the event was emitted by checking the result instead of using vm.expectEmit
|
||||
vm.prank(railAdapter);
|
||||
orchestrator.markSubmitted(triggerId, railTxRef);
|
||||
|
||||
assertEq(orchestrator.getRailTxRef(triggerId), railTxRef);
|
||||
}
|
||||
|
||||
function test_confirmSettled_inbound() public {
|
||||
// Note: This test is skipped because _resolveAccountAddress always returns address(0)
|
||||
// which causes validateAndLock and confirmSettled to fail for inbound flows.
|
||||
// In production, _resolveAccountAddress needs to be properly implemented to decode
|
||||
// walletRefId to an address or use AccountWalletRegistry properly.
|
||||
// This test demonstrates the limitation - inbound flows require account resolution.
|
||||
// For now, we test outbound flows which don't require account resolution for confirmSettled.
|
||||
}
|
||||
|
||||
function test_confirmRejected() public {
|
||||
IRailTriggerRegistry.Trigger memory t = IRailTriggerRegistry.Trigger({
|
||||
id: 0,
|
||||
rail: RailTypes.Rail.SWIFT,
|
||||
msgType: keccak256("pacs.008"),
|
||||
accountRefId: accountRefId,
|
||||
walletRefId: bytes32(0),
|
||||
token: address(token),
|
||||
amount: 1000 * 10**18,
|
||||
currencyCode: keccak256("USD"),
|
||||
instructionId: instructionId,
|
||||
state: RailTypes.State.CREATED,
|
||||
createdAt: 0,
|
||||
updatedAt: 0
|
||||
});
|
||||
|
||||
vm.prank(settlementOperator);
|
||||
uint256 triggerId = triggerRegistry.createTrigger(t);
|
||||
|
||||
vm.prank(railAdapter);
|
||||
triggerRegistry.updateState(triggerId, RailTypes.State.VALIDATED, ReasonCodes.OK);
|
||||
|
||||
bytes32 reason = keccak256("REJECTED");
|
||||
|
||||
// confirmRejected emits Rejected event, but also calls updateState which emits other events
|
||||
// We'll check the state was updated correctly instead of using vm.expectEmit
|
||||
vm.prank(railAdapter);
|
||||
orchestrator.confirmRejected(triggerId, reason);
|
||||
|
||||
IRailTriggerRegistry.Trigger memory trigger = triggerRegistry.getTrigger(triggerId);
|
||||
assertEq(uint8(trigger.state), uint8(RailTypes.State.REJECTED));
|
||||
}
|
||||
|
||||
// Helper events for testing (match ISettlementOrchestrator events)
|
||||
event Submitted(uint256 indexed triggerId, bytes32 railTxRef);
|
||||
event Rejected(uint256 indexed triggerId, bytes32 reason);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user