From 47eb02053f41d012047ab62315ac81c1b220a923 Mon Sep 17 00:00:00 2001 From: Alberto Molina Date: Mon, 22 Sep 2025 14:37:30 +0200 Subject: [PATCH 01/54] coupon listing 2 Signed-off-by: Alberto Molina --- packages/ats/contracts/Configuration.ts | 3 +- .../factory/ERC3643/interfaces/IBondRead.sol | 14 + .../layer_0/bond/BondStorageWrapper.sol | 50 +- .../layer_0/constants/storagePositions.sol | 3 + .../contracts/layer_0/constants/values.sol | 4 + .../CorporateActionsStorageWrapper.sol | 4 - ...eduledBalanceAdjustmentsStorageWrapper.sol | 6 +- .../ScheduledCouponListingStorageWrapper.sol | 107 ++++ ...heduledCrossOrderedTasksStorageWrapper.sol | 20 +- .../contracts/layer_2/bond/BondRead.sol | 23 +- .../layer_2/constants/resolverKeys.sol | 3 + .../contracts/layer_2/equity/Equity.sol | 2 +- .../layer_2/interfaces/bond/IBondRead.sol | 14 + .../IScheduledCouponListing.sol | 13 + .../ScheduledCouponListing.sol | 41 ++ .../ScheduledCouponListingFacet.sol | 55 ++ .../contracts/layer_3/bondUSA/BondUSARead.sol | 11 +- .../ScheduledCouponListingFacetTimeTravel.sol | 33 ++ .../scripts/businessLogicResolver.ts | 1 + ...nfigurationsForDeployedContractsCommand.ts | 1 + packages/ats/contracts/scripts/deploy.ts | 27 +- .../scripts/deployEnvironmentByRpc.ts | 1 + .../results/DeployAtsContractsResult.ts | 5 + packages/ats/contracts/tasks/deploy.ts | 3 + .../test/unitTests/layer_1/bond/bond.test.ts | 83 +++ .../scheduledCouponListing.test.ts | 503 ++++++++++++++++++ 26 files changed, 1010 insertions(+), 20 deletions(-) create mode 100644 packages/ats/contracts/contracts/layer_0/scheduledTasks/scheduledCouponListing/ScheduledCouponListingStorageWrapper.sol create mode 100644 packages/ats/contracts/contracts/layer_2/interfaces/scheduledTasks/scheduledCouponListing/IScheduledCouponListing.sol create mode 100644 packages/ats/contracts/contracts/layer_2/scheduledTasks/scheduledCouponListing/ScheduledCouponListing.sol create mode 100644 packages/ats/contracts/contracts/layer_2/scheduledTasks/scheduledCouponListing/ScheduledCouponListingFacet.sol create mode 100644 packages/ats/contracts/contracts/test/testTimeTravel/facetsTimeTravel/ScheduledCouponListingFacetTimeTravel.sol create mode 100644 packages/ats/contracts/test/unitTests/layer_1/scheduledTasks/scheduledCouponListing/scheduledCouponListing.test.ts diff --git a/packages/ats/contracts/Configuration.ts b/packages/ats/contracts/Configuration.ts index 855f3ff54..ff6b0f542 100644 --- a/packages/ats/contracts/Configuration.ts +++ b/packages/ats/contracts/Configuration.ts @@ -247,7 +247,8 @@ export const CONTRACT_NAMES = [ 'BondUSARead', //TODO 'ScheduledSnapshotsFacet', 'ScheduledBalanceAdjustmentsFacet', - 'scheduledCrossOrderedTasksFacet', + 'ScheduledCrossOrderedTasksFacet', + 'ScheduledCouponListingFacet', 'SnapshotsFacet', 'CorporateActionsFacet', 'TransferAndLockFacet', diff --git a/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IBondRead.sol b/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IBondRead.sol index f5b27d09b..10a5fca9f 100644 --- a/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IBondRead.sol +++ b/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IBondRead.sol @@ -78,4 +78,18 @@ interface TRexIBondRead { function getTotalCouponHolders( uint256 _couponID ) external view returns (uint256); + + function getCouponFromOrderedListAt( + uint256 _pos + ) external view returns (uint256 couponID_); + + function getCouponsOrderedList( + uint256 _pageIndex, + uint256 _pageLength + ) external view returns (uint256[] memory couponIDs_); + + function getCouponsOrderedListTotal() + external + view + returns (uint256 total_); } diff --git a/packages/ats/contracts/contracts/layer_0/bond/BondStorageWrapper.sol b/packages/ats/contracts/contracts/layer_0/bond/BondStorageWrapper.sol index 6f36411b0..4fe926253 100644 --- a/packages/ats/contracts/contracts/layer_0/bond/BondStorageWrapper.sol +++ b/packages/ats/contracts/contracts/layer_0/bond/BondStorageWrapper.sol @@ -7,8 +7,9 @@ import { import { COUPON_CORPORATE_ACTION_TYPE, SNAPSHOT_RESULT_ID, - SNAPSHOT_TASK_TYPE -} from '../../layer_2/constants/values.sol'; + SNAPSHOT_TASK_TYPE, + COUPON_LISTING_TASK_TYPE +} from '../constants/values.sol'; import {IBondRead} from '../../layer_2/interfaces/bond/IBondRead.sol'; import { IBondStorageWrapper @@ -19,6 +20,7 @@ import { import { ERC20PermitStorageWrapper } from '../ERC1400/ERC20Permit/ERC20PermitStorageWrapper.sol'; +import {LibCommon} from '../common/libraries/LibCommon.sol'; abstract contract BondStorageWrapper is IBondStorageWrapper, @@ -29,6 +31,7 @@ abstract contract BondStorageWrapper is struct BondDataStorage { IBondRead.BondDetailsData bondDetail; bool initialized; + uint256[] counponsOrderedListByIds; } /** @@ -82,7 +85,12 @@ abstract contract BondStorageWrapper is newCoupon.recordDate, abi.encode(SNAPSHOT_TASK_TYPE) ); + _addScheduledCrossOrderedTask( + newCoupon.recordDate, + abi.encode(COUPON_LISTING_TASK_TYPE) + ); _addScheduledSnapshot(newCoupon.recordDate, abi.encode(_actionId)); + _addScheduledCouponListing(newCoupon.recordDate, abi.encode(_actionId)); } /** @@ -97,6 +105,44 @@ abstract contract BondStorageWrapper is return true; } + function _addToCouponsOrderedList(uint256 _couponID) internal override { + _bondStorage().counponsOrderedListByIds.push(_couponID); + } + + function _getCouponFromOrderedListAt( + uint256 _pos + ) internal view returns (uint256 couponID_) { + if (_pos >= _getCouponsOrderedListTotal()) return 0; + return _bondStorage().counponsOrderedListByIds[_pos]; + } + + function _getCouponsOrderedList( + uint256 _pageIndex, + uint256 _pageLength + ) internal view returns (uint256[] memory couponIDs_) { + (uint256 start, uint256 end) = LibCommon.getStartAndEnd( + _pageIndex, + _pageLength + ); + + couponIDs_ = new uint256[]( + LibCommon.getSize(start, end, _getCouponsOrderedListTotal()) + ); + + for (uint256 i = 0; i < couponIDs_.length; i++) { + couponIDs_[i] = _getCouponFromOrderedListAt(start + i); + } + } + + function _getCouponsOrderedListTotal() + internal + view + override + returns (uint256 total_) + { + return _bondStorage().counponsOrderedListByIds.length; + } + function _getBondDetails() internal view diff --git a/packages/ats/contracts/contracts/layer_0/constants/storagePositions.sol b/packages/ats/contracts/contracts/layer_0/constants/storagePositions.sol index cc8553ef3..7c2c3d472 100644 --- a/packages/ats/contracts/contracts/layer_0/constants/storagePositions.sol +++ b/packages/ats/contracts/contracts/layer_0/constants/storagePositions.sol @@ -36,6 +36,9 @@ bytes32 constant _SCHEDULED_SNAPSHOTS_STORAGE_POSITION = 0xe5334ddaa6268d55c7efe // keccak256('security.token.standard.scheduledBalanceAdjustments.storage'); bytes32 constant _SCHEDULED_BALANCE_ADJUSTMENTS_STORAGE_POSITION = 0xaf4aaa3de473ec9b58645d40f5a2fe4e176157e247b2d875db61f1a70935ac68; +// keccak256('security.token.standard.scheduledCouponListing.storage'); +bytes32 constant _SCHEDULED_COUPON_LISTING_STORAGE_POSITION = 0x020cecc946ba57a1f8569220f46e5763939a3e864a1a4064efc2be63a845635a; + // keccak256('security.token.standard.scheduledCrossOrderedTasks.storage'); bytes32 constant _SCHEDULED_CROSS_ORDERED_TASKS_STORAGE_POSITION = 0x07c301a048b8fa80688acfab6d93f7e94a43ce454031a02cdd132b92ca943a70; diff --git a/packages/ats/contracts/contracts/layer_0/constants/values.sol b/packages/ats/contracts/contracts/layer_0/constants/values.sol index 2beb7e6be..65f065a53 100644 --- a/packages/ats/contracts/contracts/layer_0/constants/values.sol +++ b/packages/ats/contracts/contracts/layer_0/constants/values.sol @@ -10,6 +10,7 @@ bytes constant EMPTY_BYTES = bytes(''); // TODO: align naming bytes32 constant _DEFAULT_PARTITION = 0x0000000000000000000000000000000000000000000000000000000000000001; uint256 constant SNAPSHOT_RESULT_ID = 0; +uint256 constant COUPON_LISTING_RESULT_ID = 1; // keccak256('security.token.standard.dividend.corporateAction'); bytes32 constant DIVIDEND_CORPORATE_ACTION_TYPE = 0x1c29d09f87f2b0c8192a7719a2acdfdfa320dc2835b5a0398e5bd8dc34c14b0e; @@ -29,6 +30,9 @@ bytes32 constant BALANCE_ADJUSTMENT_TASK_TYPE = 0x9ce9cffaccaf68fc544ce4df9e5e27 // keccak256('security.token.standard.snapshot.scheduledTasks'); bytes32 constant SNAPSHOT_TASK_TYPE = 0x322c4b500b27950e00c27e3a40ca8f9ffacbc81a3b4e3c9516717391fd54234c; +// keccak256('security.token.standard.couponListing.scheduledTasks'); +bytes32 constant COUPON_LISTING_TASK_TYPE = 0xc0025ea024305bcaedb7e0a5d9ef6f0bca23bb36ee261794fdfb21cd810563ce; + bytes32 constant ERC20PERMIT_TYPEHASH = keccak256( 'Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)' ); diff --git a/packages/ats/contracts/contracts/layer_0/corporateActions/CorporateActionsStorageWrapper.sol b/packages/ats/contracts/contracts/layer_0/corporateActions/CorporateActionsStorageWrapper.sol index 89598a6ee..b4f131bd7 100644 --- a/packages/ats/contracts/contracts/layer_0/corporateActions/CorporateActionsStorageWrapper.sol +++ b/packages/ats/contracts/contracts/layer_0/corporateActions/CorporateActionsStorageWrapper.sol @@ -1,10 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity >=0.8.0 <0.9.0; -import { - ICorporateActionsStorageWrapper, - CorporateActionDataStorage -} from '../../layer_1/interfaces/corporateActions/ICorporateActionsStorageWrapper.sol'; import { ICorporateActionsStorageWrapper, CorporateActionDataStorage diff --git a/packages/ats/contracts/contracts/layer_0/scheduledTasks/scheduledBalanceAdjustments/ScheduledBalanceAdjustmentsStorageWrapper.sol b/packages/ats/contracts/contracts/layer_0/scheduledTasks/scheduledBalanceAdjustments/ScheduledBalanceAdjustmentsStorageWrapper.sol index d6e07573e..ed7ccca21 100644 --- a/packages/ats/contracts/contracts/layer_0/scheduledTasks/scheduledBalanceAdjustments/ScheduledBalanceAdjustmentsStorageWrapper.sol +++ b/packages/ats/contracts/contracts/layer_0/scheduledTasks/scheduledBalanceAdjustments/ScheduledBalanceAdjustmentsStorageWrapper.sol @@ -5,8 +5,8 @@ import { IScheduledBalanceAdjustments } from '../../../layer_2/interfaces/scheduledTasks/scheduledBalanceAdjustments/IScheduledBalanceAdjustments.sol'; import { - ScheduledSnapshotsStorageWrapper -} from '../scheduledSnapshots/ScheduledSnapshotsStorageWrapper.sol'; + ScheduledCouponListingStorageWrapper +} from '../scheduledCouponListing/ScheduledCouponListingStorageWrapper.sol'; import { ScheduledTasksLib } from '../../../layer_2/scheduledTasks/ScheduledTasksLib.sol'; @@ -20,7 +20,7 @@ import { } from '../../../layer_2/interfaces/scheduledTasks/scheduledTasksCommon/IScheduledTasksCommon.sol'; abstract contract ScheduledBalanceAdjustmentsStorageWrapper is - ScheduledSnapshotsStorageWrapper + ScheduledCouponListingStorageWrapper { function _addScheduledBalanceAdjustment( uint256 _newScheduledTimestamp, diff --git a/packages/ats/contracts/contracts/layer_0/scheduledTasks/scheduledCouponListing/ScheduledCouponListingStorageWrapper.sol b/packages/ats/contracts/contracts/layer_0/scheduledTasks/scheduledCouponListing/ScheduledCouponListingStorageWrapper.sol new file mode 100644 index 000000000..26455d29b --- /dev/null +++ b/packages/ats/contracts/contracts/layer_0/scheduledTasks/scheduledCouponListing/ScheduledCouponListingStorageWrapper.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0 <0.9.0; + +import { + IScheduledCouponListing +} from '../../../layer_2/interfaces/scheduledTasks/scheduledCouponListing/IScheduledCouponListing.sol'; +import { + ScheduledSnapshotsStorageWrapper +} from '../scheduledSnapshots/ScheduledSnapshotsStorageWrapper.sol'; +import { + ScheduledTasksLib +} from '../../../layer_2/scheduledTasks/ScheduledTasksLib.sol'; +import { + _SCHEDULED_COUPON_LISTING_STORAGE_POSITION +} from '../../constants/storagePositions.sol'; +import {IEquity} from '../../../layer_2/interfaces/equity/IEquity.sol'; +import { + ScheduledTask, + ScheduledTasksDataStorage +} from '../../../layer_2/interfaces/scheduledTasks/scheduledTasksCommon/IScheduledTasksCommon.sol'; +import {COUPON_LISTING_RESULT_ID} from '../../constants/values.sol'; + +abstract contract ScheduledCouponListingStorageWrapper is + ScheduledSnapshotsStorageWrapper +{ + function _addScheduledCouponListing( + uint256 _newScheduledTimestamp, + bytes memory _newData + ) internal { + ScheduledTasksLib.addScheduledTask( + _scheduledCouponListingtorage(), + _newScheduledTimestamp, + _newData + ); + } + + function _triggerScheduledCouponListing( + uint256 _max + ) internal returns (uint256) { + return + _triggerScheduledTasks( + _scheduledCouponListingtorage(), + _onScheduledCouponListingTriggered, + _max, + _blockTimestamp() + ); + } + + function _onScheduledCouponListingTriggered( + uint256 /*_pos*/, + uint256 /*_scheduledTasksLength*/, + ScheduledTask memory _scheduledTask + ) internal { + bytes memory data = _scheduledTask.data; + + if (data.length == 0) return; + + bytes32 actionId = abi.decode(data, (bytes32)); + + _addToCouponsOrderedList(uint256(actionId)); + uint256 pos = _getCouponsOrderedListTotal(); + + _updateCorporateActionResult( + actionId, + COUPON_LISTING_RESULT_ID, + abi.encodePacked(pos) + ); + } + + function _addToCouponsOrderedList(uint256 _couponID) internal virtual; + function _getCouponsOrderedListTotal() + internal + view + virtual + returns (uint256 total_); + + function _getScheduledCouponListingCount() internal view returns (uint256) { + return + ScheduledTasksLib.getScheduledTaskCount( + _scheduledCouponListingtorage() + ); + } + + function _getScheduledCouponListing( + uint256 _pageIndex, + uint256 _pageLength + ) internal view returns (ScheduledTask[] memory scheduledCouponListing_) { + return + ScheduledTasksLib.getScheduledTasks( + _scheduledCouponListingtorage(), + _pageIndex, + _pageLength + ); + } + + function _scheduledCouponListingtorage() + internal + pure + returns (ScheduledTasksDataStorage storage scheduledCouponListing_) + { + bytes32 position = _SCHEDULED_COUPON_LISTING_STORAGE_POSITION; + // solhint-disable-next-line no-inline-assembly + assembly { + scheduledCouponListing_.slot := position + } + } +} diff --git a/packages/ats/contracts/contracts/layer_0/scheduledTasks/scheduledCrossOrderedTasks/ScheduledCrossOrderedTasksStorageWrapper.sol b/packages/ats/contracts/contracts/layer_0/scheduledTasks/scheduledCrossOrderedTasks/ScheduledCrossOrderedTasksStorageWrapper.sol index 0850b8909..75f36cd8b 100644 --- a/packages/ats/contracts/contracts/layer_0/scheduledTasks/scheduledCrossOrderedTasks/ScheduledCrossOrderedTasksStorageWrapper.sol +++ b/packages/ats/contracts/contracts/layer_0/scheduledTasks/scheduledCrossOrderedTasks/ScheduledCrossOrderedTasksStorageWrapper.sol @@ -13,7 +13,11 @@ import { import { ScheduledBalanceAdjustmentsStorageWrapper } from '../scheduledBalanceAdjustments/ScheduledBalanceAdjustmentsStorageWrapper.sol'; -import {SNAPSHOT_TASK_TYPE} from '../../constants/values.sol'; +import { + SNAPSHOT_TASK_TYPE, + BALANCE_ADJUSTMENT_TASK_TYPE, + COUPON_LISTING_TASK_TYPE +} from '../../constants/values.sol'; import { ScheduledTask, ScheduledTasksDataStorage @@ -53,11 +57,21 @@ abstract contract ScheduledCrossOrderedTasksStorageWrapper is bytes memory data = _scheduledTask.data; if (data.length == 0) return; - if (abi.decode(data, (bytes32)) == SNAPSHOT_TASK_TYPE) { + + bytes32 taskType = abi.decode(data, (bytes32)); + + if (taskType == SNAPSHOT_TASK_TYPE) { _triggerScheduledSnapshots(1); return; } - _triggerScheduledBalanceAdjustments(1); + if (taskType == BALANCE_ADJUSTMENT_TASK_TYPE) { + _triggerScheduledBalanceAdjustments(1); + return; + } + if (taskType == COUPON_LISTING_TASK_TYPE) { + _triggerScheduledCouponListing(1); + return; + } } function _getScheduledCrossOrderedTaskCount() diff --git a/packages/ats/contracts/contracts/layer_2/bond/BondRead.sol b/packages/ats/contracts/contracts/layer_2/bond/BondRead.sol index a379f3cfe..7089a549e 100644 --- a/packages/ats/contracts/contracts/layer_2/bond/BondRead.sol +++ b/packages/ats/contracts/contracts/layer_2/bond/BondRead.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.0 <0.9.0; import {IBondRead} from '../interfaces/bond/IBondRead.sol'; import {Common} from '../../layer_1/common/Common.sol'; -import {COUPON_CORPORATE_ACTION_TYPE} from '../constants/values.sol'; +import {COUPON_CORPORATE_ACTION_TYPE} from '../../layer_0/constants/values.sol'; import { IStaticFunctionSelectors } from '../../interfaces/resolver/resolverProxy/IStaticFunctionSelectors.sol'; @@ -65,4 +65,25 @@ abstract contract BondRead is IBondRead, IStaticFunctionSelectors, Common { ) external view returns (uint256) { return _getTotalCouponHolders(_couponID); } + + function getCouponFromOrderedListAt( + uint256 _pos + ) external view returns (uint256 couponID_) { + return _getCouponFromOrderedListAt(_pos); + } + + function getCouponsOrderedList( + uint256 _pageIndex, + uint256 _pageLength + ) external view returns (uint256[] memory couponIDs_) { + return _getCouponsOrderedList(_pageIndex, _pageLength); + } + + function getCouponsOrderedListTotal() + external + view + returns (uint256 total_) + { + return _getCouponsOrderedListTotal(); + } } diff --git a/packages/ats/contracts/contracts/layer_2/constants/resolverKeys.sol b/packages/ats/contracts/contracts/layer_2/constants/resolverKeys.sol index a9f3dc595..96f07583d 100644 --- a/packages/ats/contracts/contracts/layer_2/constants/resolverKeys.sol +++ b/packages/ats/contracts/contracts/layer_2/constants/resolverKeys.sol @@ -18,6 +18,9 @@ bytes32 constant _SCHEDULED_SNAPSHOTS_RESOLVER_KEY = 0x100f681e33d02a1124c2c05a5 // keccak256('security.token.standard.scheduled.balanceAdjustments.resolverKey'); bytes32 constant _SCHEDULED_BALANCE_ADJUSTMENTS_RESOLVER_KEY = 0xc418e67a48260d700e5f85863ad6fa6593206a4385728f8baba1572d631535e0; +// keccak256('security.token.standard.scheduled.couponListing.resolverKey'); +bytes32 constant _SCHEDULED_COUPON_LISTING_RESOLVER_KEY = 0x6cc7645ae5bcd122875ce8bd150bd28dda6374546c4c2421e5ae4fdeedb3ab30; + // keccak256('security.token.standard.scheduled.tasks.resolverKey'); bytes32 constant _SCHEDULED_TASKS_RESOLVER_KEY = 0xa4934195ab83f1497ce5fc99b68d0f41694716bcfba5f232aa6c8e0d4d504f08; diff --git a/packages/ats/contracts/contracts/layer_2/equity/Equity.sol b/packages/ats/contracts/contracts/layer_2/equity/Equity.sol index 98b9b7559..5f9c583f7 100644 --- a/packages/ats/contracts/contracts/layer_2/equity/Equity.sol +++ b/packages/ats/contracts/contracts/layer_2/equity/Equity.sol @@ -6,7 +6,7 @@ import { DIVIDEND_CORPORATE_ACTION_TYPE, VOTING_RIGHTS_CORPORATE_ACTION_TYPE, BALANCE_ADJUSTMENT_CORPORATE_ACTION_TYPE -} from '../constants/values.sol'; +} from '../../layer_0/constants/values.sol'; import {IEquity} from '../interfaces/equity/IEquity.sol'; import {Common} from '../../layer_1/common/Common.sol'; import { diff --git a/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBondRead.sol b/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBondRead.sol index 79e4a862b..cf66e817d 100644 --- a/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBondRead.sol +++ b/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBondRead.sol @@ -78,4 +78,18 @@ interface IBondRead { function getTotalCouponHolders( uint256 _couponID ) external view returns (uint256); + + function getCouponFromOrderedListAt( + uint256 _pos + ) external view returns (uint256 couponID_); + + function getCouponsOrderedList( + uint256 _pageIndex, + uint256 _pageLength + ) external view returns (uint256[] memory couponIDs_); + + function getCouponsOrderedListTotal() + external + view + returns (uint256 total_); } diff --git a/packages/ats/contracts/contracts/layer_2/interfaces/scheduledTasks/scheduledCouponListing/IScheduledCouponListing.sol b/packages/ats/contracts/contracts/layer_2/interfaces/scheduledTasks/scheduledCouponListing/IScheduledCouponListing.sol new file mode 100644 index 000000000..7643666d9 --- /dev/null +++ b/packages/ats/contracts/contracts/layer_2/interfaces/scheduledTasks/scheduledCouponListing/IScheduledCouponListing.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0 <0.9.0; + +import {ScheduledTask} from '../scheduledTasksCommon/IScheduledTasksCommon.sol'; + +interface IScheduledCouponListing { + function scheduledCouponListingCount() external view returns (uint256); + + function getScheduledCouponListing( + uint256 _pageIndex, + uint256 _pageLength + ) external view returns (ScheduledTask[] memory scheduledCouponListing_); +} diff --git a/packages/ats/contracts/contracts/layer_2/scheduledTasks/scheduledCouponListing/ScheduledCouponListing.sol b/packages/ats/contracts/contracts/layer_2/scheduledTasks/scheduledCouponListing/ScheduledCouponListing.sol new file mode 100644 index 000000000..8d81f5952 --- /dev/null +++ b/packages/ats/contracts/contracts/layer_2/scheduledTasks/scheduledCouponListing/ScheduledCouponListing.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0 <0.9.0; + +import {Common} from '../../../layer_1/common/Common.sol'; +import { + IScheduledCouponListing +} from '../../interfaces/scheduledTasks/scheduledCouponListing/IScheduledCouponListing.sol'; +import { + ScheduledTask +} from '../../interfaces/scheduledTasks/scheduledTasksCommon/IScheduledTasksCommon.sol'; +import { + EnumerableSet +} from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol'; + +abstract contract ScheduledCouponListing is IScheduledCouponListing, Common { + using EnumerableSet for EnumerableSet.Bytes32Set; + + function scheduledCouponListingCount() + external + view + override + returns (uint256) + { + return _getScheduledCouponListingCount(); + } + + function getScheduledCouponListing( + uint256 _pageIndex, + uint256 _pageLength + ) + external + view + override + returns (ScheduledTask[] memory scheduledCouponListing_) + { + scheduledCouponListing_ = _getScheduledCouponListing( + _pageIndex, + _pageLength + ); + } +} diff --git a/packages/ats/contracts/contracts/layer_2/scheduledTasks/scheduledCouponListing/ScheduledCouponListingFacet.sol b/packages/ats/contracts/contracts/layer_2/scheduledTasks/scheduledCouponListing/ScheduledCouponListingFacet.sol new file mode 100644 index 000000000..05d06d7a7 --- /dev/null +++ b/packages/ats/contracts/contracts/layer_2/scheduledTasks/scheduledCouponListing/ScheduledCouponListingFacet.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0 <0.9.0; + +import { + IStaticFunctionSelectors +} from '../../../interfaces/resolver/resolverProxy/IStaticFunctionSelectors.sol'; +import { + _SCHEDULED_COUPON_LISTING_RESOLVER_KEY +} from '../../constants/resolverKeys.sol'; +import { + IScheduledCouponListing +} from '../../interfaces/scheduledTasks/scheduledCouponListing/IScheduledCouponListing.sol'; +import {ScheduledCouponListing} from './ScheduledCouponListing.sol'; + +contract ScheduledCouponListingFacet is + ScheduledCouponListing, + IStaticFunctionSelectors +{ + function getStaticResolverKey() + external + pure + override + returns (bytes32 staticResolverKey_) + { + staticResolverKey_ = _SCHEDULED_COUPON_LISTING_RESOLVER_KEY; + } + + function getStaticFunctionSelectors() + external + pure + override + returns (bytes4[] memory staticFunctionSelectors_) + { + uint256 selectorIndex; + staticFunctionSelectors_ = new bytes4[](2); + staticFunctionSelectors_[selectorIndex++] = this + .scheduledCouponListingCount + .selector; + staticFunctionSelectors_[selectorIndex++] = this + .getScheduledCouponListing + .selector; + } + + function getStaticInterfaceIds() + external + pure + override + returns (bytes4[] memory staticInterfaceIds_) + { + staticInterfaceIds_ = new bytes4[](1); + uint256 selectorsIndex; + staticInterfaceIds_[selectorsIndex++] = type(IScheduledCouponListing) + .interfaceId; + } +} diff --git a/packages/ats/contracts/contracts/layer_3/bondUSA/BondUSARead.sol b/packages/ats/contracts/contracts/layer_3/bondUSA/BondUSARead.sol index 075536b26..667dd53c8 100644 --- a/packages/ats/contracts/contracts/layer_3/bondUSA/BondUSARead.sol +++ b/packages/ats/contracts/contracts/layer_3/bondUSA/BondUSARead.sol @@ -26,7 +26,7 @@ contract BondUSARead is BondRead, Security { returns (bytes4[] memory staticFunctionSelectors_) { uint256 selectorIndex; - staticFunctionSelectors_ = new bytes4[](9); + staticFunctionSelectors_ = new bytes4[](12); staticFunctionSelectors_[selectorIndex++] = this .getBondDetails .selector; @@ -50,6 +50,15 @@ contract BondUSARead is BondRead, Security { staticFunctionSelectors_[selectorIndex++] = this .getTotalSecurityHolders .selector; + staticFunctionSelectors_[selectorIndex++] = this + .getCouponFromOrderedListAt + .selector; + staticFunctionSelectors_[selectorIndex++] = this + .getCouponsOrderedList + .selector; + staticFunctionSelectors_[selectorIndex++] = this + .getCouponsOrderedListTotal + .selector; } function getStaticInterfaceIds() diff --git a/packages/ats/contracts/contracts/test/testTimeTravel/facetsTimeTravel/ScheduledCouponListingFacetTimeTravel.sol b/packages/ats/contracts/contracts/test/testTimeTravel/facetsTimeTravel/ScheduledCouponListingFacetTimeTravel.sol new file mode 100644 index 000000000..ae41b9260 --- /dev/null +++ b/packages/ats/contracts/contracts/test/testTimeTravel/facetsTimeTravel/ScheduledCouponListingFacetTimeTravel.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0 <0.9.0; + +import { + ScheduledCouponListingFacet +} from '../../../layer_2/scheduledTasks/scheduledCouponListing/ScheduledCouponListingFacet.sol'; +import { + TimeTravelStorageWrapper +} from '../timeTravel/TimeTravelStorageWrapper.sol'; +import {LocalContext} from '../../../layer_0/context/LocalContext.sol'; + +contract ScheduledCouponListingFacetTimeTravel is + ScheduledCouponListingFacet, + TimeTravelStorageWrapper +{ + function _blockTimestamp() + internal + view + override(LocalContext, TimeTravelStorageWrapper) + returns (uint256) + { + return TimeTravelStorageWrapper._blockTimestamp(); + } + + function _blockNumber() + internal + view + override(LocalContext, TimeTravelStorageWrapper) + returns (uint256) + { + return TimeTravelStorageWrapper._blockNumber(); + } +} diff --git a/packages/ats/contracts/scripts/businessLogicResolver.ts b/packages/ats/contracts/scripts/businessLogicResolver.ts index 24c0fbca5..0cb5d4ef8 100644 --- a/packages/ats/contracts/scripts/businessLogicResolver.ts +++ b/packages/ats/contracts/scripts/businessLogicResolver.ts @@ -264,6 +264,7 @@ export interface DeployedBusinessLogics { scheduledSnapshotsFacet: IStaticFunctionSelectors scheduledBalanceAdjustmentsFacet: IStaticFunctionSelectors scheduledCrossOrderedTasksFacet: IStaticFunctionSelectors + scheduledCouponListingFacet: IStaticFunctionSelectors CapFacet: IStaticFunctionSelectors LockFacet: IStaticFunctionSelectors transferAndLockFacet: IStaticFunctionSelectors diff --git a/packages/ats/contracts/scripts/commands/CreateConfigurationsForDeployedContractsCommand.ts b/packages/ats/contracts/scripts/commands/CreateConfigurationsForDeployedContractsCommand.ts index 75ddd67ba..cade29d7d 100644 --- a/packages/ats/contracts/scripts/commands/CreateConfigurationsForDeployedContractsCommand.ts +++ b/packages/ats/contracts/scripts/commands/CreateConfigurationsForDeployedContractsCommand.ts @@ -254,6 +254,7 @@ export default class CreateConfigurationsForDeployedContractsCommand extends Bas this.excludeEquityAddresses = [ deployedContractList.bondUsaRead.address, deployedContractList.beneficiariesFacet.address, + deployedContractList.scheduledCouponListingFacet.address, ] this.bondUsaAddress = bondUsaFacet.address this.excludeBondAddresses = [ diff --git a/packages/ats/contracts/scripts/deploy.ts b/packages/ats/contracts/scripts/deploy.ts index 3199ab20c..8e27ed38d 100644 --- a/packages/ats/contracts/scripts/deploy.ts +++ b/packages/ats/contracts/scripts/deploy.ts @@ -292,6 +292,8 @@ import { ScheduledBalanceAdjustmentsFacetTimeTravel__factory, ScheduledCrossOrderedTasksFacet__factory, ScheduledCrossOrderedTasksFacetTimeTravel__factory, + ScheduledCouponListingFacet__factory, + ScheduledCouponListingFacetTimeTravel__factory, CorporateActionsFacet__factory, CorporateActionsFacetTimeTravel__factory, TransferAndLockFacet__factory, @@ -782,7 +784,19 @@ export async function deployAtsContracts({ ), signer, deployedContract: useDeployed - ? Configuration.contracts.scheduledCrossOrderedTasksFacet + ? Configuration.contracts.ScheduledCrossOrderedTasksFacet + .addresses?.[network] + : undefined, + overrides, + }), + scheduledCouponListingFacet: new DeployContractWithFactoryCommand({ + factory: getFactory( + new ScheduledCouponListingFacet__factory(), + new ScheduledCouponListingFacetTimeTravel__factory() + ), + signer, + deployedContract: useDeployed + ? Configuration.contracts.ScheduledCouponListingFacet .addresses?.[network] : undefined, overrides, @@ -1288,15 +1302,20 @@ export async function deployAtsContracts({ ) return result }), + scheduledCouponListingFacet: await deployContractWithFactory( + commands.scheduledCouponListingFacet + ).then((result) => { + console.log( + `ScheduledCouponListingFacet has been deployed successfully at ${result.address}` + ) + return result + }), scheduledCrossOrderedTasksFacet: await deployContractWithFactory( commands.scheduledCrossOrderedTasksFacet ).then((result) => { console.log( `ScheduledCrossOrderedTasksFacet has been deployed successfully at ${result.address}` ) - console.log( - `TransferAndLock has been deployed successfully at ${result.address}` - ) return result }), corporateActionsFacet: await deployContractWithFactory( diff --git a/packages/ats/contracts/scripts/deployEnvironmentByRpc.ts b/packages/ats/contracts/scripts/deployEnvironmentByRpc.ts index 88c3e717b..af8ef004c 100644 --- a/packages/ats/contracts/scripts/deployEnvironmentByRpc.ts +++ b/packages/ats/contracts/scripts/deployEnvironmentByRpc.ts @@ -298,6 +298,7 @@ function buildEmptyEnvironment(): Environment { scheduledSnapshotsFacet: {} as IStaticFunctionSelectors, scheduledBalanceAdjustmentsFacet: {} as IStaticFunctionSelectors, scheduledCrossOrderedTasksFacet: {} as IStaticFunctionSelectors, + scheduledCouponListingFacet: {} as IStaticFunctionSelectors, CapFacet: {} as IStaticFunctionSelectors, LockFacet: {} as IStaticFunctionSelectors, transferAndLockFacet: {} as IStaticFunctionSelectors, diff --git a/packages/ats/contracts/scripts/results/DeployAtsContractsResult.ts b/packages/ats/contracts/scripts/results/DeployAtsContractsResult.ts index fcf414473..78ee6c77c 100644 --- a/packages/ats/contracts/scripts/results/DeployAtsContractsResult.ts +++ b/packages/ats/contracts/scripts/results/DeployAtsContractsResult.ts @@ -248,6 +248,7 @@ import { ScheduledBalanceAdjustmentsFacet, ScheduledSnapshotsFacet, ScheduledCrossOrderedTasksFacet, + ScheduledCouponListingFacet, SnapshotsFacet, SsiManagementFacet, TransferAndLockFacet, @@ -281,6 +282,7 @@ export interface DeployAtsContractsResultParams { scheduledSnapshotsFacet: DeployContractWithFactoryResult scheduledBalanceAdjustmentsFacet: DeployContractWithFactoryResult scheduledCrossOrderedTasksFacet: DeployContractWithFactoryResult + scheduledCouponListingFacet: DeployContractWithFactoryResult snapshotsFacet: DeployContractWithFactoryResult corporateActionsFacet: DeployContractWithFactoryResult transferAndLockFacet: DeployContractWithFactoryResult @@ -333,6 +335,7 @@ export default class DeployAtsContractsResult { public readonly scheduledSnapshotsFacet: DeployContractWithFactoryResult public readonly scheduledBalanceAdjustmentsFacet: DeployContractWithFactoryResult public readonly scheduledCrossOrderedTasksFacet: DeployContractWithFactoryResult + public readonly scheduledCouponListingFacet: DeployContractWithFactoryResult public readonly snapshotsFacet: DeployContractWithFactoryResult public readonly corporateActionsFacet: DeployContractWithFactoryResult public readonly transferAndLockFacet: DeployContractWithFactoryResult @@ -384,6 +387,7 @@ export default class DeployAtsContractsResult { scheduledSnapshotsFacet, scheduledBalanceAdjustmentsFacet, scheduledCrossOrderedTasksFacet, + scheduledCouponListingFacet, snapshotsFacet, corporateActionsFacet, transferAndLockFacet, @@ -434,6 +438,7 @@ export default class DeployAtsContractsResult { this.scheduledSnapshotsFacet = scheduledSnapshotsFacet this.scheduledBalanceAdjustmentsFacet = scheduledBalanceAdjustmentsFacet this.scheduledCrossOrderedTasksFacet = scheduledCrossOrderedTasksFacet + this.scheduledCouponListingFacet = scheduledCouponListingFacet this.snapshotsFacet = snapshotsFacet this.corporateActionsFacet = corporateActionsFacet this.transferAndLockFacet = transferAndLockFacet diff --git a/packages/ats/contracts/tasks/deploy.ts b/packages/ats/contracts/tasks/deploy.ts index 5db17d0e4..02ef53356 100644 --- a/packages/ats/contracts/tasks/deploy.ts +++ b/packages/ats/contracts/tasks/deploy.ts @@ -289,6 +289,7 @@ task( scheduledSnapshotsFacet, scheduledBalanceAdjustmentsFacet, scheduledCrossOrderedTasksFacet, + scheduledCouponListingFacet, corporateActionsFacet, lockFacet, holdReadFacet, @@ -354,6 +355,8 @@ task( scheduledBalanceAdjustmentsFacet.address, 'Scheduled Cross Ordered Tasks Facet': scheduledCrossOrderedTasksFacet.address, + 'Scheduled Coupon Listing Facet': + scheduledCouponListingFacet.address, 'Corporate Actions Facet': corporateActionsFacet.address, 'Lock Facet': lockFacet.address, 'Hold Read Facet': holdReadFacet.address, diff --git a/packages/ats/contracts/test/unitTests/layer_1/bond/bond.test.ts b/packages/ats/contracts/test/unitTests/layer_1/bond/bond.test.ts index 4ed615de9..e3640efa8 100644 --- a/packages/ats/contracts/test/unitTests/layer_1/bond/bond.test.ts +++ b/packages/ats/contracts/test/unitTests/layer_1/bond/bond.test.ts @@ -235,6 +235,8 @@ import { BondUSAFacetTimeTravel__factory, FreezeFacet, ClearingTransferFacet, + ScheduledCrossOrderedTasks, + ScheduledCrossOrderedTasks__factory, } from '@typechain' import { CORPORATE_ACTION_ROLE, @@ -326,6 +328,7 @@ describe('Bond Tests', () => { let protectedPartitionsFacet: ProtectedPartitions let freezeFacet: FreezeFacet let clearingTransferFacet: ClearingTransferFacet + let scheduledTasksFacet: ScheduledCrossOrderedTasks function set_initRbacs(): Rbac[] { const rbacPause: Rbac = { @@ -390,6 +393,10 @@ describe('Bond Tests', () => { diamond.address, signer_A ) + scheduledTasksFacet = ScheduledCrossOrderedTasks__factory.connect( + diamond.address, + signer_A + ) await ssiManagementFacet.connect(signer_A).addIssuer(account_A) controlListFacet = await ethers.getContractAt( @@ -987,6 +994,82 @@ describe('Bond Tests', () => { expect(couponHolders).to.have.members([account_A]) }) + it('GIVEN an account with corporateActions role WHEN setCoupon multiple times THEN coupons ordered list properly set up', async () => { + // Granting Role to account C + accessControlFacet = accessControlFacet.connect(signer_A) + await accessControlFacet.grantRole( + CORPORATE_ACTION_ROLE, + account_C + ) + // Using account C (with role) + bondFacet = bondFacet.connect(signer_C) + + const customPeriod = 3 * 24 * 60 * 60 // 3 days in seconds + const DelayCoupon_2 = 100 + const DelayCoupon_3 = 50 + + const customCouponData_1 = { + recordDate: couponRecordDateInSeconds.toString(), + executionDate: couponExecutionDateInSeconds.toString(), + rate: couponRate, + period: customPeriod, + rateDecimals: couponRateDecimals, + } + + const customCouponData_2 = { + recordDate: ( + couponRecordDateInSeconds + DelayCoupon_2 + ).toString(), + executionDate: ( + couponExecutionDateInSeconds + DelayCoupon_2 + ).toString(), + rate: couponRate, + period: customPeriod, + rateDecimals: couponRateDecimals, + } + + const customCouponData_3 = { + recordDate: ( + couponRecordDateInSeconds + DelayCoupon_3 + ).toString(), + executionDate: ( + couponExecutionDateInSeconds + DelayCoupon_3 + ).toString(), + rate: couponRate, + period: customPeriod, + rateDecimals: couponRateDecimals, + } + + const expectedCouponOrderList = [1, 3, 2] + + await bondFacet.setCoupon(customCouponData_1) + await bondFacet.setCoupon(customCouponData_2) + await bondFacet.setCoupon(customCouponData_3) + + await timeTravelFacet.changeSystemTimestamp( + couponRecordDateInSeconds + DelayCoupon_2 + 1 + ) + + await scheduledTasksFacet.triggerPendingScheduledCrossOrderedTasks() + + const couponOrderedListTotal = + await bondReadFacet.getCouponsOrderedListTotal() + const couponOrderedList = + await bondReadFacet.getCouponsOrderedList(0, 1000) + + expect(couponOrderedListTotal).to.equal(3) + expect(couponOrderedList.length).to.equal(3) + + for (let i = 0; i < 3; i++) { + expect(couponOrderedList[i]).to.equal( + expectedCouponOrderList[i] + ) + const couponAt = + await bondReadFacet.getCouponFromOrderedListAt(i) + expect(couponAt).to.equal(expectedCouponOrderList[i]) + } + }) + it('GIVEN an account with bondManager role WHEN setMaturityDate THEN transaction succeeds', async () => { // * Arrange // Granting Role to account C diff --git a/packages/ats/contracts/test/unitTests/layer_1/scheduledTasks/scheduledCouponListing/scheduledCouponListing.test.ts b/packages/ats/contracts/test/unitTests/layer_1/scheduledTasks/scheduledCouponListing/scheduledCouponListing.test.ts new file mode 100644 index 000000000..d16a578ba --- /dev/null +++ b/packages/ats/contracts/test/unitTests/layer_1/scheduledTasks/scheduledCouponListing/scheduledCouponListing.test.ts @@ -0,0 +1,503 @@ +/* + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +import { expect } from 'chai' +import { ethers } from 'hardhat' +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers.js' +import { isinGenerator } from '@thomaschaplin/isin-generator' +import { + type ResolverProxy, + type Bond, + type ScheduledCouponListing, + type AccessControl, + ScheduledCrossOrderedTasks, + TimeTravel, + BusinessLogicResolver, + IFactory, + AccessControl__factory, + Bond__factory, + ScheduledCrossOrderedTasks__factory, + ScheduledCouponListing__factory, + TimeTravel__factory, +} from '@typechain' +import { + CORPORATE_ACTION_ROLE, + PAUSER_ROLE, + deployBondFromFactory, + Rbac, + RegulationSubType, + RegulationType, + deployAtsFullInfrastructure, + DeployAtsFullInfrastructureCommand, + dateToUnixTimestamp, + TIME_PERIODS_S, +} from '@scripts' +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' + +const numberOfUnits = 1000 +let startingDate = 9999999999 +const numberOfCoupons = 50 +const frequency = TIME_PERIODS_S.DAY +const rate = 10 +const rateDecimals = 1 +let maturityDate = startingDate + numberOfCoupons * frequency +let firstCouponDate = startingDate + 1 +const countriesControlListType = true +const listOfCountries = 'ES,FR,CH' +const info = 'info' + +describe('Scheduled Coupon Listing Tests', () => { + let diamond: ResolverProxy + let signer_A: SignerWithAddress + let signer_B: SignerWithAddress + let signer_C: SignerWithAddress + + let account_A: string + let account_B: string + let account_C: string + + let factory: IFactory + let businessLogicResolver: BusinessLogicResolver + let bondFacet: Bond + let scheduledCouponListingFacet: ScheduledCouponListing + let scheduledTasksFacet: ScheduledCrossOrderedTasks + let accessControlFacet: AccessControl + let timeTravelFacet: TimeTravel + + async function deploySecurityFixtureSinglePartition() { + const init_rbacs: Rbac[] = set_initRbacs() + + diamond = await deployBondFromFactory({ + adminAccount: account_A, + isWhiteList: false, + isControllable: true, + arePartitionsProtected: false, + clearingActive: false, + internalKycActivated: true, + isMultiPartition: false, + name: 'TEST_AccessControl', + symbol: 'TAC', + decimals: 6, + isin: isinGenerator(), + currency: '0x455552', + numberOfUnits, + nominalValue: 100, + startingDate, + maturityDate, + couponFrequency: frequency, + couponRate: rate, + couponRateDecimals: rateDecimals, + firstCouponDate, + regulationType: RegulationType.REG_S, + regulationSubType: RegulationSubType.NONE, + countriesControlListType, + listOfCountries, + info, + init_rbacs, + businessLogicResolver: businessLogicResolver.address, + factory, + }) + + await setFacets(diamond) + } + + async function setFacets(diamond: ResolverProxy) { + accessControlFacet = AccessControl__factory.connect( + diamond.address, + signer_A + ) + bondFacet = Bond__factory.connect(diamond.address, signer_A) + scheduledCouponListingFacet = ScheduledCouponListing__factory.connect( + diamond.address, + signer_A + ) + scheduledTasksFacet = ScheduledCrossOrderedTasks__factory.connect( + diamond.address, + signer_A + ) + timeTravelFacet = TimeTravel__factory.connect(diamond.address, signer_A) + } + + function set_initRbacs(): Rbac[] { + const rbacPause: Rbac = { + role: PAUSER_ROLE, + members: [account_B], + } + return [rbacPause] + } + + before(async () => { + //mute | mock console.log + console.log = () => {} + ;[signer_A, signer_B, signer_C] = await ethers.getSigners() + account_A = signer_A.address + account_B = signer_B.address + account_C = signer_C.address + + const { ...deployedContracts } = await deployAtsFullInfrastructure( + await DeployAtsFullInfrastructureCommand.newInstance({ + signer: signer_A, + useDeployed: false, + useEnvironment: true, + timeTravelEnabled: true, + }) + ) + + factory = deployedContracts.factory.contract + businessLogicResolver = deployedContracts.businessLogicResolver.contract + }) + + beforeEach(async () => { + await loadFixture(deploySecurityFixtureSinglePartition) + }) + + afterEach(async () => { + timeTravelFacet.resetSystemTimestamp() + }) + + it('GIVEN a token WHEN triggerCouponListing THEN transaction succeeds', async () => { + await accessControlFacet + .connect(signer_A) + .grantRole(CORPORATE_ACTION_ROLE, account_C) + + // set coupons + const couponsRecordDateInSeconds_1 = dateToUnixTimestamp( + '2030-01-01T00:00:06Z' + ) + const couponsRecordDateInSeconds_2 = dateToUnixTimestamp( + '2030-01-01T00:00:12Z' + ) + const couponsRecordDateInSeconds_3 = dateToUnixTimestamp( + '2030-01-01T00:00:18Z' + ) + const couponsExecutionDateInSeconds = dateToUnixTimestamp( + '2030-01-01T00:01:00Z' + ) + const couponsRate = 1 + const couponRateDecimals = 0 + const couponsPeriod = 10 + + const couponData_1 = { + recordDate: couponsRecordDateInSeconds_1.toString(), + executionDate: couponsExecutionDateInSeconds.toString(), + rate: couponsRate, + period: couponsPeriod, + rateDecimals: couponRateDecimals, + } + const couponData_2 = { + recordDate: couponsRecordDateInSeconds_2.toString(), + executionDate: couponsExecutionDateInSeconds.toString(), + rate: couponsRate, + period: couponsPeriod, + rateDecimals: couponRateDecimals, + } + const couponData_3 = { + recordDate: couponsRecordDateInSeconds_3.toString(), + executionDate: couponsExecutionDateInSeconds.toString(), + rate: couponsRate, + period: couponsPeriod, + rateDecimals: couponRateDecimals, + } + await bondFacet.connect(signer_C).setCoupon(couponData_2) + await bondFacet.connect(signer_C).setCoupon(couponData_3) + await bondFacet.connect(signer_C).setCoupon(couponData_1) + + const coupon_2_Id = + '0x0000000000000000000000000000000000000000000000000000000000000001' + const coupon_3_Id = + '0x0000000000000000000000000000000000000000000000000000000000000002' + const coupon_1_Id = + '0x0000000000000000000000000000000000000000000000000000000000000003' + + // check schedled CouponListing + let scheduledCouponListingCount = + await scheduledCouponListingFacet.scheduledCouponListingCount() + let scheduledCouponListing = + await scheduledCouponListingFacet.getScheduledCouponListing(0, 100) + + expect(scheduledCouponListingCount).to.equal(3) + expect(scheduledCouponListing.length).to.equal( + scheduledCouponListingCount + ) + expect( + scheduledCouponListing[0].scheduledTimestamp.toNumber() + ).to.equal(couponsRecordDateInSeconds_3) + expect(scheduledCouponListing[0].data).to.equal(coupon_3_Id) + expect( + scheduledCouponListing[1].scheduledTimestamp.toNumber() + ).to.equal(couponsRecordDateInSeconds_2) + expect(scheduledCouponListing[1].data).to.equal(coupon_2_Id) + expect( + scheduledCouponListing[2].scheduledTimestamp.toNumber() + ).to.equal(couponsRecordDateInSeconds_1) + expect(scheduledCouponListing[2].data).to.equal(coupon_1_Id) + + // AFTER FIRST SCHEDULED CouponListing ------------------------------------------------------------------ + await timeTravelFacet.changeSystemTimestamp( + couponsRecordDateInSeconds_1 + 1 + ) + await scheduledTasksFacet + .connect(signer_A) + .triggerPendingScheduledCrossOrderedTasks() + + scheduledCouponListingCount = + await scheduledCouponListingFacet.scheduledCouponListingCount() + scheduledCouponListing = + await scheduledCouponListingFacet.getScheduledCouponListing(0, 100) + + expect(scheduledCouponListingCount).to.equal(2) + expect(scheduledCouponListing.length).to.equal( + scheduledCouponListingCount + ) + expect( + scheduledCouponListing[0].scheduledTimestamp.toNumber() + ).to.equal(couponsRecordDateInSeconds_3) + expect(scheduledCouponListing[0].data).to.equal(coupon_3_Id) + expect( + scheduledCouponListing[1].scheduledTimestamp.toNumber() + ).to.equal(couponsRecordDateInSeconds_2) + expect(scheduledCouponListing[1].data).to.equal(coupon_2_Id) + + // AFTER SECOND SCHEDULED CouponListing ------------------------------------------------------------------ + await timeTravelFacet.changeSystemTimestamp( + couponsRecordDateInSeconds_2 + 1 + ) + await scheduledTasksFacet + .connect(signer_A) + .triggerScheduledCrossOrderedTasks(100) + + scheduledCouponListingCount = + await scheduledCouponListingFacet.scheduledCouponListingCount() + scheduledCouponListing = + await scheduledCouponListingFacet.getScheduledCouponListing(0, 100) + + expect(scheduledCouponListingCount).to.equal(1) + expect(scheduledCouponListing.length).to.equal( + scheduledCouponListingCount + ) + expect( + scheduledCouponListing[0].scheduledTimestamp.toNumber() + ).to.equal(couponsRecordDateInSeconds_3) + expect(scheduledCouponListing[0].data).to.equal(coupon_3_Id) + + // AFTER SECOND SCHEDULED CouponListing ------------------------------------------------------------------ + await timeTravelFacet.changeSystemTimestamp( + couponsRecordDateInSeconds_3 + 1 + ) + await scheduledTasksFacet + .connect(signer_A) + .triggerScheduledCrossOrderedTasks(0) + + scheduledCouponListingCount = + await scheduledCouponListingFacet.scheduledCouponListingCount() + scheduledCouponListing = + await scheduledCouponListingFacet.getScheduledCouponListing(0, 100) + + expect(scheduledCouponListingCount).to.equal(0) + expect(scheduledCouponListing.length).to.equal( + scheduledCouponListingCount + ) + }) +}) From 97c331aa637bed25d58f38d7154d40a9d330b2d8 Mon Sep 17 00:00:00 2001 From: Alberto Molina Date: Tue, 23 Sep 2025 15:12:13 +0200 Subject: [PATCH 02/54] refactor: init coupon Signed-off-by: Alberto Molina --- .../contracts/contracts/layer_0/bond/BondStorageWrapper.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/ats/contracts/contracts/layer_0/bond/BondStorageWrapper.sol b/packages/ats/contracts/contracts/layer_0/bond/BondStorageWrapper.sol index 4fe926253..c17d26a73 100644 --- a/packages/ats/contracts/contracts/layer_0/bond/BondStorageWrapper.sol +++ b/packages/ats/contracts/contracts/layer_0/bond/BondStorageWrapper.sol @@ -85,11 +85,12 @@ abstract contract BondStorageWrapper is newCoupon.recordDate, abi.encode(SNAPSHOT_TASK_TYPE) ); + _addScheduledSnapshot(newCoupon.recordDate, abi.encode(_actionId)); + _addScheduledCrossOrderedTask( newCoupon.recordDate, abi.encode(COUPON_LISTING_TASK_TYPE) ); - _addScheduledSnapshot(newCoupon.recordDate, abi.encode(_actionId)); _addScheduledCouponListing(newCoupon.recordDate, abi.encode(_actionId)); } From 97a404db4f10835ad25be78a0c1174ef953964e9 Mon Sep 17 00:00:00 2001 From: Alberto Molina Date: Thu, 25 Sep 2025 09:26:58 +0200 Subject: [PATCH 03/54] feature: bond details with enum and coupon with dates SC Signed-off-by: Alberto Molina --- .../factory/ERC3643/interfaces/IBondRead.sol | 17 +- .../layer_0/bond/BondStorageWrapper.sol | 23 ++- .../contracts/contracts/layer_2/bond/Bond.sol | 27 +-- .../layer_2/interfaces/bond/IBondRead.sol | 17 +- .../interfaces/bond/IBondStorageWrapper.sol | 13 +- packages/ats/contracts/scripts/factory.ts | 13 ++ .../test/unitTests/factory/factory.test.ts | 10 ++ .../unitTests/factory/trex/factory.test.ts | 11 ++ .../beneficiaries/beneficiaries.test.ts | 3 + .../test/unitTests/layer_1/bond/bond.test.ts | 168 +++++++++++++----- .../scheduledCouponListing.test.ts | 25 ++- .../layer_1/securityUSA/securityUSA.test.ts | 5 + 12 files changed, 230 insertions(+), 102 deletions(-) diff --git a/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IBondRead.sol b/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IBondRead.sol index 10a5fca9f..1decba8bb 100644 --- a/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IBondRead.sol +++ b/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IBondRead.sol @@ -2,19 +2,28 @@ pragma solidity ^0.8.17; interface TRexIBondRead { + enum InterestRateTypes { + FIXED_FOR_ALL_COUPONS, + FIXED_PER_COUPON, + KPI_BASED_PER_COUPON + } + struct BondDetailsData { bytes3 currency; uint256 nominalValue; uint256 startingDate; uint256 maturityDate; + InterestRateTypes interestRateType; } struct Coupon { uint256 recordDate; uint256 executionDate; + uint256 startDate; + uint256 endDate; + uint256 fixingDate; uint256 rate; uint8 rateDecimals; - uint256 period; } struct RegisteredCoupon { @@ -24,13 +33,9 @@ interface TRexIBondRead { struct CouponFor { uint256 tokenBalance; - uint256 rate; - uint8 rateDecimals; - uint256 recordDate; - uint256 executionDate; - uint256 period; uint8 decimals; bool recordDateReached; + Coupon coupon; } /** * @notice Retrieves the bond details diff --git a/packages/ats/contracts/contracts/layer_0/bond/BondStorageWrapper.sol b/packages/ats/contracts/contracts/layer_0/bond/BondStorageWrapper.sol index c17d26a73..c56bbf1b1 100644 --- a/packages/ats/contracts/contracts/layer_0/bond/BondStorageWrapper.sol +++ b/packages/ats/contracts/contracts/layer_0/bond/BondStorageWrapper.sol @@ -32,6 +32,7 @@ abstract contract BondStorageWrapper is IBondRead.BondDetailsData bondDetail; bool initialized; uint256[] counponsOrderedListByIds; + IBondRead.InterestRateTypes interestRateType; } /** @@ -45,6 +46,22 @@ abstract contract BondStorageWrapper is _; } + // solhint-disable-next-line func-name-mixedcase + function _initialize_bond( + IBondRead.BondDetailsData calldata _bondDetailsData + ) + internal + validateDates( + _bondDetailsData.startingDate, + _bondDetailsData.maturityDate + ) + onlyValidTimestamp(_bondDetailsData.startingDate) + { + BondDataStorage storage bondStorage = _bondStorage(); + bondStorage.initialized = true; + _storeBondDetails(_bondDetailsData); + } + function _storeBondDetails( IBondRead.BondDetailsData memory _bondDetails ) internal { @@ -187,11 +204,7 @@ abstract contract BondStorageWrapper is _couponID ); - couponFor_.rate = registeredCoupon.coupon.rate; - couponFor_.rateDecimals = registeredCoupon.coupon.rateDecimals; - couponFor_.recordDate = registeredCoupon.coupon.recordDate; - couponFor_.executionDate = registeredCoupon.coupon.executionDate; - couponFor_.period = registeredCoupon.coupon.period; + couponFor_.coupon = registeredCoupon.coupon; if (registeredCoupon.coupon.recordDate < _blockTimestamp()) { couponFor_.recordDateReached = true; diff --git a/packages/ats/contracts/contracts/layer_2/bond/Bond.sol b/packages/ats/contracts/contracts/layer_2/bond/Bond.sol index d0d3bf79f..4c33a340d 100644 --- a/packages/ats/contracts/contracts/layer_2/bond/Bond.sol +++ b/packages/ats/contracts/contracts/layer_2/bond/Bond.sol @@ -53,16 +53,7 @@ abstract contract Bond is IBond, Common { { bytes32 corporateActionID; (success_, corporateActionID, couponID_) = _setCoupon(_newCoupon); - emit CouponSet( - corporateActionID, - couponID_, - _msgSender(), - _newCoupon.recordDate, - _newCoupon.executionDate, - _newCoupon.rate, - _newCoupon.rateDecimals, - _newCoupon.period - ); + emit CouponSet(corporateActionID, couponID_, _msgSender(), _newCoupon); } function updateMaturityDate( @@ -83,20 +74,4 @@ abstract contract Bond is IBond, Common { success_ = _setMaturityDate(_newMaturityDate); return success_; } - - // solhint-disable-next-line func-name-mixedcase - function _initialize_bond( - IBondRead.BondDetailsData calldata _bondDetailsData - ) - internal - validateDates( - _bondDetailsData.startingDate, - _bondDetailsData.maturityDate - ) - onlyValidTimestamp(_bondDetailsData.startingDate) - { - BondDataStorage storage bondStorage = _bondStorage(); - bondStorage.initialized = true; - _storeBondDetails(_bondDetailsData); - } } diff --git a/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBondRead.sol b/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBondRead.sol index cf66e817d..df494adec 100644 --- a/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBondRead.sol +++ b/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBondRead.sol @@ -2,19 +2,28 @@ pragma solidity >=0.8.0 <0.9.0; interface IBondRead { + enum InterestRateTypes { + FIXED_FOR_ALL_COUPONS, + FIXED_PER_COUPON, + KPI_BASED_PER_COUPON + } + struct BondDetailsData { bytes3 currency; uint256 nominalValue; uint256 startingDate; uint256 maturityDate; + InterestRateTypes interestRateType; } struct Coupon { uint256 recordDate; uint256 executionDate; + uint256 startDate; + uint256 endDate; + uint256 fixingDate; uint256 rate; uint8 rateDecimals; - uint256 period; } struct RegisteredCoupon { @@ -24,13 +33,9 @@ interface IBondRead { struct CouponFor { uint256 tokenBalance; - uint256 rate; - uint8 rateDecimals; - uint256 recordDate; - uint256 executionDate; - uint256 period; uint8 decimals; bool recordDateReached; + Coupon coupon; } /** * @notice Retrieves the bond details diff --git a/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBondStorageWrapper.sol b/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBondStorageWrapper.sol index fca1e11fe..3feb6a6e5 100644 --- a/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBondStorageWrapper.sol +++ b/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBondStorageWrapper.sol @@ -1,26 +1,21 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity >=0.8.0 <0.9.0; +import {IBondRead} from './IBondRead.sol'; + interface IBondStorageWrapper { /** * @notice Emitted when a coupon is created or updated for a bond or corporate action. * @param corporateActionId Unique identifier grouping related corporate actions or coupons. * @param couponId Identifier of the created or updated coupon. * @param operator Address that performed the operation. - * @param recordDate Record date timestamp used to determine eligible holders. - * @param executionDate Execution/payment date timestamp for the coupon. - * @param rate Coupon rate or amount expressed in contract-specific units. - * @param period Period length between coupon payments. + * @param coupon Coupon struct containing recordDate, executionDate, rate, and period. */ event CouponSet( bytes32 corporateActionId, uint256 couponId, address indexed operator, - uint256 indexed recordDate, - uint256 indexed executionDate, - uint256 rate, - uint256 rateDecimals, - uint256 period + IBondRead.Coupon coupon ); /** diff --git a/packages/ats/contracts/scripts/factory.ts b/packages/ats/contracts/scripts/factory.ts index 7d3ef3847..f25eb8eeb 100644 --- a/packages/ats/contracts/scripts/factory.ts +++ b/packages/ats/contracts/scripts/factory.ts @@ -265,6 +265,7 @@ export interface BondDetailsData { nominalValue: number startingDate: number maturityDate: number + interestRateType: number } export interface CouponDetailsData { @@ -339,6 +340,12 @@ export const RegulationSubType = { REG_D_506_C: 2, } +export const InterestRateType = { + FIXED_FOR_ALL_COUPONS: 0, + FIXED_PER_COUPON: 1, + KPI_BASED_PER_COUPON: 2, +} + export async function setFactoryRegulationData( regulationType: number, regulationSubType: number, @@ -525,6 +532,7 @@ export async function setBondData({ identityRegistry, beneficiariesList, beneficiariesListData, + interestRateType, }: { adminAccount: string isWhiteList: boolean @@ -557,6 +565,7 @@ export async function setBondData({ identityRegistry?: string beneficiariesList?: string[] beneficiariesListData?: string[] + interestRateType: number }) { let rbacs: Rbac[] = [] @@ -609,6 +618,7 @@ export async function setBondData({ nominalValue: nominalValue, startingDate: startingDate, maturityDate: maturityDate, + interestRateType: interestRateType, } const couponDetails: CouponDetailsData = { @@ -793,6 +803,7 @@ export async function deployBondFromFactory({ identityRegistry, beneficiariesList, beneficiariesListData, + interestRateType, }: { adminAccount: string isWhiteList: boolean @@ -828,6 +839,7 @@ export async function deployBondFromFactory({ identityRegistry?: string beneficiariesList?: string[] beneficiariesListData?: string[] + interestRateType: number }) { const bondData = await setBondData({ adminAccount, @@ -858,6 +870,7 @@ export async function deployBondFromFactory({ identityRegistry, beneficiariesList, beneficiariesListData, + interestRateType, }) const factoryRegulationData = await setFactoryRegulationData( diff --git a/packages/ats/contracts/test/unitTests/factory/factory.test.ts b/packages/ats/contracts/test/unitTests/factory/factory.test.ts index 532c83e08..a82f8bb89 100644 --- a/packages/ats/contracts/test/unitTests/factory/factory.test.ts +++ b/packages/ats/contracts/test/unitTests/factory/factory.test.ts @@ -239,6 +239,7 @@ import { RegulationType, RegulationSubType, TIME_PERIODS_S, + InterestRateType, } from '@scripts' describe('Factory Tests', () => { @@ -287,6 +288,7 @@ describe('Factory Tests', () => { const countriesControlListType = true const listOfCountries = 'ES,FR,CH' const info = 'info' + const interestRateType = InterestRateType.FIXED_PER_COUPON let factory: Factory let businessLogicResolver: BusinessLogicResolver @@ -709,6 +711,7 @@ describe('Factory Tests', () => { init_rbacs, addAdmin: true, businessLogicResolver: ADDRESS_ZERO, + interestRateType, }) const factoryRegulationData = await setFactoryRegulationData( @@ -749,6 +752,7 @@ describe('Factory Tests', () => { init_rbacs, addAdmin: true, businessLogicResolver: businessLogicResolver.address, + interestRateType, }) const factoryRegulationData = await setFactoryRegulationData( @@ -793,6 +797,7 @@ describe('Factory Tests', () => { init_rbacs, addAdmin: false, businessLogicResolver: businessLogicResolver.address, + interestRateType, }) const factoryRegulationData = await setFactoryRegulationData( @@ -835,6 +840,7 @@ describe('Factory Tests', () => { init_rbacs, addAdmin: true, businessLogicResolver: businessLogicResolver.address, + interestRateType, }) const factoryRegulationData = await setFactoryRegulationData( @@ -893,6 +899,7 @@ describe('Factory Tests', () => { init_rbacs, addAdmin: true, businessLogicResolver: businessLogicResolver.address, + interestRateType, }) const factoryRegulationData = await setFactoryRegulationData( @@ -994,6 +1001,7 @@ describe('Factory Tests', () => { init_rbacs, addAdmin: true, businessLogicResolver: businessLogicResolver.address, + interestRateType, }) const factoryRegulationData = await setFactoryRegulationData( @@ -1037,6 +1045,7 @@ describe('Factory Tests', () => { init_rbacs, addAdmin: true, businessLogicResolver: businessLogicResolver.address, + interestRateType, }) const factoryRegulationData = await setFactoryRegulationData( @@ -1086,6 +1095,7 @@ describe('Factory Tests', () => { init_rbacs, addAdmin: true, businessLogicResolver: businessLogicResolver.address, + interestRateType, }) const factoryRegulationData = await setFactoryRegulationData( diff --git a/packages/ats/contracts/test/unitTests/factory/trex/factory.test.ts b/packages/ats/contracts/test/unitTests/factory/trex/factory.test.ts index b01f0fc3a..db371630d 100644 --- a/packages/ats/contracts/test/unitTests/factory/trex/factory.test.ts +++ b/packages/ats/contracts/test/unitTests/factory/trex/factory.test.ts @@ -231,6 +231,7 @@ import { DeployContractWithLibraryCommand, TREX_OWNER_ROLE, dateToUnixTimestamp, + InterestRateType, } from '@scripts' import { deployFullSuiteFixture } from './fixtures/deploy-full-suite.fixture' import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' @@ -276,6 +277,7 @@ describe('TREX Factory Tests', () => { const countriesControlListType = true const listOfCountries = 'ES,FR,CH' const info = 'info' + const interestRateType = InterestRateType.FIXED_PER_COUPON let businessLogicResolver: BusinessLogicResolver let factoryAts: TREXFactoryAts @@ -960,6 +962,7 @@ describe('TREX Factory Tests', () => { init_rbacs, addAdmin: true, businessLogicResolver: businessLogicResolver.address, + interestRateType, }) const factoryRegulationData = await setFactoryRegulationData( @@ -1017,6 +1020,7 @@ describe('TREX Factory Tests', () => { init_rbacs, addAdmin: true, businessLogicResolver: businessLogicResolver.address, + interestRateType, }) const factoryRegulationData = await setFactoryRegulationData( @@ -1067,6 +1071,7 @@ describe('TREX Factory Tests', () => { init_rbacs, addAdmin: true, businessLogicResolver: businessLogicResolver.address, + interestRateType, }) const factoryRegulationData = await setFactoryRegulationData( @@ -1123,6 +1128,7 @@ describe('TREX Factory Tests', () => { init_rbacs, addAdmin: true, businessLogicResolver: businessLogicResolver.address, + interestRateType, }) const factoryRegulationData = await setFactoryRegulationData( @@ -1175,6 +1181,7 @@ describe('TREX Factory Tests', () => { init_rbacs, addAdmin: true, businessLogicResolver: businessLogicResolver.address, + interestRateType, }) const factoryRegulationData = await setFactoryRegulationData( @@ -1228,6 +1235,7 @@ describe('TREX Factory Tests', () => { init_rbacs, addAdmin: true, businessLogicResolver: businessLogicResolver.address, + interestRateType, }) const factoryRegulationData = await setFactoryRegulationData( @@ -1281,6 +1289,7 @@ describe('TREX Factory Tests', () => { init_rbacs, addAdmin: true, businessLogicResolver: businessLogicResolver.address, + interestRateType, }) const factoryRegulationData = await setFactoryRegulationData( @@ -1334,6 +1343,7 @@ describe('TREX Factory Tests', () => { init_rbacs, addAdmin: true, businessLogicResolver: businessLogicResolver.address, + interestRateType, }) const factoryRegulationData = await setFactoryRegulationData( @@ -1407,6 +1417,7 @@ describe('TREX Factory Tests', () => { init_rbacs, addAdmin: true, businessLogicResolver: businessLogicResolver.address, + interestRateType, }) const factoryRegulationData = await setFactoryRegulationData( diff --git a/packages/ats/contracts/test/unitTests/layer_1/beneficiaries/beneficiaries.test.ts b/packages/ats/contracts/test/unitTests/layer_1/beneficiaries/beneficiaries.test.ts index e8d355809..af9bf5e4c 100644 --- a/packages/ats/contracts/test/unitTests/layer_1/beneficiaries/beneficiaries.test.ts +++ b/packages/ats/contracts/test/unitTests/layer_1/beneficiaries/beneficiaries.test.ts @@ -22,6 +22,7 @@ import { RegulationSubType, RegulationType, PAUSER_ROLE, + InterestRateType, } from '@scripts' const BENEFICIARY_1 = '0x1234567890123456789012345678901234567890' @@ -38,6 +39,7 @@ let firstCouponDate = 0 const countriesControlListType = true const listOfCountries = 'ES,FR,CH' const info = 'info' +const interestRateType = InterestRateType.FIXED_PER_COUPON describe('Beneficiaries Tests', () => { let signer_A: SignerWithAddress @@ -102,6 +104,7 @@ describe('Beneficiaries Tests', () => { businessLogicResolver: businessLogicResolver.address, beneficiariesList: [BENEFICIARY_2], beneficiariesListData: [BENEFICIARY_2_DATA], + interestRateType, }) beneficiariesFacet = Beneficiaries__factory.connect( diff --git a/packages/ats/contracts/test/unitTests/layer_1/bond/bond.test.ts b/packages/ats/contracts/test/unitTests/layer_1/bond/bond.test.ts index e3640efa8..fb477614d 100644 --- a/packages/ats/contracts/test/unitTests/layer_1/bond/bond.test.ts +++ b/packages/ats/contracts/test/unitTests/layer_1/bond/bond.test.ts @@ -265,6 +265,7 @@ import { FREEZE_MANAGER_ROLE, EMPTY_HEX_BYTES, TIME_PERIODS_S, + InterestRateType, } from '@scripts' import { grantRoleAndPauseToken } from '@test' import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' @@ -283,12 +284,18 @@ const info = 'info' const amount = numberOfUnits const _PARTITION_ID = '0x0000000000000000000000000000000000000000000000000000000000000002' +const interestRateType = InterestRateType.FIXED_PER_COUPON + +const couponPeriod = TIME_PERIODS_S.WEEK let couponRecordDateInSeconds = 0 let couponExecutionDateInSeconds = 0 +let couponStartDateInSeconds = 0 +let couponEndDateInSeconds = couponPeriod +let couponFixingDateInSeconds = 0 + const couponRate = 50 const couponRateDecimals = 1 -const couponPeriod = TIME_PERIODS_S.WEEK const EMPTY_VC_ID = EMPTY_STRING let couponData = { @@ -296,7 +303,9 @@ let couponData = { executionDate: couponExecutionDateInSeconds.toString(), rate: couponRate, rateDecimals: couponRateDecimals, - period: couponPeriod, + startDate: couponStartDateInSeconds, + endDate: couponEndDateInSeconds, + fixingDate: couponFixingDateInSeconds, } describe('Bond Tests', () => { @@ -466,6 +475,7 @@ describe('Bond Tests', () => { init_rbacs, factory, businessLogicResolver: businessLogicResolver.address, + interestRateType, }) await setFacets({ diamond }) @@ -500,12 +510,17 @@ describe('Bond Tests', () => { couponRecordDateInSeconds = dateToUnixTimestamp(`2030-01-01T00:01:00Z`) couponExecutionDateInSeconds = dateToUnixTimestamp(`2030-01-01T00:10:00Z`) + couponStartDateInSeconds = dateToUnixTimestamp(`2029-12-31T00:10:00Z`) + couponEndDateInSeconds = dateToUnixTimestamp(`2029-12-31T00:30:00Z`) + couponFixingDateInSeconds = dateToUnixTimestamp(`2029-12-31T00:00:00Z`) couponData = { recordDate: couponRecordDateInSeconds.toString(), executionDate: couponExecutionDateInSeconds.toString(), rate: couponRate, rateDecimals: couponRateDecimals, - period: couponPeriod, + startDate: couponStartDateInSeconds, + endDate: couponEndDateInSeconds, + fixingDate: couponFixingDateInSeconds, } await loadFixture(deploySecurityFixtureSinglePartition) }) @@ -708,7 +723,9 @@ describe('Bond Tests', () => { executionDate: couponRecordDateInSeconds.toString(), rate: couponRate, rateDecimals: couponRateDecimals, - period: couponPeriod, + startDate: couponStartDateInSeconds, + endDate: couponEndDateInSeconds, + fixingDate: couponFixingDateInSeconds, } await expect( @@ -722,7 +739,9 @@ describe('Bond Tests', () => { executionDate: couponExecutionDateInSeconds.toString(), rate: couponRate, rateDecimals: couponRateDecimals, - period: couponPeriod, + startDate: couponStartDateInSeconds, + endDate: couponEndDateInSeconds, + fixingDate: couponFixingDateInSeconds, } await expect( @@ -747,7 +766,9 @@ describe('Bond Tests', () => { executionDate: couponExecutionDateInSeconds.toString(), rate: couponRate, rateDecimals: couponRateDecimals, - period: customPeriod, + startDate: couponStartDateInSeconds, + endDate: couponStartDateInSeconds + customPeriod, + fixingDate: couponFixingDateInSeconds, } // Set coupon and verify event includes period @@ -757,20 +778,30 @@ describe('Bond Tests', () => { '0x0000000000000000000000000000000000000000000000000000000000000001', 1, account_C, - couponRecordDateInSeconds, - couponExecutionDateInSeconds, - couponRate, - couponRateDecimals, - customPeriod + [ + couponRecordDateInSeconds, + couponExecutionDateInSeconds, + couponStartDateInSeconds, + couponStartDateInSeconds + customPeriod, + couponFixingDateInSeconds, + couponRate, + couponRateDecimals, + ] ) // Verify coupon data includes period const registeredCoupon = await bondReadFacet.getCoupon(1) - expect(registeredCoupon.coupon.period).to.equal(customPeriod) + expect( + registeredCoupon.coupon.endDate.sub( + registeredCoupon.coupon.startDate + ) + ).to.equal(customPeriod) // Verify couponFor data includes period const couponFor = await bondReadFacet.getCouponFor(1, account_A) - expect(couponFor.period).to.equal(customPeriod) + expect( + couponFor.coupon.endDate.sub(couponFor.coupon.startDate) + ).to.equal(customPeriod) }) it('GIVEN an account with corporateActions role WHEN setCoupon with period 0 THEN transaction succeeds', async () => { @@ -789,7 +820,9 @@ describe('Bond Tests', () => { executionDate: couponExecutionDateInSeconds.toString(), rate: couponRate, rateDecimals: couponRateDecimals, - period: 0, + startDate: couponStartDateInSeconds, + endDate: couponStartDateInSeconds, + fixingDate: couponFixingDateInSeconds, } await expect(bondFacet.setCoupon(minValidPeriodCouponData)) @@ -798,11 +831,15 @@ describe('Bond Tests', () => { '0x0000000000000000000000000000000000000000000000000000000000000001', 1, account_C, - couponRecordDateInSeconds, - couponExecutionDateInSeconds, - couponRate, - couponRateDecimals, - 0 + [ + couponRecordDateInSeconds, + couponExecutionDateInSeconds, + couponStartDateInSeconds, + couponStartDateInSeconds, + couponFixingDateInSeconds, + couponRate, + couponRateDecimals, + ] ) }) @@ -818,11 +855,15 @@ describe('Bond Tests', () => { '0x0000000000000000000000000000000000000000000000000000000000000001', 1, account_C, - couponRecordDateInSeconds, - couponExecutionDateInSeconds, - couponRate, - couponRateDecimals, - couponPeriod + [ + couponRecordDateInSeconds, + couponExecutionDateInSeconds, + couponStartDateInSeconds, + couponEndDateInSeconds, + couponFixingDateInSeconds, + couponRate, + couponRateDecimals, + ] ) // check list members @@ -851,12 +892,26 @@ describe('Bond Tests', () => { ) expect(coupon.coupon.rate).to.equal(couponRate) expect(coupon.coupon.rateDecimals).to.equal(couponRateDecimals) - expect(couponFor.recordDate).to.equal(couponRecordDateInSeconds) - expect(couponFor.executionDate).to.equal( + expect(couponFor.coupon.recordDate).to.equal( + couponRecordDateInSeconds + ) + expect(couponFor.coupon.executionDate).to.equal( couponExecutionDateInSeconds ) - expect(couponFor.rate).to.equal(couponRate) - expect(couponFor.rateDecimals).to.equal(couponRateDecimals) + expect(couponFor.coupon.rate).to.equal(couponRate) + expect(couponFor.coupon.rateDecimals).to.equal( + couponRateDecimals + ) + expect(couponFor.coupon.startDate).to.equal( + couponStartDateInSeconds + ) + expect(couponFor.coupon.endDate).to.equal( + couponEndDateInSeconds + ) + expect(couponFor.coupon.fixingDate).to.equal( + couponFixingDateInSeconds + ) + expect(couponFor.tokenBalance).to.equal(0) expect(couponFor.recordDateReached).to.equal(false) expect(couponTotalHolders).to.equal(0) @@ -896,11 +951,15 @@ describe('Bond Tests', () => { '0x0000000000000000000000000000000000000000000000000000000000000001', 1, account_C, - couponRecordDateInSeconds, - couponExecutionDateInSeconds, - couponRate, - couponRateDecimals, - couponPeriod + [ + couponRecordDateInSeconds, + couponExecutionDateInSeconds, + couponStartDateInSeconds, + couponEndDateInSeconds, + couponFixingDateInSeconds, + couponRate, + couponRateDecimals, + ] ) // check list members @@ -963,11 +1022,15 @@ describe('Bond Tests', () => { '0x0000000000000000000000000000000000000000000000000000000000000001', 1, account_C, - couponRecordDateInSeconds, - couponExecutionDateInSeconds, - couponRate, - couponRateDecimals, - couponPeriod + [ + couponRecordDateInSeconds, + couponExecutionDateInSeconds, + couponStartDateInSeconds, + couponEndDateInSeconds, + couponFixingDateInSeconds, + couponRate, + couponRateDecimals, + ] ) // check list members @@ -1005,6 +1068,8 @@ describe('Bond Tests', () => { bondFacet = bondFacet.connect(signer_C) const customPeriod = 3 * 24 * 60 * 60 // 3 days in seconds + couponStartDateInSeconds = 0 + couponEndDateInSeconds = customPeriod const DelayCoupon_2 = 100 const DelayCoupon_3 = 50 @@ -1012,7 +1077,9 @@ describe('Bond Tests', () => { recordDate: couponRecordDateInSeconds.toString(), executionDate: couponExecutionDateInSeconds.toString(), rate: couponRate, - period: customPeriod, + startDate: couponStartDateInSeconds, + endDate: couponEndDateInSeconds, + fixingDate: couponFixingDateInSeconds, rateDecimals: couponRateDecimals, } @@ -1024,7 +1091,9 @@ describe('Bond Tests', () => { couponExecutionDateInSeconds + DelayCoupon_2 ).toString(), rate: couponRate, - period: customPeriod, + startDate: couponStartDateInSeconds, + endDate: couponEndDateInSeconds, + fixingDate: couponFixingDateInSeconds, rateDecimals: couponRateDecimals, } @@ -1036,7 +1105,9 @@ describe('Bond Tests', () => { couponExecutionDateInSeconds + DelayCoupon_3 ).toString(), rate: couponRate, - period: customPeriod, + startDate: couponStartDateInSeconds, + endDate: couponEndDateInSeconds, + fixingDate: couponFixingDateInSeconds, rateDecimals: couponRateDecimals, } @@ -1252,11 +1323,15 @@ describe('Bond Tests', () => { '0x0000000000000000000000000000000000000000000000000000000000000001', 1, account_C, - couponRecordDateInSeconds, - couponExecutionDateInSeconds, - couponRate, - couponRateDecimals, - couponPeriod + [ + couponRecordDateInSeconds, + couponExecutionDateInSeconds, + couponStartDateInSeconds, + couponEndDateInSeconds, + couponFixingDateInSeconds, + couponRate, + couponRateDecimals, + ] ) // --- Pre: before record date -> tokenBalance should be 0 and not reached @@ -1310,6 +1385,7 @@ describe('Bond Tests', () => { init_rbacs, factory, businessLogicResolver: businessLogicResolver.address, + interestRateType, }) await setFacets({ diamond: newDiamond }) diff --git a/packages/ats/contracts/test/unitTests/layer_1/scheduledTasks/scheduledCouponListing/scheduledCouponListing.test.ts b/packages/ats/contracts/test/unitTests/layer_1/scheduledTasks/scheduledCouponListing/scheduledCouponListing.test.ts index d16a578ba..86406aa53 100644 --- a/packages/ats/contracts/test/unitTests/layer_1/scheduledTasks/scheduledCouponListing/scheduledCouponListing.test.ts +++ b/packages/ats/contracts/test/unitTests/layer_1/scheduledTasks/scheduledCouponListing/scheduledCouponListing.test.ts @@ -233,6 +233,7 @@ import { DeployAtsFullInfrastructureCommand, dateToUnixTimestamp, TIME_PERIODS_S, + InterestRateType, } from '@scripts' import { loadFixture } from '@nomicfoundation/hardhat-network-helpers' @@ -247,6 +248,7 @@ let firstCouponDate = startingDate + 1 const countriesControlListType = true const listOfCountries = 'ES,FR,CH' const info = 'info' +const interestRateType = InterestRateType.FIXED_PER_COUPON describe('Scheduled Coupon Listing Tests', () => { let diamond: ResolverProxy @@ -298,6 +300,7 @@ describe('Scheduled Coupon Listing Tests', () => { init_rbacs, businessLogicResolver: businessLogicResolver.address, factory, + interestRateType, }) await setFacets(diamond) @@ -375,29 +378,43 @@ describe('Scheduled Coupon Listing Tests', () => { const couponsExecutionDateInSeconds = dateToUnixTimestamp( '2030-01-01T00:01:00Z' ) + const couponsStartDateInSeconds = dateToUnixTimestamp( + '2029-12-31T00:00:00Z' + ) + const couponsEndDateInSeconds = dateToUnixTimestamp( + '2029-12-31T00:10:00Z' + ) + const couponsFixingDateInSeconds = dateToUnixTimestamp( + '2029-12-30T00:00:00Z' + ) const couponsRate = 1 const couponRateDecimals = 0 - const couponsPeriod = 10 const couponData_1 = { recordDate: couponsRecordDateInSeconds_1.toString(), executionDate: couponsExecutionDateInSeconds.toString(), rate: couponsRate, - period: couponsPeriod, + startDate: couponsStartDateInSeconds, + endDate: couponsEndDateInSeconds, + fixingDate: couponsFixingDateInSeconds, rateDecimals: couponRateDecimals, } const couponData_2 = { recordDate: couponsRecordDateInSeconds_2.toString(), executionDate: couponsExecutionDateInSeconds.toString(), rate: couponsRate, - period: couponsPeriod, + startDate: couponsStartDateInSeconds, + endDate: couponsEndDateInSeconds, + fixingDate: couponsFixingDateInSeconds, rateDecimals: couponRateDecimals, } const couponData_3 = { recordDate: couponsRecordDateInSeconds_3.toString(), executionDate: couponsExecutionDateInSeconds.toString(), rate: couponsRate, - period: couponsPeriod, + startDate: couponsStartDateInSeconds, + endDate: couponsEndDateInSeconds, + fixingDate: couponsFixingDateInSeconds, rateDecimals: couponRateDecimals, } await bondFacet.connect(signer_C).setCoupon(couponData_2) diff --git a/packages/ats/contracts/test/unitTests/layer_1/securityUSA/securityUSA.test.ts b/packages/ats/contracts/test/unitTests/layer_1/securityUSA/securityUSA.test.ts index bcc269c07..64bc9d660 100644 --- a/packages/ats/contracts/test/unitTests/layer_1/securityUSA/securityUSA.test.ts +++ b/packages/ats/contracts/test/unitTests/layer_1/securityUSA/securityUSA.test.ts @@ -218,6 +218,7 @@ import { DeployAtsFullInfrastructureCommand, MAX_UINT256, TIME_PERIODS_S, + InterestRateType, } from '@scripts' const countriesControlListType = true @@ -235,6 +236,7 @@ const rate = 1 const rateDecimals = 0 let maturityDate = startingDate + numberOfCoupons * frequency let firstCouponDate = startingDate + 1 +const interestRateType = InterestRateType.FIXED_PER_COUPON describe('Security USA Tests', () => { let signer_A: SignerWithAddress @@ -531,6 +533,7 @@ describe('Security USA Tests', () => { init_rbacs, businessLogicResolver: businessLogicResolver.address, factory, + interestRateType, }) bondUSAFacet = await ethers.getContractAt( @@ -603,6 +606,7 @@ describe('Security USA Tests', () => { init_rbacs, businessLogicResolver: businessLogicResolver.address, factory, + interestRateType, }) bondUSAFacet = await ethers.getContractAt( @@ -674,6 +678,7 @@ describe('Security USA Tests', () => { init_rbacs, businessLogicResolver: businessLogicResolver.address, factory, + interestRateType, }) bondUSAFacet = await ethers.getContractAt( From 9020d960b5e93ea88e1719affe7061020676558a Mon Sep 17 00:00:00 2001 From: Alberto Molina Date: Thu, 25 Sep 2025 11:40:59 +0200 Subject: [PATCH 04/54] refactor: success removed from corporateActions and bondDetailsData updated Signed-off-by: Alberto Molina --- .../factory/ERC3643/interfaces/IBondRead.sol | 6 +- .../factory/ERC3643/interfaces/IEquity.sol | 6 +- .../factory/ERC3643/interfaces/IFactory.sol | 1 + .../contracts/contracts/factory/Factory.sol | 1 + .../contracts/interfaces/factory/IFactory.sol | 1 + .../layer_0/bond/BondStorageWrapper.sol | 53 ++++++++---- .../CorporateActionsStorageWrapper.sol | 8 +- .../layer_0/equity/EquityStorageWrapper.sol | 54 ++++-------- .../corporateActions/CorporateActions.sol | 12 ++- .../corporateActions/ICorporateActions.sol | 1 - .../contracts/contracts/layer_2/bond/Bond.sol | 7 +- .../contracts/layer_2/bond/BondRead.sol | 9 ++ .../contracts/layer_2/equity/Equity.sol | 13 ++- .../layer_2/interfaces/bond/IBond.sol | 2 +- .../layer_2/interfaces/bond/IBondRead.sol | 6 +- .../layer_2/interfaces/equity/IEquity.sol | 6 +- .../contracts/layer_3/bondUSA/BondUSA.sol | 3 +- .../contracts/layer_3/bondUSA/BondUSARead.sol | 5 +- .../contracts/layer_3/interfaces/IBondUSA.sol | 1 + packages/ats/contracts/scripts/factory.ts | 4 +- .../test/unitTests/factory/factory.test.ts | 3 + .../test/unitTests/layer_1/bond/bond.test.ts | 83 +++++++++++++------ .../scheduledCouponListing.test.ts | 62 +++++++------- 23 files changed, 203 insertions(+), 144 deletions(-) diff --git a/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IBondRead.sol b/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IBondRead.sol index 1decba8bb..c190d0d86 100644 --- a/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IBondRead.sol +++ b/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IBondRead.sol @@ -13,7 +13,6 @@ interface TRexIBondRead { uint256 nominalValue; uint256 startingDate; uint256 maturityDate; - InterestRateTypes interestRateType; } struct Coupon { @@ -45,6 +44,11 @@ interface TRexIBondRead { view returns (BondDetailsData memory bondDetailsData_); + function getInterestRateType() + external + view + returns (InterestRateTypes interestRateType_); + /** * @notice Retrieves a registered coupon by its ID */ diff --git a/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IEquity.sol b/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IEquity.sol index 3a092e989..0261aa3d5 100644 --- a/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IEquity.sol +++ b/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IEquity.sol @@ -71,7 +71,7 @@ interface TRexIEquity { */ function setDividends( Dividend calldata _newDividend - ) external returns (bool success_, uint256 dividendID_); + ) external returns (uint256 dividendID_); /** * @notice Sets a new voting @@ -79,7 +79,7 @@ interface TRexIEquity { */ function setVoting( Voting calldata _newVoting - ) external returns (bool success_, uint256 voteID_); + ) external returns (uint256 voteID_); /** * @notice Sets a new scheduled balance adjustment @@ -87,7 +87,7 @@ interface TRexIEquity { */ function setScheduledBalanceAdjustment( ScheduledBalanceAdjustment calldata _newBalanceAdjustment - ) external returns (bool success_, uint256 balanceAdjustmentID_); + ) external returns (uint256 balanceAdjustmentID_); function getEquityDetails() external diff --git a/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IFactory.sol b/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IFactory.sol index 7fb387a6a..34c9297ac 100644 --- a/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IFactory.sol +++ b/packages/ats/contracts/contracts/factory/ERC3643/interfaces/IFactory.sol @@ -55,6 +55,7 @@ interface TRexIFactory { struct BondData { SecurityData security; IBondRead.BondDetailsData bondDetails; + IBondRead.InterestRateTypes interestRateType; address[] beneficiaries; bytes[] beneficiariesData; } diff --git a/packages/ats/contracts/contracts/factory/Factory.sol b/packages/ats/contracts/contracts/factory/Factory.sol index 4842da578..f5e16f9dd 100644 --- a/packages/ats/contracts/contracts/factory/Factory.sol +++ b/packages/ats/contracts/contracts/factory/Factory.sol @@ -159,6 +159,7 @@ contract Factory is IFactory, LocalContext { IBondUSA(bondAddress_)._initialize_bondUSA( _bondData.bondDetails, + _bondData.interestRateType, buildRegulationData( _factoryRegulationData.regulationType, _factoryRegulationData.regulationSubType diff --git a/packages/ats/contracts/contracts/interfaces/factory/IFactory.sol b/packages/ats/contracts/contracts/interfaces/factory/IFactory.sol index 5a214034d..cda78c62c 100644 --- a/packages/ats/contracts/contracts/interfaces/factory/IFactory.sol +++ b/packages/ats/contracts/contracts/interfaces/factory/IFactory.sol @@ -53,6 +53,7 @@ interface IFactory { struct BondData { SecurityData security; IBondRead.BondDetailsData bondDetails; + IBondRead.InterestRateTypes interestRateType; address[] beneficiaries; bytes[] beneficiariesData; } diff --git a/packages/ats/contracts/contracts/layer_0/bond/BondStorageWrapper.sol b/packages/ats/contracts/contracts/layer_0/bond/BondStorageWrapper.sol index c56bbf1b1..36aeb240e 100644 --- a/packages/ats/contracts/contracts/layer_0/bond/BondStorageWrapper.sol +++ b/packages/ats/contracts/contracts/layer_0/bond/BondStorageWrapper.sol @@ -48,7 +48,8 @@ abstract contract BondStorageWrapper is // solhint-disable-next-line func-name-mixedcase function _initialize_bond( - IBondRead.BondDetailsData calldata _bondDetailsData + IBondRead.BondDetailsData calldata _bondDetailsData, + IBondRead.InterestRateTypes _interestRateType ) internal validateDates( @@ -60,6 +61,7 @@ abstract contract BondStorageWrapper is BondDataStorage storage bondStorage = _bondStorage(); bondStorage.initialized = true; _storeBondDetails(_bondDetailsData); + _setInterestRateType(_interestRateType); } function _storeBondDetails( @@ -68,28 +70,27 @@ abstract contract BondStorageWrapper is _bondStorage().bondDetail = _bondDetails; } + function _setInterestRateType( + IBondRead.InterestRateTypes _interestRateType + ) internal { + _bondStorage().interestRateType = _interestRateType; + } + function _setCoupon( IBondRead.Coupon memory _newCoupon - ) - internal - returns (bool success_, bytes32 corporateActionId_, uint256 couponID_) - { + ) internal returns (bytes32 corporateActionId_, uint256 couponID_) { bytes memory data = abi.encode(_newCoupon); - (success_, corporateActionId_, couponID_) = _addCorporateAction( + (corporateActionId_, couponID_) = _addCorporateAction( COUPON_CORPORATE_ACTION_TYPE, data ); - _initCoupon(success_, corporateActionId_, data); + _initCoupon(corporateActionId_, data); } - function _initCoupon( - bool _success, - bytes32 _actionId, - bytes memory _data - ) internal { - if (!_success) { + function _initCoupon(bytes32 _actionId, bytes memory _data) internal { + if (_actionId == bytes32(0)) { revert IBondStorageWrapper.CouponCreationFailed(); } @@ -104,11 +105,19 @@ abstract contract BondStorageWrapper is ); _addScheduledSnapshot(newCoupon.recordDate, abi.encode(_actionId)); - _addScheduledCrossOrderedTask( - newCoupon.recordDate, - abi.encode(COUPON_LISTING_TASK_TYPE) - ); - _addScheduledCouponListing(newCoupon.recordDate, abi.encode(_actionId)); + if ( + _getInterestRateType() == + IBondRead.InterestRateTypes.KPI_BASED_PER_COUPON + ) { + _addScheduledCrossOrderedTask( + newCoupon.fixingDate, + abi.encode(COUPON_LISTING_TASK_TYPE) + ); + _addScheduledCouponListing( + newCoupon.fixingDate, + abi.encode(_actionId) + ); + } } /** @@ -173,6 +182,14 @@ abstract contract BondStorageWrapper is return _bondStorage().bondDetail.maturityDate; } + function _getInterestRateType() + internal + view + returns (IBondRead.InterestRateTypes interestRateType_) + { + return _bondStorage().interestRateType; + } + function _getCoupon( uint256 _couponID ) diff --git a/packages/ats/contracts/contracts/layer_0/corporateActions/CorporateActionsStorageWrapper.sol b/packages/ats/contracts/contracts/layer_0/corporateActions/CorporateActionsStorageWrapper.sol index b4f131bd7..b9f5a6a14 100644 --- a/packages/ats/contracts/contracts/layer_0/corporateActions/CorporateActionsStorageWrapper.sol +++ b/packages/ats/contracts/contracts/layer_0/corporateActions/CorporateActionsStorageWrapper.sol @@ -35,7 +35,6 @@ abstract contract CorporateActionsStorageWrapper is ClearingStorageWrapper1 { ) internal returns ( - bool success_, bytes32 corporateActionId_, uint256 corporateActionIndexByType_ ) @@ -44,11 +43,14 @@ abstract contract CorporateActionsStorageWrapper is ClearingStorageWrapper1 { storage corporateActions_ = _corporateActionsStorage(); corporateActionId_ = bytes32(corporateActions_.actions.length() + 1); // TODO: Review when it can return false. - success_ = - corporateActions_.actions.add(corporateActionId_) && + bool success = corporateActions_.actions.add(corporateActionId_) && corporateActions_.actionsByType[_actionType].add( corporateActionId_ ); + + if (!success) { + return (bytes32(0), 0); + } corporateActions_ .actionsData[corporateActionId_] .actionType = _actionType; diff --git a/packages/ats/contracts/contracts/layer_0/equity/EquityStorageWrapper.sol b/packages/ats/contracts/contracts/layer_0/equity/EquityStorageWrapper.sol index 06f9a7ec9..ef4c703f7 100644 --- a/packages/ats/contracts/contracts/layer_0/equity/EquityStorageWrapper.sol +++ b/packages/ats/contracts/contracts/layer_0/equity/EquityStorageWrapper.sol @@ -40,26 +40,19 @@ abstract contract EquityStorageWrapper is function _setDividends( IEquity.Dividend calldata _newDividend - ) - internal - returns (bool success_, bytes32 corporateActionId_, uint256 dividendId_) - { + ) internal returns (bytes32 corporateActionId_, uint256 dividendId_) { bytes memory data = abi.encode(_newDividend); - (success_, corporateActionId_, dividendId_) = _addCorporateAction( + (corporateActionId_, dividendId_) = _addCorporateAction( DIVIDEND_CORPORATE_ACTION_TYPE, data ); - _initDividend(success_, corporateActionId_, data); + _initDividend(corporateActionId_, data); } - function _initDividend( - bool _success, - bytes32 _actionId, - bytes memory _data - ) internal { - if (!_success) { + function _initDividend(bytes32 _actionId, bytes memory _data) internal { + if (_actionId == bytes32(0)) { revert IEquityStorageWrapper.DividendCreationFailed(); } @@ -77,26 +70,19 @@ abstract contract EquityStorageWrapper is function _setVoting( IEquity.Voting calldata _newVoting - ) - internal - returns (bool success_, bytes32 corporateActionId_, uint256 voteID_) - { + ) internal returns (bytes32 corporateActionId_, uint256 voteID_) { bytes memory data = abi.encode(_newVoting); - (success_, corporateActionId_, voteID_) = _addCorporateAction( + (corporateActionId_, voteID_) = _addCorporateAction( VOTING_RIGHTS_CORPORATE_ACTION_TYPE, data ); - _initVotingRights(success_, corporateActionId_, data); + _initVotingRights(corporateActionId_, data); } - function _initVotingRights( - bool _success, - bytes32 _actionId, - bytes memory _data - ) internal { - if (!_success) { + function _initVotingRights(bytes32 _actionId, bytes memory _data) internal { + if (_actionId == bytes32(0)) { revert IEquityStorageWrapper.VotingRightsCreationFailed(); } @@ -113,29 +99,23 @@ abstract contract EquityStorageWrapper is IEquity.ScheduledBalanceAdjustment calldata _newBalanceAdjustment ) internal - returns ( - bool success_, - bytes32 corporateActionId_, - uint256 balanceAdjustmentID_ - ) + returns (bytes32 corporateActionId_, uint256 balanceAdjustmentID_) { bytes memory data = abi.encode(_newBalanceAdjustment); - ( - success_, - corporateActionId_, - balanceAdjustmentID_ - ) = _addCorporateAction(BALANCE_ADJUSTMENT_CORPORATE_ACTION_TYPE, data); + (corporateActionId_, balanceAdjustmentID_) = _addCorporateAction( + BALANCE_ADJUSTMENT_CORPORATE_ACTION_TYPE, + data + ); - _initBalanceAdjustment(success_, corporateActionId_, data); + _initBalanceAdjustment(corporateActionId_, data); } function _initBalanceAdjustment( - bool _success, bytes32 _actionId, bytes memory _data ) internal { - if (!_success) { + if (_actionId == bytes32(0)) { revert IEquityStorageWrapper.BalanceAdjustmentCreationFailed(); } diff --git a/packages/ats/contracts/contracts/layer_1/corporateActions/CorporateActions.sol b/packages/ats/contracts/contracts/layer_1/corporateActions/CorporateActions.sol index b5be7f082..2a482f8cc 100644 --- a/packages/ats/contracts/contracts/layer_1/corporateActions/CorporateActions.sol +++ b/packages/ats/contracts/contracts/layer_1/corporateActions/CorporateActions.sol @@ -17,18 +17,16 @@ abstract contract CorporateActions is ICorporateActions, Common { onlyUnpaused onlyRole(_CORPORATE_ACTION_ROLE) returns ( - bool success_, bytes32 corporateActionId_, uint256 corporateActionIndexByType_ ) { - ( - success_, - corporateActionId_, - corporateActionIndexByType_ - ) = _addCorporateAction(_actionType, _data); + (corporateActionId_, corporateActionIndexByType_) = _addCorporateAction( + _actionType, + _data + ); - if (!success_) { + if (corporateActionId_ == bytes32(0)) { revert DuplicatedCorporateAction(_actionType, _data); } emit CorporateActionAdded( diff --git a/packages/ats/contracts/contracts/layer_1/interfaces/corporateActions/ICorporateActions.sol b/packages/ats/contracts/contracts/layer_1/interfaces/corporateActions/ICorporateActions.sol index b06d7586a..59a825d72 100644 --- a/packages/ats/contracts/contracts/layer_1/interfaces/corporateActions/ICorporateActions.sol +++ b/packages/ats/contracts/contracts/layer_1/interfaces/corporateActions/ICorporateActions.sol @@ -27,7 +27,6 @@ interface ICorporateActions { ) external returns ( - bool success_, bytes32 corporateActionId_, uint256 corporateActionIndexByType_ ); diff --git a/packages/ats/contracts/contracts/layer_2/bond/Bond.sol b/packages/ats/contracts/contracts/layer_2/bond/Bond.sol index 4c33a340d..7b37d470d 100644 --- a/packages/ats/contracts/contracts/layer_2/bond/Bond.sol +++ b/packages/ats/contracts/contracts/layer_2/bond/Bond.sol @@ -47,12 +47,15 @@ abstract contract Bond is IBond, Common { override onlyUnpaused onlyRole(_CORPORATE_ACTION_ROLE) + validateDates(_newCoupon.startDate, _newCoupon.endDate) validateDates(_newCoupon.recordDate, _newCoupon.executionDate) + validateDates(_newCoupon.fixingDate, _newCoupon.executionDate) onlyValidTimestamp(_newCoupon.recordDate) - returns (bool success_, uint256 couponID_) + onlyValidTimestamp(_newCoupon.fixingDate) + returns (uint256 couponID_) { bytes32 corporateActionID; - (success_, corporateActionID, couponID_) = _setCoupon(_newCoupon); + (corporateActionID, couponID_) = _setCoupon(_newCoupon); emit CouponSet(corporateActionID, couponID_, _msgSender(), _newCoupon); } diff --git a/packages/ats/contracts/contracts/layer_2/bond/BondRead.sol b/packages/ats/contracts/contracts/layer_2/bond/BondRead.sol index 7089a549e..e49e81bdf 100644 --- a/packages/ats/contracts/contracts/layer_2/bond/BondRead.sol +++ b/packages/ats/contracts/contracts/layer_2/bond/BondRead.sol @@ -18,6 +18,15 @@ abstract contract BondRead is IBondRead, IStaticFunctionSelectors, Common { return _getBondDetails(); } + function getInterestRateType() + external + view + override + returns (InterestRateTypes interestRateType_) + { + return _getInterestRateType(); + } + function getCoupon( uint256 _couponID ) diff --git a/packages/ats/contracts/contracts/layer_2/equity/Equity.sol b/packages/ats/contracts/contracts/layer_2/equity/Equity.sol index 5f9c583f7..57b88de02 100644 --- a/packages/ats/contracts/contracts/layer_2/equity/Equity.sol +++ b/packages/ats/contracts/contracts/layer_2/equity/Equity.sol @@ -25,12 +25,10 @@ abstract contract Equity is IEquity, Common { onlyRole(_CORPORATE_ACTION_ROLE) validateDates(_newDividend.recordDate, _newDividend.executionDate) onlyValidTimestamp(_newDividend.recordDate) - returns (bool success_, uint256 dividendID_) + returns (uint256 dividendID_) { bytes32 corporateActionID; - (success_, corporateActionID, dividendID_) = _setDividends( - _newDividend - ); + (corporateActionID, dividendID_) = _setDividends(_newDividend); emit DividendSet( corporateActionID, dividendID_, @@ -49,10 +47,10 @@ abstract contract Equity is IEquity, Common { onlyUnpaused onlyRole(_CORPORATE_ACTION_ROLE) onlyValidTimestamp(_newVoting.recordDate) - returns (bool success_, uint256 voteID_) + returns (uint256 voteID_) { bytes32 corporateActionID; - (success_, corporateActionID, voteID_) = _setVoting(_newVoting); + (corporateActionID, voteID_) = _setVoting(_newVoting); emit VotingSet( corporateActionID, voteID_, @@ -71,11 +69,10 @@ abstract contract Equity is IEquity, Common { onlyRole(_CORPORATE_ACTION_ROLE) onlyValidTimestamp(_newBalanceAdjustment.executionDate) validateFactor(_newBalanceAdjustment.factor) - returns (bool success_, uint256 balanceAdjustmentID_) + returns (uint256 balanceAdjustmentID_) { bytes32 corporateActionID; ( - success_, corporateActionID, balanceAdjustmentID_ ) = _setScheduledBalanceAdjustment(_newBalanceAdjustment); diff --git a/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBond.sol b/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBond.sol index 8b0daacbb..99435b328 100644 --- a/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBond.sol +++ b/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBond.sol @@ -21,7 +21,7 @@ interface IBond { */ function setCoupon( IBondRead.Coupon calldata _newCoupon - ) external returns (bool success_, uint256 couponID_); + ) external returns (uint256 couponID_); /** * @notice Updates the maturity date of the bond. diff --git a/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBondRead.sol b/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBondRead.sol index df494adec..a6f986309 100644 --- a/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBondRead.sol +++ b/packages/ats/contracts/contracts/layer_2/interfaces/bond/IBondRead.sol @@ -13,7 +13,6 @@ interface IBondRead { uint256 nominalValue; uint256 startingDate; uint256 maturityDate; - InterestRateTypes interestRateType; } struct Coupon { @@ -45,6 +44,11 @@ interface IBondRead { view returns (BondDetailsData memory bondDetailsData_); + function getInterestRateType() + external + view + returns (InterestRateTypes interestRateType_); + /** * @notice Retrieves a registered coupon by its ID */ diff --git a/packages/ats/contracts/contracts/layer_2/interfaces/equity/IEquity.sol b/packages/ats/contracts/contracts/layer_2/interfaces/equity/IEquity.sol index de7b04cca..5725ddcf8 100644 --- a/packages/ats/contracts/contracts/layer_2/interfaces/equity/IEquity.sol +++ b/packages/ats/contracts/contracts/layer_2/interfaces/equity/IEquity.sol @@ -71,7 +71,7 @@ interface IEquity { */ function setDividends( Dividend calldata _newDividend - ) external returns (bool success_, uint256 dividendID_); + ) external returns (uint256 dividendID_); /** * @notice Sets a new voting @@ -79,7 +79,7 @@ interface IEquity { */ function setVoting( Voting calldata _newVoting - ) external returns (bool success_, uint256 voteID_); + ) external returns (uint256 voteID_); /** * @notice Sets a new scheduled balance adjustment @@ -87,7 +87,7 @@ interface IEquity { */ function setScheduledBalanceAdjustment( ScheduledBalanceAdjustment calldata _newBalanceAdjustment - ) external returns (bool success_, uint256 balanceAdjustmentID_); + ) external returns (uint256 balanceAdjustmentID_); function getEquityDetails() external diff --git a/packages/ats/contracts/contracts/layer_3/bondUSA/BondUSA.sol b/packages/ats/contracts/contracts/layer_3/bondUSA/BondUSA.sol index c31ff52b7..5fa90523b 100644 --- a/packages/ats/contracts/contracts/layer_3/bondUSA/BondUSA.sol +++ b/packages/ats/contracts/contracts/layer_3/bondUSA/BondUSA.sol @@ -14,10 +14,11 @@ abstract contract BondUSA is IBondUSA, Bond { // solhint-disable-next-line private-vars-leading-underscore function _initialize_bondUSA( IBondRead.BondDetailsData calldata _bondDetailsData, + IBondRead.InterestRateTypes _interestRateType, RegulationData memory _regulationData, AdditionalSecurityData calldata _additionalSecurityData ) external override onlyUninitialized(_bondStorage().initialized) { - _initialize_bond(_bondDetailsData); + _initialize_bond(_bondDetailsData, _interestRateType); _initializeSecurity(_regulationData, _additionalSecurityData); } } diff --git a/packages/ats/contracts/contracts/layer_3/bondUSA/BondUSARead.sol b/packages/ats/contracts/contracts/layer_3/bondUSA/BondUSARead.sol index 667dd53c8..399837a9e 100644 --- a/packages/ats/contracts/contracts/layer_3/bondUSA/BondUSARead.sol +++ b/packages/ats/contracts/contracts/layer_3/bondUSA/BondUSARead.sol @@ -26,10 +26,13 @@ contract BondUSARead is BondRead, Security { returns (bytes4[] memory staticFunctionSelectors_) { uint256 selectorIndex; - staticFunctionSelectors_ = new bytes4[](12); + staticFunctionSelectors_ = new bytes4[](13); staticFunctionSelectors_[selectorIndex++] = this .getBondDetails .selector; + staticFunctionSelectors_[selectorIndex++] = this + .getInterestRateType + .selector; staticFunctionSelectors_[selectorIndex++] = this.getCoupon.selector; staticFunctionSelectors_[selectorIndex++] = this.getCouponFor.selector; staticFunctionSelectors_[selectorIndex++] = this diff --git a/packages/ats/contracts/contracts/layer_3/interfaces/IBondUSA.sol b/packages/ats/contracts/contracts/layer_3/interfaces/IBondUSA.sol index ce033dacf..92459091b 100644 --- a/packages/ats/contracts/contracts/layer_3/interfaces/IBondUSA.sol +++ b/packages/ats/contracts/contracts/layer_3/interfaces/IBondUSA.sol @@ -13,6 +13,7 @@ interface IBondUSA is IBond { // solhint-disable-next-line private-vars-leading-underscore function _initialize_bondUSA( IBondRead.BondDetailsData calldata _bondDetailsData, + IBondRead.InterestRateTypes _interestRateType, RegulationData memory _regulationData, AdditionalSecurityData calldata _additionalSecurityData ) external; diff --git a/packages/ats/contracts/scripts/factory.ts b/packages/ats/contracts/scripts/factory.ts index f25eb8eeb..7d2fe1492 100644 --- a/packages/ats/contracts/scripts/factory.ts +++ b/packages/ats/contracts/scripts/factory.ts @@ -265,7 +265,6 @@ export interface BondDetailsData { nominalValue: number startingDate: number maturityDate: number - interestRateType: number } export interface CouponDetailsData { @@ -311,6 +310,7 @@ export interface EquityData { export interface BondData { security: SecurityData bondDetails: BondDetailsData + interestRateType: number couponDetails: CouponDetailsData beneficiaries: string[] beneficiariesData: string[] @@ -618,7 +618,6 @@ export async function setBondData({ nominalValue: nominalValue, startingDate: startingDate, maturityDate: maturityDate, - interestRateType: interestRateType, } const couponDetails: CouponDetailsData = { @@ -634,6 +633,7 @@ export async function setBondData({ const bondData: BondData = { security, bondDetails, + interestRateType, couponDetails, beneficiaries, beneficiariesData, diff --git a/packages/ats/contracts/test/unitTests/factory/factory.test.ts b/packages/ats/contracts/test/unitTests/factory/factory.test.ts index a82f8bb89..4302bbbe4 100644 --- a/packages/ats/contracts/test/unitTests/factory/factory.test.ts +++ b/packages/ats/contracts/test/unitTests/factory/factory.test.ts @@ -1169,6 +1169,9 @@ describe('Factory Tests', () => { bondData.bondDetails.maturityDate ) + const interestRateTypeData = await bondFacet.getInterestRateType() + expect(interestRateTypeData).to.be.equal(interestRateType) + const couponCount = await bondFacet.getCouponCount() expect(couponCount).to.equal(0) }) diff --git a/packages/ats/contracts/test/unitTests/layer_1/bond/bond.test.ts b/packages/ats/contracts/test/unitTests/layer_1/bond/bond.test.ts index fb477614d..6f09ba685 100644 --- a/packages/ats/contracts/test/unitTests/layer_1/bond/bond.test.ts +++ b/packages/ats/contracts/test/unitTests/layer_1/bond/bond.test.ts @@ -303,9 +303,9 @@ let couponData = { executionDate: couponExecutionDateInSeconds.toString(), rate: couponRate, rateDecimals: couponRateDecimals, - startDate: couponStartDateInSeconds, - endDate: couponEndDateInSeconds, - fixingDate: couponFixingDateInSeconds, + startDate: couponStartDateInSeconds.toString(), + endDate: couponEndDateInSeconds.toString(), + fixingDate: couponFixingDateInSeconds.toString(), } describe('Bond Tests', () => { @@ -477,8 +477,6 @@ describe('Bond Tests', () => { businessLogicResolver: businessLogicResolver.address, interestRateType, }) - - await setFacets({ diamond }) } before(async () => { @@ -518,11 +516,12 @@ describe('Bond Tests', () => { executionDate: couponExecutionDateInSeconds.toString(), rate: couponRate, rateDecimals: couponRateDecimals, - startDate: couponStartDateInSeconds, - endDate: couponEndDateInSeconds, - fixingDate: couponFixingDateInSeconds, + startDate: couponStartDateInSeconds.toString(), + endDate: couponEndDateInSeconds.toString(), + fixingDate: couponFixingDateInSeconds.toString(), } await loadFixture(deploySecurityFixtureSinglePartition) + await setFacets({ diamond }) }) afterEach(async () => { @@ -1058,6 +1057,42 @@ describe('Bond Tests', () => { }) it('GIVEN an account with corporateActions role WHEN setCoupon multiple times THEN coupons ordered list properly set up', async () => { + const init_rbacs: Rbac[] = set_initRbacs() + + const newDiamond = await deployBondFromFactory({ + adminAccount: account_A, + isWhiteList: false, + isControllable: true, + arePartitionsProtected: false, + clearingActive: false, + internalKycActivated: true, + isMultiPartition: true, + name: 'TEST_AccessControl', + symbol: 'TAC', + decimals: 6, + isin: isinGenerator(), + currency: '0x455552', + numberOfUnits, + nominalValue: 100, + startingDate, + maturityDate, + couponFrequency: frequency, + couponRate: rate, + couponRateDecimals: rateDecimals, + firstCouponDate, + regulationType: RegulationType.REG_D, + regulationSubType: RegulationSubType.REG_D_506_C, + countriesControlListType, + listOfCountries, + info, + init_rbacs, + factory, + businessLogicResolver: businessLogicResolver.address, + interestRateType: InterestRateType.KPI_BASED_PER_COUPON, + }) + + await setFacets({ diamond: newDiamond }) + // Granting Role to account C accessControlFacet = accessControlFacet.connect(signer_A) await accessControlFacet.grantRole( @@ -1077,37 +1112,37 @@ describe('Bond Tests', () => { recordDate: couponRecordDateInSeconds.toString(), executionDate: couponExecutionDateInSeconds.toString(), rate: couponRate, - startDate: couponStartDateInSeconds, - endDate: couponEndDateInSeconds, - fixingDate: couponFixingDateInSeconds, + startDate: couponStartDateInSeconds.toString(), + endDate: couponEndDateInSeconds.toString(), + fixingDate: couponFixingDateInSeconds.toString(), rateDecimals: couponRateDecimals, } const customCouponData_2 = { - recordDate: ( - couponRecordDateInSeconds + DelayCoupon_2 - ).toString(), + recordDate: couponRecordDateInSeconds.toString(), executionDate: ( couponExecutionDateInSeconds + DelayCoupon_2 ).toString(), rate: couponRate, - startDate: couponStartDateInSeconds, - endDate: couponEndDateInSeconds, - fixingDate: couponFixingDateInSeconds, + startDate: couponStartDateInSeconds.toString(), + endDate: couponEndDateInSeconds.toString(), + fixingDate: ( + couponFixingDateInSeconds + DelayCoupon_2 + ).toString(), rateDecimals: couponRateDecimals, } const customCouponData_3 = { - recordDate: ( - couponRecordDateInSeconds + DelayCoupon_3 - ).toString(), + recordDate: couponRecordDateInSeconds.toString(), executionDate: ( couponExecutionDateInSeconds + DelayCoupon_3 ).toString(), rate: couponRate, - startDate: couponStartDateInSeconds, - endDate: couponEndDateInSeconds, - fixingDate: couponFixingDateInSeconds, + startDate: couponStartDateInSeconds.toString(), + endDate: couponEndDateInSeconds.toString(), + fixingDate: ( + couponFixingDateInSeconds + DelayCoupon_3 + ).toString(), rateDecimals: couponRateDecimals, } @@ -1118,7 +1153,7 @@ describe('Bond Tests', () => { await bondFacet.setCoupon(customCouponData_3) await timeTravelFacet.changeSystemTimestamp( - couponRecordDateInSeconds + DelayCoupon_2 + 1 + couponFixingDateInSeconds + DelayCoupon_2 + 1 ) await scheduledTasksFacet.triggerPendingScheduledCrossOrderedTasks() diff --git a/packages/ats/contracts/test/unitTests/layer_1/scheduledTasks/scheduledCouponListing/scheduledCouponListing.test.ts b/packages/ats/contracts/test/unitTests/layer_1/scheduledTasks/scheduledCouponListing/scheduledCouponListing.test.ts index 86406aa53..8eed89f71 100644 --- a/packages/ats/contracts/test/unitTests/layer_1/scheduledTasks/scheduledCouponListing/scheduledCouponListing.test.ts +++ b/packages/ats/contracts/test/unitTests/layer_1/scheduledTasks/scheduledCouponListing/scheduledCouponListing.test.ts @@ -248,7 +248,7 @@ let firstCouponDate = startingDate + 1 const countriesControlListType = true const listOfCountries = 'ES,FR,CH' const info = 'info' -const interestRateType = InterestRateType.FIXED_PER_COUPON +const interestRateType = InterestRateType.KPI_BASED_PER_COUPON describe('Scheduled Coupon Listing Tests', () => { let diamond: ResolverProxy @@ -366,15 +366,9 @@ describe('Scheduled Coupon Listing Tests', () => { .grantRole(CORPORATE_ACTION_ROLE, account_C) // set coupons - const couponsRecordDateInSeconds_1 = dateToUnixTimestamp( + const couponsRecordDateInSeconds = dateToUnixTimestamp( '2030-01-01T00:00:06Z' ) - const couponsRecordDateInSeconds_2 = dateToUnixTimestamp( - '2030-01-01T00:00:12Z' - ) - const couponsRecordDateInSeconds_3 = dateToUnixTimestamp( - '2030-01-01T00:00:18Z' - ) const couponsExecutionDateInSeconds = dateToUnixTimestamp( '2030-01-01T00:01:00Z' ) @@ -384,37 +378,43 @@ describe('Scheduled Coupon Listing Tests', () => { const couponsEndDateInSeconds = dateToUnixTimestamp( '2029-12-31T00:10:00Z' ) - const couponsFixingDateInSeconds = dateToUnixTimestamp( - '2029-12-30T00:00:00Z' + const couponsFixingDateInSeconds_1 = dateToUnixTimestamp( + '2030-01-01T00:00:06Z' + ) + const couponsFixingDateInSeconds_2 = dateToUnixTimestamp( + '2030-01-01T00:00:12Z' + ) + const couponsFixingDateInSeconds_3 = dateToUnixTimestamp( + '2030-01-01T00:00:18Z' ) const couponsRate = 1 const couponRateDecimals = 0 const couponData_1 = { - recordDate: couponsRecordDateInSeconds_1.toString(), + recordDate: couponsRecordDateInSeconds.toString(), executionDate: couponsExecutionDateInSeconds.toString(), rate: couponsRate, - startDate: couponsStartDateInSeconds, - endDate: couponsEndDateInSeconds, - fixingDate: couponsFixingDateInSeconds, + startDate: couponsStartDateInSeconds.toString(), + endDate: couponsEndDateInSeconds.toString(), + fixingDate: couponsFixingDateInSeconds_1.toString(), rateDecimals: couponRateDecimals, } const couponData_2 = { - recordDate: couponsRecordDateInSeconds_2.toString(), + recordDate: couponsRecordDateInSeconds.toString(), executionDate: couponsExecutionDateInSeconds.toString(), rate: couponsRate, - startDate: couponsStartDateInSeconds, - endDate: couponsEndDateInSeconds, - fixingDate: couponsFixingDateInSeconds, + startDate: couponsStartDateInSeconds.toString(), + endDate: couponsEndDateInSeconds.toString(), + fixingDate: couponsFixingDateInSeconds_2.toString(), rateDecimals: couponRateDecimals, } const couponData_3 = { - recordDate: couponsRecordDateInSeconds_3.toString(), + recordDate: couponsRecordDateInSeconds.toString(), executionDate: couponsExecutionDateInSeconds.toString(), rate: couponsRate, - startDate: couponsStartDateInSeconds, - endDate: couponsEndDateInSeconds, - fixingDate: couponsFixingDateInSeconds, + startDate: couponsStartDateInSeconds.toString(), + endDate: couponsEndDateInSeconds.toString(), + fixingDate: couponsFixingDateInSeconds_3.toString(), rateDecimals: couponRateDecimals, } await bondFacet.connect(signer_C).setCoupon(couponData_2) @@ -440,20 +440,20 @@ describe('Scheduled Coupon Listing Tests', () => { ) expect( scheduledCouponListing[0].scheduledTimestamp.toNumber() - ).to.equal(couponsRecordDateInSeconds_3) + ).to.equal(couponsFixingDateInSeconds_3) expect(scheduledCouponListing[0].data).to.equal(coupon_3_Id) expect( scheduledCouponListing[1].scheduledTimestamp.toNumber() - ).to.equal(couponsRecordDateInSeconds_2) + ).to.equal(couponsFixingDateInSeconds_2) expect(scheduledCouponListing[1].data).to.equal(coupon_2_Id) expect( scheduledCouponListing[2].scheduledTimestamp.toNumber() - ).to.equal(couponsRecordDateInSeconds_1) + ).to.equal(couponsFixingDateInSeconds_1) expect(scheduledCouponListing[2].data).to.equal(coupon_1_Id) // AFTER FIRST SCHEDULED CouponListing ------------------------------------------------------------------ await timeTravelFacet.changeSystemTimestamp( - couponsRecordDateInSeconds_1 + 1 + couponsFixingDateInSeconds_1 + 1 ) await scheduledTasksFacet .connect(signer_A) @@ -470,16 +470,16 @@ describe('Scheduled Coupon Listing Tests', () => { ) expect( scheduledCouponListing[0].scheduledTimestamp.toNumber() - ).to.equal(couponsRecordDateInSeconds_3) + ).to.equal(couponsFixingDateInSeconds_3) expect(scheduledCouponListing[0].data).to.equal(coupon_3_Id) expect( scheduledCouponListing[1].scheduledTimestamp.toNumber() - ).to.equal(couponsRecordDateInSeconds_2) + ).to.equal(couponsFixingDateInSeconds_2) expect(scheduledCouponListing[1].data).to.equal(coupon_2_Id) // AFTER SECOND SCHEDULED CouponListing ------------------------------------------------------------------ await timeTravelFacet.changeSystemTimestamp( - couponsRecordDateInSeconds_2 + 1 + couponsFixingDateInSeconds_2 + 1 ) await scheduledTasksFacet .connect(signer_A) @@ -496,12 +496,12 @@ describe('Scheduled Coupon Listing Tests', () => { ) expect( scheduledCouponListing[0].scheduledTimestamp.toNumber() - ).to.equal(couponsRecordDateInSeconds_3) + ).to.equal(couponsFixingDateInSeconds_3) expect(scheduledCouponListing[0].data).to.equal(coupon_3_Id) // AFTER SECOND SCHEDULED CouponListing ------------------------------------------------------------------ await timeTravelFacet.changeSystemTimestamp( - couponsRecordDateInSeconds_3 + 1 + couponsFixingDateInSeconds_3 + 1 ) await scheduledTasksFacet .connect(signer_A) From dba89e0563d47f6b605e01c01b5feacfac1b62ff Mon Sep 17 00:00:00 2001 From: Alberto Molina Date: Thu, 25 Sep 2025 13:26:14 +0200 Subject: [PATCH 05/54] refactor: sdk updated to bond and coupon changes Signed-off-by: Alberto Molina --- .../__tests__/fixtures/bond/BondFixture.ts | 16 +- .../ats/sdk/__tests__/port/environmentMock.ts | 12 +- .../ats/sdk/__tests__/port/in/Bond.test.ts | 16 +- .../port/out/AWSKMSTransactionAdapter.test.ts | 3 + .../port/out/DFNSTransactionAdapter.test.ts | 3 + .../out/FireblocksTransactionAdapter.test.ts | 3 + .../bond/coupon/set/SetCouponCommand.ts | 4 +- .../coupon/set/SetCouponCommandHandler.ts | 14 +- .../set/SetCouponCommandHandler.unit.test.ts | 4 +- .../command/bond/create/CreateBondCommand.ts | 2 + .../bond/create/CreateBondCommandHandler.ts | 6 + .../CreateTrexSuiteBondCommand.ts | 2 + .../CreateTrexSuiteBondCommandHandler.ts | 3 + packages/ats/sdk/src/core/error/BaseError.ts | 1 + .../src/domain/context/bond/BondDetails.ts | 4 + .../ats/sdk/src/domain/context/bond/Coupon.ts | 12 +- .../src/domain/context/factory/Factories.ts | 12 + .../context/factory/FactorySecurityToken.ts | 4 +- .../context/factory/InterestRateType.ts | 224 ++++++++++++++++++ .../factory/error/InvalidInterestRateType.ts | 215 +++++++++++++++++ .../factory/error/MissingInterestRateType.ts | 212 +++++++++++++++++ packages/ats/sdk/src/port/in/bond/Bond.ts | 25 +- .../sdk/src/port/in/bond/Bond.unit.test.ts | 15 +- .../port/in/request/bond/CreateBondRequest.ts | 8 + .../bond/CreateTrexSuiteBondRequest.ts | 7 + .../port/in/request/bond/SetCouponRequest.ts | 31 ++- .../port/in/response/BondDetailsViewModel.ts | 1 + .../src/port/in/response/CouponViewModel.ts | 4 +- .../sdk/src/port/out/TransactionAdapter.ts | 9 +- .../port/out/hs/HederaTransactionAdapter.ts | 15 +- .../sdk/src/port/out/rpc/RPCQueryAdapter.ts | 13 +- .../src/port/out/rpc/RPCTransactionAdapter.ts | 14 +- 32 files changed, 878 insertions(+), 36 deletions(-) create mode 100644 packages/ats/sdk/src/domain/context/factory/InterestRateType.ts create mode 100644 packages/ats/sdk/src/domain/context/factory/error/InvalidInterestRateType.ts create mode 100644 packages/ats/sdk/src/domain/context/factory/error/MissingInterestRateType.ts diff --git a/packages/ats/sdk/__tests__/fixtures/bond/BondFixture.ts b/packages/ats/sdk/__tests__/fixtures/bond/BondFixture.ts index 3ae3c1eab..798872b6e 100644 --- a/packages/ats/sdk/__tests__/fixtures/bond/BondFixture.ts +++ b/packages/ats/sdk/__tests__/fixtures/bond/BondFixture.ts @@ -255,6 +255,7 @@ import { import AddBeneficiaryRequest from '@port/in/request/bond/AddBeneficiaryRequest'; import RemoveBeneficiaryRequest from '@port/in/request/bond/RemoveBeneficiaryRequest'; import UpdateBeneficiaryDataRequest from '@port/in/request/bond/UpdateBeneficiaryDataRequest'; +import { InterestRateType } from '../../../src/domain/context/factory/InterestRateType.js'; export const SetCouponCommandFixture = createFixture( (command) => { @@ -268,8 +269,12 @@ export const SetCouponCommandFixture = createFixture( command.rate.faker((faker) => faker.number.int({ min: 100, max: 999 }).toString(), ); - command.period.faker((faker) => - faker.number.int({ min: 86400, max: 31536000 }).toString(), + command.startDate.faker((faker) => + faker.date.future().getTime().toString(), + ); + command.endDate.faker((faker) => faker.date.future().getTime().toString()); + command.fixingDate.faker((faker) => + faker.date.future().getTime().toString(), ); }, ); @@ -569,7 +574,11 @@ export const SetCouponRequestFixture = createFixture( request.executionTimestamp.faker((faker) => faker.date.future().getTime().toString(), ); - request.period.as(() => TIME_PERIODS_S.DAY.toString()); + request.startTimestamp.as(() => '0'); + request.endTimestamp.as(() => TIME_PERIODS_S.DAY.toString()); + request.fixingTimestamp.faker((faker) => + faker.date.past().getTime().toString(), + ); }, ); @@ -696,6 +705,7 @@ export const CreateTrexSuiteBondRequestFixture = maturityDate = faker.date.future({ years: 2 }); return maturityDate.getTime().toString(); }); + request.interestRateType.as(() => InterestRateType.FIXED_FOR_ALL_COUPONS); request.configId.faker( (faker) => diff --git a/packages/ats/sdk/__tests__/port/environmentMock.ts b/packages/ats/sdk/__tests__/port/environmentMock.ts index 5cad4a084..aa2c88325 100644 --- a/packages/ats/sdk/__tests__/port/environmentMock.ts +++ b/packages/ats/sdk/__tests__/port/environmentMock.ts @@ -546,7 +546,9 @@ function createBondMockImplementation( timeStamp, couponInfo.couponRate, couponInfo.couponRateDecimals, - 0, + timeStamp, + timeStamp, + timeStamp, ); coupons.push(coupon); } @@ -1969,13 +1971,18 @@ jest.mock('@port/out/rpc/RPCTransactionAdapter', () => { recordDate: BigDecimal, executionDate: BigDecimal, rate: BigDecimal, + startDate: BigDecimal, + endDate: BigDecimal, + fixingDate: BigDecimal, ) => { const coupon = new Coupon( parseInt(recordDate.toString()), parseInt(executionDate.toString()), rate, rate.decimals, - 0, + parseInt(startDate.toString()), + parseInt(endDate.toString()), + parseInt(fixingDate.toString()), ); coupons.push(coupon); return { @@ -2244,6 +2251,7 @@ jest.mock('@port/out/rpc/RPCTransactionAdapter', () => { bondInfo.nominalValue, bondInfo.startingDate, _maturityDate, + bondInfo.interestRateType, ); return { status: 'success', data: [] } as TransactionResponse< diff --git a/packages/ats/sdk/__tests__/port/in/Bond.test.ts b/packages/ats/sdk/__tests__/port/in/Bond.test.ts index 2846603f4..afcf973c1 100644 --- a/packages/ats/sdk/__tests__/port/in/Bond.test.ts +++ b/packages/ats/sdk/__tests__/port/in/Bond.test.ts @@ -244,6 +244,10 @@ import { MirrorNodeAdapter } from '@port/out/mirror/MirrorNodeAdapter'; import { RPCTransactionAdapter } from '@port/out/rpc/RPCTransactionAdapter'; import { Wallet, ethers } from 'ethers'; import BaseError from '@core/error/BaseError'; +import { + CastInterestRateType, + InterestRateType, +} from '@domain/context/factory/InterestRateType'; SDK.log = { level: 'ERROR', transports: new LoggerTransports.Console() }; @@ -260,6 +264,7 @@ const startingDate = currentTimeInSeconds + TIME; const maturityDate = startingDate + 365; // 1 year maturity const regulationType = RegulationType.REG_S; const regulationSubType = RegulationSubType.NONE; +const interestRateType = InterestRateType.FIXED_PER_COUPON; const countries = 'AF,HG,BN'; const info = 'Anything'; const configId = @@ -352,6 +357,7 @@ describe('🧪 Bond test', () => { info: info, configId: configId, configVersion: configVersion, + interestRateType: CastInterestRateType.toNumber(interestRateType), }); Injectable.resolveTransactionHandler(); @@ -379,6 +385,7 @@ describe('🧪 Bond test', () => { const couponRate = '3'; const couponRecordDate = startingDate + 30; const couponExecutionDate = startingDate + 35; + const couponFixingDate = startingDate + 25; await Bond.setCoupon( new SetCouponRequest({ @@ -386,7 +393,9 @@ describe('🧪 Bond test', () => { rate: couponRate, recordTimestamp: couponRecordDate.toString(), executionTimestamp: couponExecutionDate.toString(), - period: TIME_PERIODS_S.DAY.toString(), + startTimestamp: '0', + endTimestamp: TIME_PERIODS_S.DAY.toString(), + fixingTimestamp: couponFixingDate.toString(), }), ); @@ -431,6 +440,7 @@ describe('🧪 Bond test', () => { const rate = '1'; const recordTimestamp = Math.ceil(new Date().getTime() / 1000) + 1000; const executionTimestamp = recordTimestamp + 1000; + const couponFixingDate = recordTimestamp - 1000; await Bond.setCoupon( new SetCouponRequest({ @@ -438,7 +448,9 @@ describe('🧪 Bond test', () => { rate: rate, recordTimestamp: recordTimestamp.toString(), executionTimestamp: executionTimestamp.toString(), - period: TIME_PERIODS_S.DAY.toString(), + startTimestamp: '0', + endTimestamp: TIME_PERIODS_S.DAY.toString(), + fixingTimestamp: couponFixingDate.toString(), }), ); diff --git a/packages/ats/sdk/__tests__/port/out/AWSKMSTransactionAdapter.test.ts b/packages/ats/sdk/__tests__/port/out/AWSKMSTransactionAdapter.test.ts index f027d46e7..641494ed2 100644 --- a/packages/ats/sdk/__tests__/port/out/AWSKMSTransactionAdapter.test.ts +++ b/packages/ats/sdk/__tests__/port/out/AWSKMSTransactionAdapter.test.ts @@ -229,6 +229,7 @@ import { RegulationSubType, RegulationType, } from '@domain/context/factory/RegulationType'; +import { CastInterestRateType, InterestRateType } from '@domain/context/factory/InterestRateType'; SDK.log = { level: 'ERROR', transports: new LoggerTransports.Console() }; @@ -249,6 +250,7 @@ const maturityDate = startingDate + numberOfCoupons * couponFrequency; const firstCouponDate = startingDate + 1; const regulationType = RegulationType.REG_S; const regulationSubType = RegulationSubType.NONE; +const interestRateType = InterestRateType.FIXED_PER_COUPON; const countries = 'AF,HG,BN'; const info = 'Anything'; const configId = @@ -318,6 +320,7 @@ describe('AWSKMS Transaction Adapter test', () => { info: info, configId: configId, configVersion: configVersion, + interestRateType: CastInterestRateType.toNumber(interestRateType), }); bond = (await Bond.create(requestST)).security; diff --git a/packages/ats/sdk/__tests__/port/out/DFNSTransactionAdapter.test.ts b/packages/ats/sdk/__tests__/port/out/DFNSTransactionAdapter.test.ts index d1bae48ee..a138e9980 100644 --- a/packages/ats/sdk/__tests__/port/out/DFNSTransactionAdapter.test.ts +++ b/packages/ats/sdk/__tests__/port/out/DFNSTransactionAdapter.test.ts @@ -225,6 +225,7 @@ import { RegulationSubType, RegulationType, } from '@domain/context/factory/RegulationType'; +import { CastInterestRateType, InterestRateType } from '@domain/context/factory/InterestRateType'; SDK.log = { level: 'ERROR', transports: new LoggerTransports.Console() }; @@ -245,6 +246,7 @@ const maturityDate = startingDate + numberOfCoupons * couponFrequency; const firstCouponDate = startingDate + 1; const regulationType = RegulationType.REG_S; const regulationSubType = RegulationSubType.NONE; +const interestRateType = InterestRateType.FIXED_PER_COUPON; const countries = 'AF,HG,BN'; const info = 'Anything'; const configId = @@ -314,6 +316,7 @@ describe('DFNS Transaction Adapter test', () => { info: info, configId: configId, configVersion: configVersion, + interestRateType: CastInterestRateType.toNumber(interestRateType), }); bond = (await Bond.create(requestST)).security; diff --git a/packages/ats/sdk/__tests__/port/out/FireblocksTransactionAdapter.test.ts b/packages/ats/sdk/__tests__/port/out/FireblocksTransactionAdapter.test.ts index 189cb3bd0..3fbcf27b1 100644 --- a/packages/ats/sdk/__tests__/port/out/FireblocksTransactionAdapter.test.ts +++ b/packages/ats/sdk/__tests__/port/out/FireblocksTransactionAdapter.test.ts @@ -229,6 +229,7 @@ import { RegulationSubType, RegulationType, } from '@domain/context/factory/RegulationType'; +import { CastInterestRateType, InterestRateType } from '@domain/context/factory/InterestRateType'; SDK.log = { level: 'ERROR', transports: new LoggerTransports.Console() }; @@ -249,6 +250,7 @@ const maturityDate = startingDate + numberOfCoupons * couponFrequency; const firstCouponDate = startingDate + 1; const regulationType = RegulationType.REG_S; const regulationSubType = RegulationSubType.NONE; +const interestRateType = InterestRateType.FIXED_PER_COUPON; const countries = 'AF,HG,BN'; const info = 'Anything'; const configId = @@ -318,6 +320,7 @@ describe('Fireblocks Transaction Adapter test', () => { info: info, configId: configId, configVersion: configVersion, + interestRateType: CastInterestRateType.toNumber(interestRateType), }); bond = (await Bond.create(requestST)).security; diff --git a/packages/ats/sdk/src/app/usecase/command/bond/coupon/set/SetCouponCommand.ts b/packages/ats/sdk/src/app/usecase/command/bond/coupon/set/SetCouponCommand.ts index cee0eb249..ea5d576d8 100644 --- a/packages/ats/sdk/src/app/usecase/command/bond/coupon/set/SetCouponCommand.ts +++ b/packages/ats/sdk/src/app/usecase/command/bond/coupon/set/SetCouponCommand.ts @@ -219,7 +219,9 @@ export class SetCouponCommand extends Command { public readonly recordDate: string, public readonly executionDate: string, public readonly rate: string, - public readonly period: string, + public readonly startDate: string, + public readonly endDate: string, + public readonly fixingDate: string, ) { super(); } diff --git a/packages/ats/sdk/src/app/usecase/command/bond/coupon/set/SetCouponCommandHandler.ts b/packages/ats/sdk/src/app/usecase/command/bond/coupon/set/SetCouponCommandHandler.ts index 9bc7bc7b3..c908bffc5 100644 --- a/packages/ats/sdk/src/app/usecase/command/bond/coupon/set/SetCouponCommandHandler.ts +++ b/packages/ats/sdk/src/app/usecase/command/bond/coupon/set/SetCouponCommandHandler.ts @@ -225,7 +225,15 @@ export class SetCouponCommandHandler async execute(command: SetCouponCommand): Promise { try { - const { address, recordDate, executionDate, rate, period } = command; + const { + address, + recordDate, + executionDate, + rate, + startDate, + endDate, + fixingDate, + } = command; const handler = this.transactionService.getHandler(); const securityEvmAddress = @@ -236,7 +244,9 @@ export class SetCouponCommandHandler BigDecimal.fromString(recordDate), BigDecimal.fromString(executionDate), BigDecimal.fromString(rate), - BigDecimal.fromString(period), + BigDecimal.fromString(startDate), + BigDecimal.fromString(endDate), + BigDecimal.fromString(fixingDate), address, ); diff --git a/packages/ats/sdk/src/app/usecase/command/bond/coupon/set/SetCouponCommandHandler.unit.test.ts b/packages/ats/sdk/src/app/usecase/command/bond/coupon/set/SetCouponCommandHandler.unit.test.ts index 7158e8858..d440be821 100644 --- a/packages/ats/sdk/src/app/usecase/command/bond/coupon/set/SetCouponCommandHandler.unit.test.ts +++ b/packages/ats/sdk/src/app/usecase/command/bond/coupon/set/SetCouponCommandHandler.unit.test.ts @@ -314,7 +314,9 @@ describe('SetCouponCommandHandler', () => { BigDecimal.fromString(command.recordDate), BigDecimal.fromString(command.executionDate), BigDecimal.fromString(command.rate), - BigDecimal.fromString(command.period), + BigDecimal.fromString(command.startDate), + BigDecimal.fromString(command.endDate), + BigDecimal.fromString(command.fixingDate), command.address, ); expect(transactionServiceMock.getTransactionResult).toHaveBeenCalledWith( diff --git a/packages/ats/sdk/src/app/usecase/command/bond/create/CreateBondCommand.ts b/packages/ats/sdk/src/app/usecase/command/bond/create/CreateBondCommand.ts index baad25954..ee414d284 100644 --- a/packages/ats/sdk/src/app/usecase/command/bond/create/CreateBondCommand.ts +++ b/packages/ats/sdk/src/app/usecase/command/bond/create/CreateBondCommand.ts @@ -207,6 +207,7 @@ import { Command } from '@core/command/Command'; import { CommandResponse } from '@core/command/CommandResponse'; import ContractId from '@domain/context/contract/ContractId'; import { SecurityProps } from '@domain/context/security/Security'; +import { InterestRateType } from '@domain/context/factory/InterestRateType.js'; export class CreateBondCommandResponse implements CommandResponse { public readonly securityId: ContractId; @@ -237,6 +238,7 @@ export class CreateBondCommand extends Command { public readonly identityRegistryId?: string, public readonly beneficiariesIds?: string[], public readonly beneficiariesData?: string[], + public readonly interestRateType?: InterestRateType, ) { super(); } diff --git a/packages/ats/sdk/src/app/usecase/command/bond/create/CreateBondCommandHandler.ts b/packages/ats/sdk/src/app/usecase/command/bond/create/CreateBondCommandHandler.ts index dc56695b9..b7f2ef97e 100644 --- a/packages/ats/sdk/src/app/usecase/command/bond/create/CreateBondCommandHandler.ts +++ b/packages/ats/sdk/src/app/usecase/command/bond/create/CreateBondCommandHandler.ts @@ -225,6 +225,7 @@ import { Response } from '@domain/context/transaction/Response'; import { MissingRegulationType } from '@domain/context/factory/error/MissingRegulationType'; import { MissingRegulationSubType } from '@domain/context/factory/error/MissingRegulationSubType'; import { EVM_ZERO_ADDRESS } from '@core/Constants'; +import { MissingInterestRateType } from '@domain/context/factory/error/MissingInterestRateType'; @CommandHandler(CreateBondCommand) export class CreateBondCommandHandler @@ -264,6 +265,7 @@ export class CreateBondCommandHandler identityRegistryId, beneficiariesIds, beneficiariesData, + interestRateType, } = command; //TODO: Boy scout: remove request validations and adjust test @@ -288,6 +290,9 @@ export class CreateBondCommandHandler if (!security.regulationsubType) { throw new MissingRegulationSubType(); } + if (!interestRateType) { + throw new MissingInterestRateType(); + } const diamondOwnerAccountEvmAddress: EvmAddress = await this.accountService.getAccountEvmAddress(diamondOwnerAccount!); @@ -333,6 +338,7 @@ export class CreateBondCommandHandler BigDecimal.fromString(nominalValue), parseInt(startingDate), parseInt(maturityDate), + interestRateType, ); res = await handler.createBond( diff --git a/packages/ats/sdk/src/app/usecase/command/bond/createTrexSuite/CreateTrexSuiteBondCommand.ts b/packages/ats/sdk/src/app/usecase/command/bond/createTrexSuite/CreateTrexSuiteBondCommand.ts index d05a7e76d..d75feb752 100644 --- a/packages/ats/sdk/src/app/usecase/command/bond/createTrexSuite/CreateTrexSuiteBondCommand.ts +++ b/packages/ats/sdk/src/app/usecase/command/bond/createTrexSuite/CreateTrexSuiteBondCommand.ts @@ -207,6 +207,7 @@ import { Command } from '@core/command/Command'; import { CommandResponse } from '@core/command/CommandResponse'; import ContractId from '@domain/context/contract/ContractId'; import { SecurityProps } from '@domain/context/security/Security'; +import { InterestRateType } from '@domain/context/factory/InterestRateType.js'; export class CreateTrexSuiteBondCommandResponse implements CommandResponse { public readonly securityId: ContractId; @@ -237,6 +238,7 @@ export class CreateTrexSuiteBondCommand extends Command { currency: string; nominalValue: BigDecimal; startingDate: number; maturityDate: number; + interestRateType: InterestRateType; constructor( currency: string, nominalValue: BigDecimal, startingDate: number, maturityDate: number, + interestRateType: InterestRateType, ) { super({ maturityDate: (val) => { @@ -229,6 +232,7 @@ export class BondDetails extends ValidatedDomain { this.nominalValue = nominalValue; this.startingDate = startingDate; this.maturityDate = maturityDate; + this.interestRateType = interestRateType; ValidatedDomain.handleValidation(BondDetails.name, this); } diff --git a/packages/ats/sdk/src/domain/context/bond/Coupon.ts b/packages/ats/sdk/src/domain/context/bond/Coupon.ts index 33883d292..1f0ecf247 100644 --- a/packages/ats/sdk/src/domain/context/bond/Coupon.ts +++ b/packages/ats/sdk/src/domain/context/bond/Coupon.ts @@ -212,7 +212,9 @@ export class Coupon extends ValidatedDomain { executionTimeStamp: number; rate: BigDecimal; rateDecimals: number; - period: number; + startDate: number; + endDate: number; + fixingDate: number; snapshotId?: number; @@ -221,7 +223,9 @@ export class Coupon extends ValidatedDomain { executionTimeStamp: number, rate: BigDecimal, rateDecimals: number, - period: number, + startDate: number, + endDate: number, + fixingDate: number, snapshotId?: number, ) { super({ @@ -234,7 +238,9 @@ export class Coupon extends ValidatedDomain { this.executionTimeStamp = executionTimeStamp; this.rate = rate; this.rateDecimals = rateDecimals; - this.period = period; + this.startDate = startDate; + this.endDate = endDate; + this.fixingDate = fixingDate; this.snapshotId = snapshotId ? snapshotId : undefined; ValidatedDomain.handleValidation(Coupon.name, this); diff --git a/packages/ats/sdk/src/domain/context/factory/Factories.ts b/packages/ats/sdk/src/domain/context/factory/Factories.ts index de83b41ac..3d4a316aa 100644 --- a/packages/ats/sdk/src/domain/context/factory/Factories.ts +++ b/packages/ats/sdk/src/domain/context/factory/Factories.ts @@ -215,6 +215,8 @@ import { InvalidRegulationSubTypeForType, } from './error/InvalidRegulationSubType'; import { InvalidRegulationType } from './error/InvalidRegulationType'; +import { InterestRateType } from './InterestRateType'; +import { InvalidInterestRateType } from './error/InvalidInterestRateType'; export class EnvironmentFactory { factory: string; @@ -260,4 +262,14 @@ export class Factory { return errorList; } + + public static checkInterestRateType(value: number): BaseError[] { + const errorList: BaseError[] = []; + + const length = Object.keys(InterestRateType).length; + + if (value >= length) errorList.push(new InvalidInterestRateType(value)); + + return errorList; + } } diff --git a/packages/ats/sdk/src/domain/context/factory/FactorySecurityToken.ts b/packages/ats/sdk/src/domain/context/factory/FactorySecurityToken.ts index c8860b7de..13825aee5 100644 --- a/packages/ats/sdk/src/domain/context/factory/FactorySecurityToken.ts +++ b/packages/ats/sdk/src/domain/context/factory/FactorySecurityToken.ts @@ -221,18 +221,20 @@ export class FactoryEquityToken { export class FactoryBondToken { public security: SecurityData; public bondDetails: BondDetailsData; - + public interestRateType: number; public beneficiaries: string[]; public beneficiariesData: string[]; constructor( security: SecurityData, bondDetails: BondDetailsData, + interestRateType: number, beneficiaries: string[], beneficiariesData: string[], ) { this.security = security; this.bondDetails = bondDetails; + this.interestRateType = interestRateType; this.beneficiaries = beneficiaries; this.beneficiariesData = beneficiariesData; } diff --git a/packages/ats/sdk/src/domain/context/factory/InterestRateType.ts b/packages/ats/sdk/src/domain/context/factory/InterestRateType.ts new file mode 100644 index 000000000..d93e11297 --- /dev/null +++ b/packages/ats/sdk/src/domain/context/factory/InterestRateType.ts @@ -0,0 +1,224 @@ +/* + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +export enum InterestRateType { + FIXED_FOR_ALL_COUPONS = 'FIXED_FOR_ALL_COUPONS', + FIXED_PER_COUPON = 'FIXED_PER_COUPON ', + KPI_BASED_PER_COUPON = 'KPI_BASED_PER_COUPON', +} + +export class CastInterestRateType { + static fromNumber(id: number): InterestRateType { + if (id == 0) return InterestRateType.FIXED_FOR_ALL_COUPONS; + if (id == 1) return InterestRateType.FIXED_PER_COUPON; + return InterestRateType.KPI_BASED_PER_COUPON; + } + + static toNumber(value: InterestRateType): number { + if (value == InterestRateType.FIXED_FOR_ALL_COUPONS) return 0; + if (value == InterestRateType.FIXED_PER_COUPON) return 1; + return 2; + } +} diff --git a/packages/ats/sdk/src/domain/context/factory/error/InvalidInterestRateType.ts b/packages/ats/sdk/src/domain/context/factory/error/InvalidInterestRateType.ts new file mode 100644 index 000000000..29f2deb64 --- /dev/null +++ b/packages/ats/sdk/src/domain/context/factory/error/InvalidInterestRateType.ts @@ -0,0 +1,215 @@ +/* + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +import BaseError, { ErrorCode } from '@core/error/BaseError'; + +export class InvalidInterestRateType extends BaseError { + constructor(value: number) { + super( + ErrorCode.InvalidInterestRateType, + `Interest Rate Type ${value} is not valid`, + ); + } +} diff --git a/packages/ats/sdk/src/domain/context/factory/error/MissingInterestRateType.ts b/packages/ats/sdk/src/domain/context/factory/error/MissingInterestRateType.ts new file mode 100644 index 000000000..e8952aeb2 --- /dev/null +++ b/packages/ats/sdk/src/domain/context/factory/error/MissingInterestRateType.ts @@ -0,0 +1,212 @@ +/* + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +import BaseError, { ErrorCode } from '@core/error/BaseError'; + +export class MissingInterestRateType extends BaseError { + constructor() { + super(ErrorCode.EmptyValue, `Interest Rate type is missing`); + } +} diff --git a/packages/ats/sdk/src/port/in/bond/Bond.ts b/packages/ats/sdk/src/port/in/bond/Bond.ts index d2b1469b0..fcfdd6ad4 100644 --- a/packages/ats/sdk/src/port/in/bond/Bond.ts +++ b/packages/ats/sdk/src/port/in/bond/Bond.ts @@ -262,6 +262,7 @@ import { GetBeneficiariesCountRequest, GetBeneficiariesRequest, } from '../request'; +import { CastInterestRateType } from '../../../domain/context/factory/InterestRateType.js'; interface IBondInPort { create( @@ -372,6 +373,7 @@ class BondInPort implements IBondInPort { req.identityRegistryId, req.beneficiariesIds, req.beneficiariesData, + CastInterestRateType.fromNumber(req.interestRateType), ), ); @@ -411,6 +413,9 @@ class BondInPort implements IBondInPort { nominalValue: res.bond.nominalValue.toString(), startingDate: new Date(res.bond.startingDate * ONE_THOUSAND), maturityDate: new Date(res.bond.maturityDate * ONE_THOUSAND), + interestRateType: CastInterestRateType.toNumber( + res.bond.interestRateType, + ), }; return bondDetails; @@ -420,8 +425,15 @@ class BondInPort implements IBondInPort { async setCoupon( request: SetCouponRequest, ): Promise<{ payload: number; transactionId: string }> { - const { rate, recordTimestamp, executionTimestamp, securityId, period } = - request; + const { + rate, + recordTimestamp, + executionTimestamp, + securityId, + startTimestamp, + endTimestamp, + fixingTimestamp, + } = request; ValidatedRequest.handleValidation('SetCouponRequest', request); return await this.commandBus.execute( @@ -430,7 +442,9 @@ class BondInPort implements IBondInPort { recordTimestamp, executionTimestamp, rate, - period, + startTimestamp, + endTimestamp, + fixingTimestamp, ), ); } @@ -470,7 +484,9 @@ class BondInPort implements IBondInPort { executionDate: new Date(res.coupon.executionTimeStamp * ONE_THOUSAND), rate: res.coupon.rate.toString(), rateDecimals: res.coupon.rateDecimals, - period: res.coupon.period, + startDate: res.coupon.startDate, + endDate: res.coupon.endDate, + fixingDate: res.coupon.fixingDate, }; return coupon; @@ -621,6 +637,7 @@ class BondInPort implements IBondInPort { req.nominalValue, req.startingDate, req.maturityDate, + CastInterestRateType.fromNumber(req.interestRateType), new ContractId(securityFactory), new ContractId(resolver), req.configId, diff --git a/packages/ats/sdk/src/port/in/bond/Bond.unit.test.ts b/packages/ats/sdk/src/port/in/bond/Bond.unit.test.ts index 563602182..f8a467fc1 100644 --- a/packages/ats/sdk/src/port/in/bond/Bond.unit.test.ts +++ b/packages/ats/sdk/src/port/in/bond/Bond.unit.test.ts @@ -286,6 +286,7 @@ import { IsBeneficiaryQuery } from '@query/security/beneficiary/isBeneficiary/Is import { GetBeneficiariesCountQuery } from '@query/security/beneficiary/getBeneficiariesCount/GetBeneficiariesCountQuery'; import { GetBeneficiaryDataQuery } from '@query/security/beneficiary/getBeneficiaryData/GetBeneficiaryDataQuery'; import { GetBeneficiariesQuery } from '@query/security/beneficiary/getBeneficiaries/GetBeneficiariesQuery'; +import { CastInterestRateType } from '@domain/context/factory/InterestRateType'; describe('Bond', () => { let commandBusMock: jest.Mocked; @@ -752,7 +753,9 @@ describe('Bond', () => { setCouponRequest.recordTimestamp, setCouponRequest.executionTimestamp, setCouponRequest.rate, - setCouponRequest.period, + setCouponRequest.startTimestamp, + setCouponRequest.endTimestamp, + setCouponRequest.fixingTimestamp, ), ); @@ -778,7 +781,9 @@ describe('Bond', () => { setCouponRequest.recordTimestamp, setCouponRequest.executionTimestamp, setCouponRequest.rate, - setCouponRequest.period, + setCouponRequest.startTimestamp, + setCouponRequest.endTimestamp, + setCouponRequest.fixingTimestamp, ), ); }); @@ -1541,6 +1546,9 @@ describe('Bond', () => { createTrexSuiteBondRequest.nominalValue, createTrexSuiteBondRequest.startingDate, createTrexSuiteBondRequest.maturityDate, + CastInterestRateType.fromNumber( + createTrexSuiteBondRequest.interestRateType, + ), new ContractId(factoryAddress), new ContractId(resolverAddress), createTrexSuiteBondRequest.configId, @@ -1621,6 +1629,9 @@ describe('Bond', () => { createTrexSuiteBondRequest.nominalValue, createTrexSuiteBondRequest.startingDate, createTrexSuiteBondRequest.maturityDate, + CastInterestRateType.fromNumber( + createTrexSuiteBondRequest.interestRateType, + ), new ContractId(factoryAddress), new ContractId(resolverAddress), createTrexSuiteBondRequest.configId, diff --git a/packages/ats/sdk/src/port/in/request/bond/CreateBondRequest.ts b/packages/ats/sdk/src/port/in/request/bond/CreateBondRequest.ts index 406b6b50b..23d615350 100644 --- a/packages/ats/sdk/src/port/in/request/bond/CreateBondRequest.ts +++ b/packages/ats/sdk/src/port/in/request/bond/CreateBondRequest.ts @@ -268,6 +268,8 @@ export default class CreateBondRequest extends ValidatedRequest { @@ -419,6 +423,9 @@ export default class CreateBondRequest extends ValidatedRequest { + return Factory.checkInterestRateType(val); + }, }); this.name = name; this.symbol = symbol; @@ -452,5 +459,6 @@ export default class CreateBondRequest extends ValidatedRequest { @@ -454,6 +457,9 @@ export default class CreateTrexSuiteBondRequest extends ValidatedRequest { + return Factory.checkInterestRateType(val); + }, }); this.salt = salt; @@ -500,5 +506,6 @@ export default class CreateTrexSuiteBondRequest extends ValidatedRequest rate: string; recordTimestamp: string; executionTimestamp: string; - period: string; + startTimestamp: string; + endTimestamp: string; + fixingTimestamp: string; constructor({ securityId, rate, recordTimestamp, executionTimestamp, - period, + startTimestamp, + endTimestamp, + fixingTimestamp, }: { securityId: string; rate: string; recordTimestamp: string; executionTimestamp: string; - period: string; + startTimestamp: string; + endTimestamp: string; + fixingTimestamp: string; }) { super({ rate: FormatValidation.checkAmount(true), @@ -245,13 +251,28 @@ export default class SetCouponRequest extends ValidatedRequest ); }, securityId: FormatValidation.checkHederaIdFormatOrEvmAddress(), - period: FormatValidation.checkAmount(), + endTimestamp: (val) => { + return SecurityDate.checkDateTimestamp( + parseInt(val), + parseInt(this.startTimestamp), + undefined, + ); + }, + fixingTimestamp: (val) => { + return SecurityDate.checkDateTimestamp( + parseInt(val), + undefined, + parseInt(this.executionTimestamp), + ); + }, }); this.securityId = securityId; this.rate = rate; this.recordTimestamp = recordTimestamp; this.executionTimestamp = executionTimestamp; - this.period = period; + this.startTimestamp = startTimestamp; + this.endTimestamp = endTimestamp; + this.fixingTimestamp = fixingTimestamp; } } diff --git a/packages/ats/sdk/src/port/in/response/BondDetailsViewModel.ts b/packages/ats/sdk/src/port/in/response/BondDetailsViewModel.ts index a04f27864..ee477960f 100644 --- a/packages/ats/sdk/src/port/in/response/BondDetailsViewModel.ts +++ b/packages/ats/sdk/src/port/in/response/BondDetailsViewModel.ts @@ -210,4 +210,5 @@ export default interface BondDetailsViewModel extends QueryResponse { nominalValue: string; startingDate: Date; maturityDate: Date; + interestRateType: number; } diff --git a/packages/ats/sdk/src/port/in/response/CouponViewModel.ts b/packages/ats/sdk/src/port/in/response/CouponViewModel.ts index 082119c9e..bab8edfde 100644 --- a/packages/ats/sdk/src/port/in/response/CouponViewModel.ts +++ b/packages/ats/sdk/src/port/in/response/CouponViewModel.ts @@ -211,6 +211,8 @@ export default interface CouponViewModel extends QueryResponse { executionDate: Date; rate: string; rateDecimals: number; - period: number; + startDate: number; + endDate: number; + fixingDate: number; snapshotId?: number; } diff --git a/packages/ats/sdk/src/port/out/TransactionAdapter.ts b/packages/ats/sdk/src/port/out/TransactionAdapter.ts index 69b1f0ef0..c285c7270 100644 --- a/packages/ats/sdk/src/port/out/TransactionAdapter.ts +++ b/packages/ats/sdk/src/port/out/TransactionAdapter.ts @@ -220,6 +220,7 @@ import DfnsSettings from '@core/settings/custodialWalletSettings/DfnsSettings'; import FireblocksSettings from '@core/settings/custodialWalletSettings/FireblocksSettings'; import AWSKMSSettings from '@core/settings/custodialWalletSettings/AWSKMSSettings'; import { ClearingOperationType } from '@domain/context/security/Clearing'; +import { InterestRateType } from '@domain/context/factory/InterestRateType'; export interface InitializationData { account?: Account; @@ -381,7 +382,9 @@ interface ITransactionAdapter { recordDate: BigDecimal, executionDate: BigDecimal, rate: BigDecimal, - period: BigDecimal, + startDate: BigDecimal, + endDate: BigDecimal, + fixingDate: BigDecimal, securityId?: ContractId | string, ): Promise>; setDocument( @@ -1292,7 +1295,9 @@ export default abstract class TransactionAdapter recordDate: BigDecimal, executionDate: BigDecimal, rate: BigDecimal, - period: BigDecimal, + startDate: BigDecimal, + endDate: BigDecimal, + fixingDate: BigDecimal, securityId?: ContractId | string, ): Promise>; abstract setVotingRights( diff --git a/packages/ats/sdk/src/port/out/hs/HederaTransactionAdapter.ts b/packages/ats/sdk/src/port/out/hs/HederaTransactionAdapter.ts index 8bb047696..5515e1329 100644 --- a/packages/ats/sdk/src/port/out/hs/HederaTransactionAdapter.ts +++ b/packages/ats/sdk/src/port/out/hs/HederaTransactionAdapter.ts @@ -312,6 +312,7 @@ import { import { MissingRegulationSubType } from '@domain/context/factory/error/MissingRegulationSubType'; import { MissingRegulationType } from '@domain/context/factory/error/MissingRegulationType'; import { BaseContract, Contract, ContractTransaction } from 'ethers'; +import { CastInterestRateType } from '../../../domain/context/factory/InterestRateType.js'; export abstract class HederaTransactionAdapter extends TransactionAdapter { mirrorNodes: MirrorNodes; @@ -614,6 +615,7 @@ export abstract class HederaTransactionAdapter extends TransactionAdapter { const securityTokenToCreate = new FactoryBondToken( security, bondDetails, + CastInterestRateType.toNumber(bondInfo.interestRateType), beneficiaries.map((addr) => addr.toString()), beneficiariesData.map((data) => (data == '' ? '0x' : data)), ); @@ -1086,7 +1088,9 @@ export abstract class HederaTransactionAdapter extends TransactionAdapter { recordDate: BigDecimal, executionDate: BigDecimal, rate: BigDecimal, - period: BigDecimal, + startDate: BigDecimal, + endDate: BigDecimal, + fixingDate: BigDecimal, securityId: ContractId | string, ): Promise> { LogService.logTrace( @@ -1094,7 +1098,9 @@ export abstract class HederaTransactionAdapter extends TransactionAdapter { recordDate :${recordDate} , executionDate: ${executionDate}, rate : ${rate}, - period: ${period}`, + startDate: ${startDate}, + endDate: ${endDate}, + fixingDate: ${fixingDate}`, ); const coupon = { @@ -1102,7 +1108,9 @@ export abstract class HederaTransactionAdapter extends TransactionAdapter { executionDate: executionDate.toHexString(), rate: rate.toHexString(), rateDecimals: rate.decimals, - period: period.toHexString(), + startDate: startDate.toBigNumber(), + endDate: endDate.toBigNumber(), + fixingDate: fixingDate.toBigNumber(), }; return this.executeWithArgs( new BondUSAFacet__factory().attach(security.toString()), @@ -3253,6 +3261,7 @@ export abstract class HederaTransactionAdapter extends TransactionAdapter { const securityTokenToCreate = new FactoryBondToken( securityData, bondDetailsData, + CastInterestRateType.toNumber(bondDetails.interestRateType), beneficiaries.map((b) => b.toString()), beneficiariesData.map((data) => (data == '' ? '0x' : data)), ); diff --git a/packages/ats/sdk/src/port/out/rpc/RPCQueryAdapter.ts b/packages/ats/sdk/src/port/out/rpc/RPCQueryAdapter.ts index 75426950a..725402c77 100644 --- a/packages/ats/sdk/src/port/out/rpc/RPCQueryAdapter.ts +++ b/packages/ats/sdk/src/port/out/rpc/RPCQueryAdapter.ts @@ -290,7 +290,7 @@ import { ClearingTransfer, } from '@domain/context/security/Clearing'; import { HoldDetails } from '@domain/context/security/Hold'; -import { start } from 'repl'; +import { CastInterestRateType } from '@domain/context/factory/InterestRateType.js'; const LOCAL_JSON_RPC_RELAY_URL = 'http://127.0.0.1:7546/api'; @@ -682,11 +682,19 @@ export class RPCQueryAdapter { address.toString(), ).getBondDetails(); + const interestRateType = await this.connect( + BondRead__factory, + address.toString(), + ).getInterestRateType(); + return new BondDetails( res.currency, new BigDecimal(res.nominalValue.toString()), res.startingDate.toNumber(), res.maturityDate.toNumber(), + CastInterestRateType.fromNumber( + interestRateType, + ) ); } @@ -856,6 +864,9 @@ export class RPCQueryAdapter { new BigDecimal(couponInfo.coupon.rate.toString()), couponInfo.coupon.rateDecimals, couponInfo.snapshotId.toNumber(), + couponInfo.coupon.startDate.toNumber(), + couponInfo.coupon.endDate.toNumber(), + couponInfo.coupon.fixingDate.toNumber(), ); } diff --git a/packages/ats/sdk/src/port/out/rpc/RPCTransactionAdapter.ts b/packages/ats/sdk/src/port/out/rpc/RPCTransactionAdapter.ts index 0405e1eb8..1e3833101 100644 --- a/packages/ats/sdk/src/port/out/rpc/RPCTransactionAdapter.ts +++ b/packages/ats/sdk/src/port/out/rpc/RPCTransactionAdapter.ts @@ -309,6 +309,7 @@ import { import { SecurityDataBuilder } from '@domain/context/util/SecurityDataBuilder'; import NetworkService from '@service/network/NetworkService'; import MetamaskService from '@service/wallet/metamask/MetamaskService'; +import { CastInterestRateType, InterestRateType } from '@domain/context/factory/InterestRateType'; @singleton() export class RPCTransactionAdapter extends TransactionAdapter { @@ -443,6 +444,7 @@ export class RPCTransactionAdapter extends TransactionAdapter { new FactoryBondToken( security, details.bondDetails, + CastInterestRateType.toNumber(bondInfo.interestRateType), beneficiaries.map((addr) => addr.toString()), beneficiariesData.map((data) => (data == '' ? '0x' : data)), ), @@ -855,7 +857,9 @@ export class RPCTransactionAdapter extends TransactionAdapter { recordDate: BigDecimal, executionDate: BigDecimal, rate: BigDecimal, - period: BigDecimal, + startDate: BigDecimal, + endDate: BigDecimal, + fixingDate: BigDecimal, securityId?: ContractId | string, ): Promise { LogService.logTrace( @@ -863,14 +867,18 @@ export class RPCTransactionAdapter extends TransactionAdapter { recordDate :${recordDate} , executionDate: ${executionDate}, rate : ${rate}, - period: ${period}`, + startDate: ${startDate}, + endDate: ${endDate}, + fixingDate: ${fixingDate}`, ); const couponStruct: IBondRead.CouponStruct = { recordDate: recordDate.toBigNumber(), executionDate: executionDate.toBigNumber(), rate: rate.toBigNumber(), rateDecimals: rate.decimals, - period: period.toBigNumber(), + startDate: startDate.toBigNumber(), + endDate: endDate.toBigNumber(), + fixingDate: fixingDate.toBigNumber(), }; return this.executeTransaction( From 9e63dbe6ac27a2825dd797bd18ee143c4249a4b0 Mon Sep 17 00:00:00 2001 From: Alberto Molina Date: Thu, 25 Sep 2025 15:06:25 +0200 Subject: [PATCH 06/54] refactor: sdk tests fixed Signed-off-by: Alberto Molina --- .../__tests__/fixtures/bond/BondFixture.ts | 31 +++++++++++++++++-- .../command/bond/create/CreateBondCommand.ts | 4 +-- .../bond/create/CreateBondCommandHandler.ts | 4 --- .../CreateTrexSuiteBondCommand.ts | 2 +- packages/ats/sdk/src/port/in/bond/Bond.ts | 4 +-- .../sdk/src/port/in/bond/Bond.unit.test.ts | 2 ++ .../port/out/hs/HederaTransactionAdapter.ts | 2 +- .../sdk/src/port/out/rpc/RPCQueryAdapter.ts | 2 +- 8 files changed, 38 insertions(+), 13 deletions(-) diff --git a/packages/ats/sdk/__tests__/fixtures/bond/BondFixture.ts b/packages/ats/sdk/__tests__/fixtures/bond/BondFixture.ts index 798872b6e..71d954e3c 100644 --- a/packages/ats/sdk/__tests__/fixtures/bond/BondFixture.ts +++ b/packages/ats/sdk/__tests__/fixtures/bond/BondFixture.ts @@ -255,7 +255,14 @@ import { import AddBeneficiaryRequest from '@port/in/request/bond/AddBeneficiaryRequest'; import RemoveBeneficiaryRequest from '@port/in/request/bond/RemoveBeneficiaryRequest'; import UpdateBeneficiaryDataRequest from '@port/in/request/bond/UpdateBeneficiaryDataRequest'; -import { InterestRateType } from '../../../src/domain/context/factory/InterestRateType.js'; +import { + CastInterestRateType, + InterestRateType, +} from '@domain/context/factory/InterestRateType'; + +const interestRateValues = Object.values( + InterestRateType, +) as InterestRateType[]; export const SetCouponCommandFixture = createFixture( (command) => { @@ -292,6 +299,10 @@ export const CreateBondCommandFixture = createFixture( command.maturityDate.faker((faker) => faker.date.future({ years: 2 }).getTime().toString(), ); + const interestRateType = CastInterestRateType.toNumber( + faker.helpers.arrayElement(interestRateValues), + ); + command.interestRateType.as(() => interestRateType); command.factory?.as( () => new ContractId(ContractIdPropFixture.create().value), ); @@ -374,6 +385,10 @@ export const CreateTrexSuiteBondCommandFixture = command.beneficiariesData?.faker((faker) => [ faker.string.alphanumeric({ length: 32 }), ]); + const interestRateType = CastInterestRateType.toNumber( + faker.helpers.arrayElement(interestRateValues), + ); + command.interestRateType.as(() => interestRateType); }); export const UpdateMaturityDateCommandFixture = @@ -401,6 +416,11 @@ export const BondDetailsFixture = createFixture((props) => { ); props.startingDate.faker((faker) => faker.date.past()); props.maturityDate.faker((faker) => faker.date.recent()); + props.interestRateType.faker((faker) => + CastInterestRateType.toNumber( + faker.helpers.arrayElement(interestRateValues), + ), + ); }); export const GetCouponQueryFixture = createFixture((query) => { @@ -554,6 +574,10 @@ export const CreateBondRequestFixture = createFixture( HederaIdPropsFixture.create().value, ]); request.beneficiariesData?.as(() => ['0x0000']); + const interestRateType = CastInterestRateType.toNumber( + faker.helpers.arrayElement(interestRateValues), + ); + request.interestRateType.as(() => interestRateType); }, ); @@ -705,7 +729,10 @@ export const CreateTrexSuiteBondRequestFixture = maturityDate = faker.date.future({ years: 2 }); return maturityDate.getTime().toString(); }); - request.interestRateType.as(() => InterestRateType.FIXED_FOR_ALL_COUPONS); + const interestRateType = CastInterestRateType.toNumber( + faker.helpers.arrayElement(interestRateValues), + ); + request.interestRateType.as(() => interestRateType); request.configId.faker( (faker) => diff --git a/packages/ats/sdk/src/app/usecase/command/bond/create/CreateBondCommand.ts b/packages/ats/sdk/src/app/usecase/command/bond/create/CreateBondCommand.ts index ee414d284..f07ebf14a 100644 --- a/packages/ats/sdk/src/app/usecase/command/bond/create/CreateBondCommand.ts +++ b/packages/ats/sdk/src/app/usecase/command/bond/create/CreateBondCommand.ts @@ -207,7 +207,7 @@ import { Command } from '@core/command/Command'; import { CommandResponse } from '@core/command/CommandResponse'; import ContractId from '@domain/context/contract/ContractId'; import { SecurityProps } from '@domain/context/security/Security'; -import { InterestRateType } from '@domain/context/factory/InterestRateType.js'; +import { InterestRateType } from '@domain/context/factory/InterestRateType'; export class CreateBondCommandResponse implements CommandResponse { public readonly securityId: ContractId; @@ -226,6 +226,7 @@ export class CreateBondCommand extends Command { public readonly nominalValue: string, public readonly startingDate: string, public readonly maturityDate: string, + public readonly interestRateType: InterestRateType, public readonly factory?: ContractId, public readonly resolver?: ContractId, public readonly configId?: string, @@ -238,7 +239,6 @@ export class CreateBondCommand extends Command { public readonly identityRegistryId?: string, public readonly beneficiariesIds?: string[], public readonly beneficiariesData?: string[], - public readonly interestRateType?: InterestRateType, ) { super(); } diff --git a/packages/ats/sdk/src/app/usecase/command/bond/create/CreateBondCommandHandler.ts b/packages/ats/sdk/src/app/usecase/command/bond/create/CreateBondCommandHandler.ts index b7f2ef97e..bc745c48e 100644 --- a/packages/ats/sdk/src/app/usecase/command/bond/create/CreateBondCommandHandler.ts +++ b/packages/ats/sdk/src/app/usecase/command/bond/create/CreateBondCommandHandler.ts @@ -225,7 +225,6 @@ import { Response } from '@domain/context/transaction/Response'; import { MissingRegulationType } from '@domain/context/factory/error/MissingRegulationType'; import { MissingRegulationSubType } from '@domain/context/factory/error/MissingRegulationSubType'; import { EVM_ZERO_ADDRESS } from '@core/Constants'; -import { MissingInterestRateType } from '@domain/context/factory/error/MissingInterestRateType'; @CommandHandler(CreateBondCommand) export class CreateBondCommandHandler @@ -290,9 +289,6 @@ export class CreateBondCommandHandler if (!security.regulationsubType) { throw new MissingRegulationSubType(); } - if (!interestRateType) { - throw new MissingInterestRateType(); - } const diamondOwnerAccountEvmAddress: EvmAddress = await this.accountService.getAccountEvmAddress(diamondOwnerAccount!); diff --git a/packages/ats/sdk/src/app/usecase/command/bond/createTrexSuite/CreateTrexSuiteBondCommand.ts b/packages/ats/sdk/src/app/usecase/command/bond/createTrexSuite/CreateTrexSuiteBondCommand.ts index d75feb752..0f0451ad6 100644 --- a/packages/ats/sdk/src/app/usecase/command/bond/createTrexSuite/CreateTrexSuiteBondCommand.ts +++ b/packages/ats/sdk/src/app/usecase/command/bond/createTrexSuite/CreateTrexSuiteBondCommand.ts @@ -207,7 +207,7 @@ import { Command } from '@core/command/Command'; import { CommandResponse } from '@core/command/CommandResponse'; import ContractId from '@domain/context/contract/ContractId'; import { SecurityProps } from '@domain/context/security/Security'; -import { InterestRateType } from '@domain/context/factory/InterestRateType.js'; +import { InterestRateType } from '@domain/context/factory/InterestRateType'; export class CreateTrexSuiteBondCommandResponse implements CommandResponse { public readonly securityId: ContractId; diff --git a/packages/ats/sdk/src/port/in/bond/Bond.ts b/packages/ats/sdk/src/port/in/bond/Bond.ts index fcfdd6ad4..ca5351dc8 100644 --- a/packages/ats/sdk/src/port/in/bond/Bond.ts +++ b/packages/ats/sdk/src/port/in/bond/Bond.ts @@ -262,7 +262,7 @@ import { GetBeneficiariesCountRequest, GetBeneficiariesRequest, } from '../request'; -import { CastInterestRateType } from '../../../domain/context/factory/InterestRateType.js'; +import { CastInterestRateType } from '@domain/context/factory/InterestRateType'; interface IBondInPort { create( @@ -361,6 +361,7 @@ class BondInPort implements IBondInPort { req.nominalValue, req.startingDate, req.maturityDate, + CastInterestRateType.fromNumber(req.interestRateType), securityFactory ? new ContractId(securityFactory) : undefined, resolver ? new ContractId(resolver) : undefined, req.configId, @@ -373,7 +374,6 @@ class BondInPort implements IBondInPort { req.identityRegistryId, req.beneficiariesIds, req.beneficiariesData, - CastInterestRateType.fromNumber(req.interestRateType), ), ); diff --git a/packages/ats/sdk/src/port/in/bond/Bond.unit.test.ts b/packages/ats/sdk/src/port/in/bond/Bond.unit.test.ts index f8a467fc1..7e9b8d486 100644 --- a/packages/ats/sdk/src/port/in/bond/Bond.unit.test.ts +++ b/packages/ats/sdk/src/port/in/bond/Bond.unit.test.ts @@ -387,6 +387,7 @@ describe('Bond', () => { createBondRequest.nominalValue, createBondRequest.startingDate, createBondRequest.maturityDate, + CastInterestRateType.fromNumber(createBondRequest.interestRateType), new ContractId(factoryAddress), new ContractId(resolverAddress), createBondRequest.configId, @@ -452,6 +453,7 @@ describe('Bond', () => { createBondRequest.nominalValue, createBondRequest.startingDate, createBondRequest.maturityDate, + CastInterestRateType.fromNumber(createBondRequest.interestRateType), new ContractId(factoryAddress), new ContractId(resolverAddress), createBondRequest.configId, diff --git a/packages/ats/sdk/src/port/out/hs/HederaTransactionAdapter.ts b/packages/ats/sdk/src/port/out/hs/HederaTransactionAdapter.ts index 5515e1329..9d63bf1c0 100644 --- a/packages/ats/sdk/src/port/out/hs/HederaTransactionAdapter.ts +++ b/packages/ats/sdk/src/port/out/hs/HederaTransactionAdapter.ts @@ -312,7 +312,7 @@ import { import { MissingRegulationSubType } from '@domain/context/factory/error/MissingRegulationSubType'; import { MissingRegulationType } from '@domain/context/factory/error/MissingRegulationType'; import { BaseContract, Contract, ContractTransaction } from 'ethers'; -import { CastInterestRateType } from '../../../domain/context/factory/InterestRateType.js'; +import { CastInterestRateType } from '../../../domain/context/factory/InterestRateType'; export abstract class HederaTransactionAdapter extends TransactionAdapter { mirrorNodes: MirrorNodes; diff --git a/packages/ats/sdk/src/port/out/rpc/RPCQueryAdapter.ts b/packages/ats/sdk/src/port/out/rpc/RPCQueryAdapter.ts index 725402c77..189e44b7b 100644 --- a/packages/ats/sdk/src/port/out/rpc/RPCQueryAdapter.ts +++ b/packages/ats/sdk/src/port/out/rpc/RPCQueryAdapter.ts @@ -290,7 +290,7 @@ import { ClearingTransfer, } from '@domain/context/security/Clearing'; import { HoldDetails } from '@domain/context/security/Hold'; -import { CastInterestRateType } from '@domain/context/factory/InterestRateType.js'; +import { CastInterestRateType } from '@domain/context/factory/InterestRateType'; const LOCAL_JSON_RPC_RELAY_URL = 'http://127.0.0.1:7546/api'; From 23ca72d7fa750b27fa41ce89d3183e0b63404c14 Mon Sep 17 00:00:00 2001 From: Alberto Molina Date: Thu, 25 Sep 2025 15:18:30 +0200 Subject: [PATCH 07/54] test: smart contract bond tests added Signed-off-by: Alberto Molina --- .../test/unitTests/layer_1/bond/bond.test.ts | 68 +++++++++++++++++-- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/packages/ats/contracts/test/unitTests/layer_1/bond/bond.test.ts b/packages/ats/contracts/test/unitTests/layer_1/bond/bond.test.ts index 6f09ba685..839b1ec72 100644 --- a/packages/ats/contracts/test/unitTests/layer_1/bond/bond.test.ts +++ b/packages/ats/contracts/test/unitTests/layer_1/bond/bond.test.ts @@ -712,19 +712,19 @@ describe('Bond Tests', () => { ).to.be.rejectedWith('TokenIsPaused') }) - it('GIVEN an account with corporateActions role WHEN setCoupon with wrong dates THEN transaction fails', async () => { + it('GIVEN an account with corporateActions role WHEN setCoupon with wrong record date THEN transaction fails', async () => { await accessControlFacet .connect(signer_A) .grantRole(CORPORATE_ACTION_ROLE, account_C) // set coupon const wrongcouponData_1 = { - recordDate: couponExecutionDateInSeconds.toString(), - executionDate: couponRecordDateInSeconds.toString(), + recordDate: (couponExecutionDateInSeconds + 1).toString(), + executionDate: couponExecutionDateInSeconds.toString(), rate: couponRate, rateDecimals: couponRateDecimals, - startDate: couponStartDateInSeconds, - endDate: couponEndDateInSeconds, - fixingDate: couponFixingDateInSeconds, + startDate: couponStartDateInSeconds.toString(), + endDate: couponEndDateInSeconds.toString(), + fixingDate: couponFixingDateInSeconds.toString(), } await expect( @@ -748,6 +748,62 @@ describe('Bond Tests', () => { ).to.be.revertedWithCustomError(bondFacet, 'WrongTimestamp') }) + it('GIVEN an account with corporateActions role WHEN setCoupon with wrong start and end dates THEN transaction fails', async () => { + await accessControlFacet + .connect(signer_A) + .grantRole(CORPORATE_ACTION_ROLE, account_C) + // set coupon + const wrongcouponData = { + recordDate: couponRecordDateInSeconds.toString(), + executionDate: couponExecutionDateInSeconds.toString(), + rate: couponRate, + rateDecimals: couponRateDecimals, + startDate: (couponEndDateInSeconds + 1).toString(), + endDate: couponEndDateInSeconds.toString(), + fixingDate: couponFixingDateInSeconds.toString(), + } + + await expect( + bondFacet.connect(signer_C).setCoupon(wrongcouponData) + ).to.be.revertedWithCustomError(bondFacet, 'WrongDates') + }) + + it('GIVEN an account with corporateActions role WHEN setCoupon with wrong fixing dates THEN transaction fails', async () => { + await accessControlFacet + .connect(signer_A) + .grantRole(CORPORATE_ACTION_ROLE, account_C) + // set coupon + const wrongcouponData_1 = { + recordDate: couponRecordDateInSeconds.toString(), + executionDate: couponExecutionDateInSeconds.toString(), + rate: couponRate, + rateDecimals: couponRateDecimals, + startDate: couponStartDateInSeconds.toString(), + endDate: couponEndDateInSeconds.toString(), + fixingDate: (couponExecutionDateInSeconds + 1).toString(), + } + + await expect( + bondFacet.connect(signer_C).setCoupon(wrongcouponData_1) + ).to.be.revertedWithCustomError(bondFacet, 'WrongDates') + + const wrongcouponData_2 = { + recordDate: couponRecordDateInSeconds.toString(), + executionDate: couponExecutionDateInSeconds.toString(), + rate: couponRate, + rateDecimals: couponRateDecimals, + startDate: couponStartDateInSeconds, + endDate: couponEndDateInSeconds, + fixingDate: ( + (await ethers.provider.getBlock('latest')).timestamp - 1 + ).toString(), + } + + await expect( + bondFacet.connect(signer_C).setCoupon(wrongcouponData_2) + ).to.be.revertedWithCustomError(bondFacet, 'WrongTimestamp') + }) + it('GIVEN an account with corporateActions role WHEN setCoupon with period THEN period is stored correctly', async () => { // Granting Role to account C accessControlFacet = accessControlFacet.connect(signer_A) From 69584db6c500c1919a16027da8a20c5182ba9243 Mon Sep 17 00:00:00 2001 From: Alberto Molina Date: Thu, 25 Sep 2025 15:49:32 +0200 Subject: [PATCH 08/54] refactor: sdk updated Signed-off-by: Alberto Molina --- .../ats/sdk/src/domain/context/bond/Coupon.ts | 18 +++++++++--------- packages/ats/sdk/src/port/in/bond/Bond.ts | 6 +++--- .../src/port/in/response/CouponViewModel.ts | 6 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/ats/sdk/src/domain/context/bond/Coupon.ts b/packages/ats/sdk/src/domain/context/bond/Coupon.ts index 1f0ecf247..c8dddf4fc 100644 --- a/packages/ats/sdk/src/domain/context/bond/Coupon.ts +++ b/packages/ats/sdk/src/domain/context/bond/Coupon.ts @@ -212,9 +212,9 @@ export class Coupon extends ValidatedDomain { executionTimeStamp: number; rate: BigDecimal; rateDecimals: number; - startDate: number; - endDate: number; - fixingDate: number; + startTimeStamp: number; + endTimeStamp: number; + fixingTimeStamp: number; snapshotId?: number; @@ -223,9 +223,9 @@ export class Coupon extends ValidatedDomain { executionTimeStamp: number, rate: BigDecimal, rateDecimals: number, - startDate: number, - endDate: number, - fixingDate: number, + startTimeStamp: number, + endTimeStamp: number, + fixingTimeStamp: number, snapshotId?: number, ) { super({ @@ -238,9 +238,9 @@ export class Coupon extends ValidatedDomain { this.executionTimeStamp = executionTimeStamp; this.rate = rate; this.rateDecimals = rateDecimals; - this.startDate = startDate; - this.endDate = endDate; - this.fixingDate = fixingDate; + this.startTimeStamp = startTimeStamp; + this.endTimeStamp = endTimeStamp; + this.fixingTimeStamp = fixingTimeStamp; this.snapshotId = snapshotId ? snapshotId : undefined; ValidatedDomain.handleValidation(Coupon.name, this); diff --git a/packages/ats/sdk/src/port/in/bond/Bond.ts b/packages/ats/sdk/src/port/in/bond/Bond.ts index ca5351dc8..e77968fec 100644 --- a/packages/ats/sdk/src/port/in/bond/Bond.ts +++ b/packages/ats/sdk/src/port/in/bond/Bond.ts @@ -484,9 +484,9 @@ class BondInPort implements IBondInPort { executionDate: new Date(res.coupon.executionTimeStamp * ONE_THOUSAND), rate: res.coupon.rate.toString(), rateDecimals: res.coupon.rateDecimals, - startDate: res.coupon.startDate, - endDate: res.coupon.endDate, - fixingDate: res.coupon.fixingDate, + startDate: new Date(res.coupon.startTimeStamp * ONE_THOUSAND), + endDate: new Date(res.coupon.endTimeStamp * ONE_THOUSAND), + fixingDate: new Date(res.coupon.fixingTimeStamp * ONE_THOUSAND), }; return coupon; diff --git a/packages/ats/sdk/src/port/in/response/CouponViewModel.ts b/packages/ats/sdk/src/port/in/response/CouponViewModel.ts index bab8edfde..0c3ff6353 100644 --- a/packages/ats/sdk/src/port/in/response/CouponViewModel.ts +++ b/packages/ats/sdk/src/port/in/response/CouponViewModel.ts @@ -211,8 +211,8 @@ export default interface CouponViewModel extends QueryResponse { executionDate: Date; rate: string; rateDecimals: number; - startDate: number; - endDate: number; - fixingDate: number; + startDate: Date; + endDate: Date; + fixingDate: Date; snapshotId?: number; } From 6cd573308388073893363a73b6bd4efce6da8b7f Mon Sep 17 00:00:00 2001 From: Alberto Molina Date: Thu, 25 Sep 2025 15:55:09 +0200 Subject: [PATCH 09/54] refactor: sdk tests fixed Signed-off-by: Alberto Molina --- packages/ats/sdk/__tests__/fixtures/bond/BondFixture.ts | 5 +++++ packages/ats/sdk/src/port/in/bond/Bond.unit.test.ts | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/packages/ats/sdk/__tests__/fixtures/bond/BondFixture.ts b/packages/ats/sdk/__tests__/fixtures/bond/BondFixture.ts index 71d954e3c..23509f569 100644 --- a/packages/ats/sdk/__tests__/fixtures/bond/BondFixture.ts +++ b/packages/ats/sdk/__tests__/fixtures/bond/BondFixture.ts @@ -473,6 +473,11 @@ export const CouponFixture = createFixture((props) => { (faker) => new BigDecimal(BigNumber.from(faker.number.int({ min: 1, max: 5 }))), ); + props.startTimeStamp.faker((faker) => faker.date.past().getTime().toString()); + props.endTimeStamp.faker((faker) => faker.date.past().getTime().toString()); + props.fixingTimeStamp.faker((faker) => + faker.date.past().getTime().toString(), + ); }); export const GetCouponHoldersRequestFixture = diff --git a/packages/ats/sdk/src/port/in/bond/Bond.unit.test.ts b/packages/ats/sdk/src/port/in/bond/Bond.unit.test.ts index 7e9b8d486..5f87a94d3 100644 --- a/packages/ats/sdk/src/port/in/bond/Bond.unit.test.ts +++ b/packages/ats/sdk/src/port/in/bond/Bond.unit.test.ts @@ -1059,6 +1059,15 @@ describe('Bond', () => { expectedResponse2.coupon.executionTimeStamp * ONE_THOUSAND, ), rate: expectedResponse2.coupon.rate.toString(), + startDate: new Date( + expectedResponse2.coupon.startTimeStamp * ONE_THOUSAND, + ), + endDate: new Date( + expectedResponse2.coupon.endTimeStamp * ONE_THOUSAND, + ), + fixingDate: new Date( + expectedResponse2.coupon.fixingTimeStamp * ONE_THOUSAND, + ), }, ]), ); From ba417c162176bdd831fd840c7a5488ce326e319b Mon Sep 17 00:00:00 2001 From: Alberto Molina Date: Thu, 25 Sep 2025 16:03:32 +0200 Subject: [PATCH 10/54] refactor: web updated to match new bonds and coupons Signed-off-by: Alberto Molina --- apps/ats/web/src/i18n/en/security/coupons.ts | 29 ++-- .../CreateBond/Components/StepReview.tsx | 1 + .../Components/Coupons/CouponsList.tsx | 22 ++- .../Components/Coupons/ProgramCoupon.tsx | 137 +++++++++++------- .../Components/Coupons/SeeCoupon.tsx | 20 ++- 5 files changed, 132 insertions(+), 77 deletions(-) diff --git a/apps/ats/web/src/i18n/en/security/coupons.ts b/apps/ats/web/src/i18n/en/security/coupons.ts index e733b165b..a15814ad7 100644 --- a/apps/ats/web/src/i18n/en/security/coupons.ts +++ b/apps/ats/web/src/i18n/en/security/coupons.ts @@ -240,18 +240,21 @@ export default { placeholder: '0,123%', tooltip: 'Interest rate for the coupon.', }, - period: { - label: 'Coupon period', - placeholder: 'Select coupon period', + startDate: { + label: 'Start date', + placeholder: 'Select start date', + tooltip: 'Coupon’s start date, must occur before the end date.', + }, + endDate: { + label: 'End date', + placeholder: 'Select end date', tooltip: - 'The period between coupon payments. This field is required for all coupon operations.', - options: { - day: '1 Day', - week: '1 Week', - month: '1 Month', - quarter: '3 Months', - year: '1 Year', - }, + 'Coupon’s end date, Accrual period correspond to the period between start and end date.', + }, + fixingDate: { + label: 'Fixing date', + placeholder: 'Select fixing date', + tooltip: 'Coupon’s fixing date, floating rate coupons only.', }, }, }, @@ -275,7 +278,9 @@ export default { details: { title: 'Detail', paymentDay: 'Payment day', - period: 'Period', + startDay: 'start day', + endDay: 'end day', + fixingDay: 'fixing day', amount: 'Amount', }, }, diff --git a/apps/ats/web/src/views/CreateBond/Components/StepReview.tsx b/apps/ats/web/src/views/CreateBond/Components/StepReview.tsx index 0fec1564d..02ad6f807 100644 --- a/apps/ats/web/src/views/CreateBond/Components/StepReview.tsx +++ b/apps/ats/web/src/views/CreateBond/Components/StepReview.tsx @@ -335,6 +335,7 @@ export const StepReview = () => { nominalValue: (nominalValue * NOMINAL_VALUE_FACTOR).toString(), startingDate: dateToUnixTimestamp(startingDate), maturityDate: dateToUnixTimestamp(maturityDate), + interestRateType: 1, currency: '0x' + currency.charCodeAt(0) + diff --git a/apps/ats/web/src/views/DigitalSecurityDetails/Components/Coupons/CouponsList.tsx b/apps/ats/web/src/views/DigitalSecurityDetails/Components/Coupons/CouponsList.tsx index 4c1c5ad34..b8df749b0 100644 --- a/apps/ats/web/src/views/DigitalSecurityDetails/Components/Coupons/CouponsList.tsx +++ b/apps/ats/web/src/views/DigitalSecurityDetails/Components/Coupons/CouponsList.tsx @@ -8,11 +8,7 @@ import { createColumnHelper } from '@tanstack/table-core'; import { Table, Text } from 'io-bricks-ui'; import { useTranslation } from 'react-i18next'; import { DATE_TIME_FORMAT } from '../../../../utils/constants'; -import { - formatDate, - formatCouponPeriod, - formatNumberLocale, -} from '../../../../utils/format'; +import { formatDate, formatNumberLocale } from '../../../../utils/format'; export const CouponsList = () => { const { id } = useParams(); @@ -54,9 +50,19 @@ export const CouponsList = () => { `${formatNumberLocale(row.getValue(), row.row.original.rateDecimals ?? 0)}%`, enableSorting: false, }), - columnHelper.accessor('period', { - header: t('columns.period'), - cell: (row) => formatCouponPeriod(row.getValue()), + columnHelper.accessor('startDate', { + header: t('columns.startDate'), + cell: (row) => formatDate(row.getValue(), DATE_TIME_FORMAT), + enableSorting: false, + }), + columnHelper.accessor('endDate', { + header: t('columns.endDate'), + cell: (row) => formatDate(row.getValue(), DATE_TIME_FORMAT), + enableSorting: false, + }), + columnHelper.accessor('fixingDate', { + header: t('columns.fixingDate'), + cell: (row) => formatDate(row.getValue(), DATE_TIME_FORMAT), enableSorting: false, }), columnHelper.accessor('snapshotId', { diff --git a/apps/ats/web/src/views/DigitalSecurityDetails/Components/Coupons/ProgramCoupon.tsx b/apps/ats/web/src/views/DigitalSecurityDetails/Components/Coupons/ProgramCoupon.tsx index 6c8bb46a5..79a907100 100644 --- a/apps/ats/web/src/views/DigitalSecurityDetails/Components/Coupons/ProgramCoupon.tsx +++ b/apps/ats/web/src/views/DigitalSecurityDetails/Components/Coupons/ProgramCoupon.tsx @@ -208,7 +208,6 @@ import { CalendarInputController, InputNumberController, PhosphorIcon, - SelectController, Text, Tooltip, } from 'io-bricks-ui'; @@ -223,18 +222,17 @@ import { import { useParams } from 'react-router-dom'; import { useCoupons } from '../../../../hooks/queries/useCoupons'; import { useGetBondDetails } from '../../../../hooks/queries/useGetSecurityDetails'; -import { - dateToUnixTimestamp, - validateCouponPeriod, -} from '../../../../utils/format'; -import { DATE_TIME_FORMAT, TIME_PERIODS_S } from '../../../../utils/constants'; +import { dateToUnixTimestamp } from '../../../../utils/format'; +import { DATE_TIME_FORMAT } from '../../../../utils/constants'; import { isBeforeDate } from '../../../../utils/helpers'; interface ProgramCouponFormValues { rate: number; recordTimestamp: string; executionTimestamp: string; - period: string; + startTimestamp: string; + endTimestamp: string; + fixingTimestamp: string; } export const ProgramCoupon = () => { @@ -249,6 +247,8 @@ export const ProgramCoupon = () => { const { t: tGlobal } = useTranslation('globals'); const { id = '' } = useParams(); const recordTimestamp = watch('recordTimestamp'); + const startTimestamp = watch('startTimestamp'); + const fixingTimestamp = watch('fixingTimestamp'); const { data: bondDetails } = useGetBondDetails( new GetBondDetailsRequest({ @@ -262,7 +262,9 @@ export const ProgramCoupon = () => { rate: params.rate.toString(), recordTimestamp: dateToUnixTimestamp(params.recordTimestamp), executionTimestamp: dateToUnixTimestamp(params.executionTimestamp), - period: params.period, + startTimestamp: dateToUnixTimestamp(params.startTimestamp), + endTimestamp: dateToUnixTimestamp(params.endTimestamp), + fixingTimestamp: dateToUnixTimestamp(params.fixingTimestamp), }); createCoupon(request, { @@ -321,7 +323,9 @@ export const ProgramCoupon = () => { id="executionTimestamp" rules={{ required, - validate: isAfterDate(new Date(recordTimestamp)), + validate: + isAfterDate(new Date(recordTimestamp)) && + isAfterDate(new Date(fixingTimestamp)), }} fromDate={new Date()} toDate={new Date(bondDetails.maturityDate)} @@ -331,6 +335,77 @@ export const ProgramCoupon = () => { /> )} + + + + {tForm('startDate.label')}* + + + + + + {bondDetails && ( + + )} + + + + + {tForm('endDate.label')}* + + + + + + {bondDetails && ( + + )} + + + + + {tForm('fixingDate.label')}* + + + + + + {bondDetails && ( + + )} + {tForm('rate.label')}* @@ -351,50 +426,6 @@ export const ProgramCoupon = () => { decimalSeparator="." /> - - - - {tForm('period.label')}* - - - - - - { - const validation = validateCouponPeriod(parseInt(value)); - return validation === true || validation; - }, - }} - placeholder={tForm('period.placeholder')} - options={[ - { - label: tForm('period.options.day'), - value: TIME_PERIODS_S.DAY.toString(), - }, - { - label: tForm('period.options.week'), - value: TIME_PERIODS_S.WEEK.toString(), - }, - { - label: tForm('period.options.month'), - value: TIME_PERIODS_S.MONTH.toString(), - }, - { - label: tForm('period.options.quarter'), - value: TIME_PERIODS_S.QUARTER.toString(), - }, - { - label: tForm('period.options.year'), - value: TIME_PERIODS_S.YEAR.toString(), - }, - ]} - /> -