NDF Instrument
NDFInstrument.sol is the payoff math of one non-deliverable forward: a thin clone per relationship that holds no funds. This page covers its Position struct, the mark and margin functions, and the reverts.
Code
NDFInstrument.sol is the payoff math of a single non-deliverable forward. One lightweight clone is deployed per relationship; it carries the relationship identity (core, acaId) and holds no funds. The core vault keys all collateral by agreement.
The math lives in InstrumentMathLib. The core runs it inline on the heavy paths (the cascade, the variation-margin settlement) without a cross-contract call. This clone is a thin external wrapper over the same library, kept for the per-relationship identity and for any off-chain reader that calls the IInstrument surface. All clones share one implementation. Upgrading it once updates them all.
The payoff is linear both ways: notional × (markRate − lockedRate) / lockedRate, net of variation margin already exchanged. Positive means the long is in the money.
State
Position struct
The math takes a Position, the live state of one NDF, stored in the core. The payoff functions read only the fields shown below.
struct Position {
uint256 notional; // face value, token base units
int256 lockedRate; // the forward rate the position binds at
int256 vmPosted; // net VM already exchanged; + = long has received from short
// …
address longAcct; // the long side
address shortAcct; // the short side
uint16 imLongBps; // long-side initial margin, bps of notional
uint16 imShortBps; // short-side initial margin, bps of notional
// … dates, marks, and state fields
}core / acaId
address public core;
bytes32 public acaId;The clone's identity: the CRX core and the agreement it belongs to. Set once by the factory.
Functions
initialize
function initialize(address core, bytes32 acaId) external;Initializes the clone with its core and acaId. Called once by the factory immediately after the clone is deployed.
- The implementation itself is never initialized. Its
core/acaIdstay zero, harmless because it is logic only. - Reverts
AlreadyInitializedif called a second time.
Parameters:
| Name | Type | Description |
|---|---|---|
core | address | The CRX core. |
acaId | bytes32 | The agreement this clone belongs to. |
markValue
function markValue(Position calldata p, int256 markRate) external pure returns (int256);The mark-to-market value of the position, signed from the long's perspective, net of VM already moved.
move = notional × (markRate − lockedRate) / lockedRate, then subtract vmPosted, leaving only the unmet exposure. > 0 means the long is in the money (the short owes); < 0 means the short is in the money (the long owes).
- Reverts
ZeroRateiflockedRate <= 0.
Parameters:
| Name | Type | Description |
|---|---|---|
p | Position | The position to value. |
markRate | int256 | The current oracle mark rate. |
Return Values:
| Type | Description |
|---|---|
| int256 | The long-side mark value, net of VM. > 0 = long in the money. |
owedBy
function owedBy(Position calldata p, address party, int256 markRate) external pure returns (int256);What party owes its counterparty under this position at markRate, signed: > 0 = party owes, < 0 = party is owed (a receivable). The cascade sums this over a position set and never branches on instrument type.
- For the short side this is the long-side mark value; for the long side it is its negation. A party that is neither side returns
0.
Parameters:
| Name | Type | Description |
|---|---|---|
p | Position | The position. |
party | address | The party to settle for. |
markRate | int256 | The current oracle mark rate. |
Return Values:
| Type | Description |
|---|---|
| int256 | > 0 = party owes; < 0 = party is owed; 0 = not a party. |
imRequired
function imRequired(Position calldata p, address party) external pure returns (uint256);The initial margin required from party for these terms, in token base units. NDF margin is asymmetric: the long posts imLongBps, the short posts imShortBps, each as bps of notional (floored).
Parameters:
| Name | Type | Description |
|---|---|---|
p | Position | The position. |
party | address | The party to size IM for. |
Return Values:
| Type | Description |
|---|---|
| uint256 | The IM required, in token base units. 0 if not a party. |
Events
The NDF clone holds no funds and emits no events of its own. It is pure payoff math behind the IInstrument surface. Lifecycle events (bind, VM, settle, burn) are emitted by the core, keyed by agreement and position.
Errors
| Error | Thrown when |
|---|---|
AlreadyInitialized | initialize is called a second time on a clone. |
ZeroRate | lockedRate <= 0 when valuing the position. |
The same math runs inline in the core on the hot paths. See CRX (~10 min) and The Margin Engine (~3 min).