Skip to main content

Changelog

Current contract suite: 5.1.3 (RestrictedLockupToken.contractVersion), RestrictedSwap 1.1.0.

5.1.1 → 5.1.3

Deployment bytecode changed for every contract in the suite except AccessControl, which is byte-for-byte identical. Anyone re-deploying must use the refreshed artifacts; contracts already on chain are unaffected but must be verified against the matching source bundle.

Breaking ABI changes

PurchaseContract — cancellations are now keyed by purchase ID, so a cancelled ID cannot be replayed or subsidised by another purchase:

RemovedReplaced by
cancelPurchase(address paymentTokenAddress_, uint256 amount_)cancelPurchase(string purchaseId_, address paymentTokenAddress_, uint256 amount_)
event PurchaseCanceled(address, address, address, uint256)event PurchaseCanceled(string purchaseId, address authorityAddress, address paymentTokenAddress, address canceledPurchaseWallet, uint256 amount)

New reads and errors: isPurchaseIdCanceled(string), canceledPurchaseIds(string), PurchaseContract_InvalidPurchaseId(), PurchaseContract_PurchaseIdCanceled(). A non-zero payment amount is now required, so a purchase can no longer mint unbacked tokens.

RestrictedSwap — all seven *WithPermit entry points take the permit value explicitly instead of recomputing it from mutable reservation state, which previously made an approve+action pair single-use. Each gains uint256 permitValue immediately before permitDeadline:

  • completeSwapWithQuoteTokenPermit
  • completeSwapWithRestrictedTokenPermit
  • configureBuyWithPermit
  • configureSellWithPermit
  • increaseOrderWithPermit
  • takeOpenBuyWithPermit
  • takeOpenSellWithPermit

Payer-side quote-token debit checks were added, so overpayment is rejected rather than absorbed.

Additive ABI changes

RestrictedLockupToken (and mirrored across all three extensions)

  • registryUnordered() → bool and event GlobalRegistryBecameUnordered(uint256, uint256) — the global holdings registry no longer relies on a zero sentinel, so reclassification of holdings can no longer happen silently
  • previewTransferableFromHoldings(address, address, uint256) → uint256 (management extension) — per-holding eligibility scanning now takes a recipient-specific cursor
  • New errors RestrictedLockupToken_TransfersPaused() and RestrictedLockupToken_ReclaimToFrozen(address); cancelTimelock now enforces the basic transfer restrictions, so tokens cannot move while paused, frozen, or AML-invalid
  • Zero-amount ERC-20 transfers no longer create a holder record

InterestPayment

  • Principal redemption is staged and inspectable: requiredPrincipalFunding() → uint256, principalRedemptionOpen() → bool, principalAmountPerWholeToken() → uint256, event PrincipalFullyFunded(uint256)
  • New errors InterestPayment_PrincipalNotFullyFunded(uint256, uint256), InterestPayment_PrincipalFundingExceedsEntitlement(uint256, uint256), InterestPayment_IncompatibleTokenDecimals(uint8, uint8), InterestPayment_InvalidRecipientAddress()
  • Per-period accumulators widened past uint128, per-period caps no longer read a stale global pool, partial funding no longer denies principal redemption, and reclaim/force-claim paths reject the contract's own address so funds cannot be stranded
  • principalAmountPerToken unit mismatch fixed, with an exact-funding invariant and a constructor decimals guard
  • Outbound ERC-20 fee/rebase amounts are now handled, so transfers cannot silently underpay

TransferRules

  • MAX_LOCK_DURATION() → uint256, NEVER_UNLOCKS() → uint256, and eligibilityThresholdFor(uint256, (uint256[], uint256, uint256, uint256, bool), bool) → (bool, uint256, uint256)
  • New error TransferRules_LockDurationTooLarge(): out-of-range lock durations are rejected at pack time rather than wrapping
  • getUnlockTimestamp no longer returns a misleading value, and token-type determination is no longer order-dependent when Identity Registry regions change
  • A region value of 0 is an exact match, not a wildcard

ERC2771CustomForwarder

  • New error ERC2771CustomForwarder_NonZeroValueNotAllowed() — native value is rejected
  • executeBatch in atomic mode handles empty reverts, so a batch can no longer burn relayer ETH or silently execute non-atomically; requests with already-consumed nonces are skipped in non-atomic batches

Behavioural fixes with no ABI change

  • IdentityRegistry — bytecode changed, ABI identical
  • SnapshotPeriods — unbounded amount × time arithmetic no longer overflows under extreme supply
  • RecallablePayment — outbound ERC-20 fee/rebase handling, matching InterestPayment

Unchanged

  • AccessControl — identical ABI and identical bytecode

v5.0 → v5.1 (suite 5.1.1)

RestrictedLockupToken

  • New RestrictedLockupTokenStandardsExtension (delegatecall) exposing EIP-2612 permit, nonces, and DOMAIN_SEPARATOR
  • Main token constructor adds a restrictedLockupTokenStandardsExtension address argument
  • Deploy order inserts StandardsExtension immediately before the token: Extension → ManagementExtension → StandardsExtension → Token

InterestPayment

  • Interest rate bips precision raised: 100 bips = 1% (v5)100,000 bips = 1% (v5.1) (BIPS_PRECISION = 10_000_000 for 100%)
  • Example: a 5% rate was 500 bips in v5 and is 500_000 bips in v5.1
  • Constructor argument list is unchanged; only the scale of rate values changes

RestrictedSwap

RestrictedSwap contract version 1.1.0 — largest surface change in v5.1.

  • Constructor gains immutable maxSwapLifetime (seconds; allowed range [1 hour, 365 days])
  • Swap deadlines are required and must satisfy deadline - block.timestamp <= maxSwapLifetime
  • Open orders (address(0) counterparty) with partial fills via takeOpenBuy / takeOpenSell (and permit variants)
  • configureBuy / configureSell gain minimumFillAmount (must be 0 for closed swaps)
  • Order resize without cancel/recreate: decreaseOrder, increaseOrder, increaseOrderWithPermit
  • Parent-offer linking: completeSwapWithRestrictedToken takes an optional sellSwapNumber_ to deduct from an open sell
  • EIP-2612 permit variants across configure, complete, take-open, and increase paths
  • New events and errors for open orders, deadlines, resize, and self-fill prohibition