Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions contracts/docs/contracts-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,6 @@ Key functions:
then runs DvP settlement for each allocation.
- `cancelAuction(isin)`:
cancels an active auction and reduces the reserved offering accordingly.
- `withdrawFailedIssuance(isin)`:
recovers unsold or failed-to-settle bonds still held by the manager contract.
- `payCoupon(isin, holders)`:
pays the next coupon to the provided holder list and advances coupon state.
On the final period it also repays principal, burns every unit (unsold units
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,12 +273,6 @@ error BondDoesNotExist(string isin);
error IncorrectBondState(string isin, bool expected);
```

### NoFailedIssuance

```solidity
error NoFailedIssuance();
```

### GovReserveAddressZero

```solidity
Expand Down Expand Up @@ -321,6 +315,12 @@ error AllCouponsPaid(string isin);
error CouponPaymentBalanceMismatch(string isin, uint256 processedBalance, uint256 totalSupply);
```

### DuplicateHolder

```solidity
error DuplicateHolder(string isin, address holder);
```

### BondAlreadyDisabled

```solidity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -402,21 +402,6 @@ function getSealedBids(string calldata _isin) external view returns (IBondAuctio
|`<none>`|`IBondAuction.Bid[]`|bids Array of sealed bids.|


### withdrawFailedIssuance

Allows the issuer to recover bonds that failed to settle during DVP.


```solidity
function withdrawFailedIssuance(string calldata _isin) external onlyRole(Roles.BOND_MANAGER_ROLE);
```
**Parameters**

|Name|Type|Description|
|----|----|-----------|
|`_isin`|`string`|Target ISIN with failed issuance.|


### payCoupon

Pay the next coupon to every holder; the final coupon also repays principal and closes the bond.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,24 @@ event BondBuybackComplete(bytes32 indexed id, string isin, uint256 total);
event CouponPaid(string indexed isin, address indexed holder, uint256 paymentAmount, uint256 paymentNumber);
```

### CouponPeriodPaid

Emitted once per coupon period after the payment count advances, whether or not any
holder received cash (units held by the manager earn nothing).

```solidity
event CouponPeriodPaid(string indexed isin, uint256 paymentNumber, uint256 holdersPaid, uint256 couponPaid);
```

**Parameters**

|Name|Type|Description|
|----|----|-----------|
|`isin`|`string`|ISIN of the bond.|
|`paymentNumber`|`uint256`|1-based period that was just paid.|
|`holdersPaid`|`uint256`|Number of holders that received a coupon this period.|
|`couponPaid`|`uint256`|Total coupon paid this period in WNOK.|

### BondRedeemed

```solidity
Expand Down
6 changes: 6 additions & 0 deletions contracts/script/private-bank/08_TbdSetup.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ contract TbdSetupScript is RegistryScript {

/// @notice Add CSD to DNB allowlist
_csdSetup(tbdDnb, dnb.key, vm.addr(vm.envUint("PK_CSD")));

/// @notice Allow both dealer accounts to hold TBD Nordea (Banking page demos)
vm.startBroadcast(nordea.key);
tbdNordea.add(nordea.addr);
tbdNordea.add(dnb.addr);
vm.stopBroadcast();
}

// internal function to set up investors
Expand Down
2 changes: 1 addition & 1 deletion contracts/src/common/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,14 @@ library Errors {
error BondUnitNominalZero();
error BondDoesNotExist(string isin);
error IncorrectBondState(string isin, bool expected);
error NoFailedIssuance();
error GovReserveAddressZero();
error RedemptionIncomplete(string _isin, uint256 remaining);
error BuybackExceedsSupply(string isin, uint256 buybackSize, uint256 currentSupply);
error BuybackOfferingZero(string isin);
error CouponNotReady(string isin, uint256 nextPaymentTime, uint256 currentTime);
error AllCouponsPaid(string isin);
error CouponPaymentBalanceMismatch(string isin, uint256 processedBalance, uint256 totalSupply);
error DuplicateHolder(string isin, address holder);
error BondAlreadyDisabled(string isin);
error BondNotEmpty(string isin, uint256 supply);
error BondHasFinalisedAuction(string isin, bytes32 auctionId);
Expand Down
66 changes: 30 additions & 36 deletions contracts/src/norges-bank/BondManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,27 @@ import {Roles} from "@common/Roles.sol";
contract BondManager is IBondManager, AccessControl {
uint256 private constant PERCENTAGE_PRECISION = 10000; // bps precision (e.g., 425 = 4.25%)

/**
* @dev Running totals of one coupon period, in units (processed, unsold) and WNOK (coupon, principal).
*/
struct PayoutTotals {
uint256 processed;
uint256 unsold;
uint256 holdersPaid;
uint256 coupon;
uint256 principal;
}

/**
* @dev Parameters of the coupon period being paid.
*/
struct Period {
bytes32 partition;
uint256 paymentPerBond;
uint256 paymentNumber;
bool finalPeriod;
}

string public name;

/**
Expand Down Expand Up @@ -467,22 +488,6 @@ contract BondManager is IBondManager, AccessControl {
return BOND_AUCTION.getSealedBids(auctionId);
}

/**
* @notice Allows the issuer to recover bonds that failed to settle during DVP.
* @param _isin Target ISIN with failed issuance.
*/
function withdrawFailedIssuance(string calldata _isin) external onlyRole(Roles.BOND_MANAGER_ROLE) {
bytes32 partition = BOND_TOKEN.isinToPartition(_isin);
uint256 failedIssuance = BOND_TOKEN.balanceOfByPartition(partition, address(this));

if (failedIssuance == 0) revert Errors.NoFailedIssuance();

bytes32 returnedPartition = BOND_TOKEN.transferByPartition(partition, msg.sender, failedIssuance, "");
if (returnedPartition != partition) {
revert Errors.SettlementFailure(uint8(IBondDvP.FailureReason.Security), "partition mismatch");
}
}

/**
* @notice Pay the next coupon to every holder; the final coupon also repays principal and closes the bond.
* @param _isin ISIN string
Expand Down Expand Up @@ -543,6 +548,7 @@ contract BondManager is IBondManager, AccessControl {
}

BOND_TOKEN.updateCouponPayment(_isin, block.timestamp, paymentNumber);
emit CouponPeriodPaid(_isin, paymentNumber, totals.holdersPaid, totals.coupon);

if (finalPeriod) {
if (totals.unsold > 0) {
Expand All @@ -556,26 +562,6 @@ contract BondManager is IBondManager, AccessControl {
}
}

/**
* @dev Running totals of one coupon period, in units (processed, unsold) and WNOK (coupon, principal).
*/
struct PayoutTotals {
uint256 processed;
uint256 unsold;
uint256 coupon;
uint256 principal;
}

/**
* @dev Parameters of the coupon period being paid.
*/
struct Period {
bytes32 partition;
uint256 paymentPerBond;
uint256 paymentNumber;
bool finalPeriod;
}

/**
* @dev Pays every listed holder for one period. Units held by this contract were never sold:
* they are counted as unsold and receive nothing.
Expand All @@ -585,6 +571,13 @@ contract BondManager is IBondManager, AccessControl {
returns (PayoutTotals memory totals)
{
for (uint256 i = 0; i < _holders.length; i++) {
// A repeated address would be paid twice while the coverage check still passes
// if another holder were omitted, so reject duplicates outright.
for (uint256 j = 0; j < i; j++) {
if (_holders[j] == _holders[i]) {
revert Errors.DuplicateHolder(_isin, _holders[i]);
}
}
uint256 balance = BOND_TOKEN.balanceOfByPartition(_p.partition, _holders[i]);
if (balance == 0) {
continue;
Expand All @@ -595,6 +588,7 @@ contract BondManager is IBondManager, AccessControl {
}
(uint256 couponAmount, uint256 principal) = _payHolder(_isin, _holders[i], balance, _p);
totals.processed += balance;
totals.holdersPaid += 1;
totals.coupon += couponAmount;
totals.principal += principal;
}
Expand Down
10 changes: 10 additions & 0 deletions contracts/src/norges-bank/interfaces/IBondManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ interface IBondManager {
// Coupon payment event (one per holder per period)
event CouponPaid(string indexed isin, address indexed holder, uint256 paymentAmount, uint256 paymentNumber);

/**
* @notice Emitted once per coupon period after the payment count advances, whether or not any
* holder received cash (units held by the manager earn nothing).
* @param isin ISIN of the bond.
* @param paymentNumber 1-based period that was just paid.
* @param holdersPaid Number of holders that received a coupon this period.
* @param couponPaid Total coupon paid this period in WNOK.
*/
event CouponPeriodPaid(string indexed isin, uint256 paymentNumber, uint256 holdersPaid, uint256 couponPaid);

// Principal repayment event (one per holder, final period only)
event BondRedeemed(string indexed isin, address indexed holder, uint256 value, uint256 wnokAmount);

Expand Down
Loading