Skip to main content

Decimals Wrapper

Overview​

PendleDecimalsWrapper is a thin ERC-20 wrapper that re-expresses any token with fewer than 18 decimals as a new token with exactly 18 decimals. No interest accrues, there is no rebasing, and no fees are taken — it is a purely mechanical decimal normalisation layer.

Within the Pendle system, this is the foundation of the Scaled18 SY pattern: an SY contract accepts a low-decimal raw token (e.g., LBTC with 8 decimals or USDC with 6 decimals), wraps it through PendleDecimalsWrapper, and exposes the 18-decimal representation to PY contracts and the market. The PendleDecimalsWrapperFactory creates and caches these wrappers so that only one wrapper ever exists per (rawToken, targetDecimals) pair.

Sources:


PendleDecimalsWrapper​

A standard ERC-20 token (inherits PendleERC20) with decimals() = 18. Wrapping and unwrapping is a simple multiply/divide by 10^(18 - rawDecimals).

The constructor enforces rawDecimals <= 18. The contract is always deployed by the factory — do not deploy it directly.

Public State​

VariableTypeDescription
factoryaddressThe PendleDecimalsWrapperFactory that deployed this contract
rawTokenaddressThe original token being wrapped
rawDecimalsuint8Decimals of rawToken

wrap​

function wrap(uint256 amount) external returns (uint256 amountOut);

Transfers amount of rawToken from msg.sender into the wrapper, then mints rawToWrapped(amount) of the wrapped token to msg.sender.

Important: amount is in raw units (i.e., rawToken decimals). The returned amountOut is in wrapped units (18 decimals). There is no rounding loss in the wrap direction.

unwrap​

function unwrap(uint256 amount) external returns (uint256 amountOut);

Burns amount of the wrapped token from msg.sender, then transfers wrappedToRaw(amount) of rawToken back.

Important: amount is in wrapped units (18 decimals). wrappedToRaw performs integer division — the caller may receive slightly fewer raw units than the proportional share (the remainder stays in the contract as recoverable dust). See dustReceiver.

rawToWrapped​

function rawToWrapped(uint256 amount) public view returns (uint256);
wrappedAmount = amount × 10^(18 - rawDecimals)

Converts a raw-unit quantity to its 18-decimal representation. No rounding; exact for any amount.

wrappedToRaw​

function wrappedToRaw(uint256 amount) public view returns (uint256);
rawAmount = amount / 10^(18 - rawDecimals)    (integer division — truncates)

Converts a wrapped-unit quantity back to raw units. Because of integer truncation, wrappedToRaw(rawToWrapped(x)) always equals x, but rawToWrapped(wrappedToRaw(y)) may be slightly less than y if y is not a multiple of the scale factor. The shortfall remains in the contract as dust.


PendleDecimalsWrapperFactory​

Idempotent factory that ensures only one wrapper exists per (rawToken, targetDecimals) pair. The factory is the sole deployer of PendleDecimalsWrapper instances.

Current limitation

getOrCreate currently only supports _decimals = 18. Passing any other target decimals will revert. The API accepts _decimals as a parameter for forward-compatibility, but the implementation asserts _decimals == 18 internally.

Constructor​

constructor(address _dustReceiver);

Sets the immutable dustReceiver address that receives dust from all wrappers created by this factory.

getOrCreate​

function getOrCreate(
address _rawToken,
uint8 _decimals
) external returns (address decimalWrapper);

Returns the wrapper address for (_rawToken, _decimals). If no wrapper exists yet, deploys a new PendleDecimalsWrapper and registers it. Subsequent calls with the same arguments return the already-deployed address without re-deploying.

Reverts: implicitly via assert if _decimals != 18 (see note above).

Emits DecimalWrapperCreated(rawToken, decimals, decimalWrapper) on first deployment only.

decimalWrappers​

mapping(address => mapping(uint8 => address)) public decimalWrappers;

Registry mapping rawToken → targetDecimals → wrapper address. Returns address(0) if no wrapper has been created for that pair.

dustReceiver​

address public immutable dustReceiver;

The address that receives dust when any wrapper's sweep() is called.

Events​

EventParametersDescription
DecimalWrapperCreatedrawToken, decimals, decimalsWrapperEmitted when a new wrapper is deployed

Token Naming Convention​

Wrapper tokens are named automatically by the factory:

PropertyValue
name"{rawToken.name} scaled18"
symbol"{rawToken.symbol}-scaled18"
decimals18 (always)

For example, wrapping LBTC (8 decimals) produces a token named "Lombard Staked Bitcoin scaled18" with symbol "LBTC-scaled18".


How Scaled18 SY Contracts Use the Wrapper​

For concrete implementations, refer to some of our examples:


Further Reading​