fn name(env: soroban_sdk::Env) -> soroban_sdk::String
Pull the restricted asset in, mint wrapped 1:1. The from is
gated: a non-allowlisted address cannot wrap because that would
be a way to acquire the wrapped token.
fn wrap(env: soroban_sdk::Env, from: soroban_sdk::Address, amount: i128)
Admin adds an address to the transfer allowlist. The counterpart
is disallow, which REMOVES the entry rather than writing false
(presence is the semantic, matching the unfreeze pattern).
disallow is REFUSED against any address the operator has marked
with add_venue (panics with #16 VenueProtected); the venue
guard is mandatory because removing the Blend pool or the facade
from the allowlist strands every supplier at once. The new entry
is bumped to the network maximum TTL because admission is
permanent-by-design (a quiet-but-readmitted member must never see
their entry archive) and re-allow after disallow re-applies
the same bump.
fn allow(env: soroban_sdk::Env, id: soroban_sdk::Address)
Admin freezes an address. The from-side gate in transfer
then refuses that address with #8. wrap and unwrap are
NOT affected - a frozen member can still be paid out by a
venue (Blend payout is transfer(pool -> member) where from
is the pool, not the member) and can still redeem their own
wrapped balance. Admins can be frozen; the admin multisig
itself is the only address that can write the entry. Bumps
the entry to the network maximum TTL because an archived
Frozen entry fails at footprint load, which fails CLOSED.
Venue guard: a venue address (one the operator has marked with
add_venue) is REFUSED with #9 VenueNotFreezable. Freezing
the venue sender would strand every supplied balance until
unfrozen, because transfer applies require_not_frozen to
any from - and Blend's payout is transfer(pool -> member)
where the POOL is the sender. The guard closes that. There is
no remove_venue so the marker is monotone-additive; an
address that was a venue stays one, and freeze continues to
fn freeze(env: soroban_sdk::Env, id: soroban_sdk::Address)
fn symbol(env: soroban_sdk::Env) -> soroban_sdk::String
Burn wrapped, return the restricted asset. Deliberately has NO
eligibility check on from: the underlying asset's authorization
flags refuse the payout if the holder is no longer eligible
(SAC error #11). Adding a check here would be redundant and
risks diverging from the asset's own view. Exit is structurally
unpausable - see the file-level comment.
fn unwrap(env: soroban_sdk::Env, from: soroban_sdk::Address, amount: i128)
fn balance(env: soroban_sdk::Env, id: soroban_sdk::Address) -> i128
fn decimals(env: soroban_sdk::Env) -> u32
Admin removes an address from the transfer allowlist. The
counterpart of allow. REMOVES the storage entry rather than
writing false - mirrors unfreeze exactly so the invariant is
symmetric with the age-out failure mode: a missing Allowed entry
fails the next transaction at footprint load (which fails CLOSED
for a withdrawal) rather than silently admitting. The transfer
gate (require_allowed) uses has() and reads unwrap_or(false);
after disallow the gate panics with #1 NotAllowed for any
wrap or inbound transfer into the address - the same code as a
never-admitted address, so existing telemetry and indexer rules
for #1 continue to apply.
Venue guard: a venue address (one the operator has marked with
add_venue) is REFUSED with #16 VenueProtected. Removing the
Blend pool or the facade from the allowlist would strand every
supplier at once: a supply transfers wrapped tokens TO the pool,
so the pool must remain receivable. The guard is the structural
mirror of the freeze guard (which uses `#
fn disallow(env: soroban_sdk::Env, id: soroban_sdk::Address)
fn is_venue(env: soroban_sdk::Env, id: soroban_sdk::Address) -> bool
Gate on the RECIPIENT (to). This is pool/src/pool/submit.rs:243
(transfer(pool, to, amount)) plus the pool-initiated credit
paths; an ineligible to is refused with #1. Gate is a pure
local storage read - see the file-level comment.
Sender-side freeze: a frozen from is refused with #8. The
check is on from ONLY - deliberately NOT on to (a frozen
address can still RECEIVE wrapped tokens, which is what makes
a venue payout into a frozen member succeed). See the
file-level comment for why this is the only place the check
lives and why that placement is the only acceptable one.
fn transfer(
env: soroban_sdk::Env,
from: soroban_sdk::Address,
to: soroban_sdk::Address,
amount: i128,
)
Admin reverses a freeze. REMOVES the storage entry rather than
writing false. Only frozen addresses then hold a Frozen
entry; an archived entry fails at footprint load, which fails
CLOSED (transfer blocked). The remove-on-unfreeze pattern
keeps the invariant symmetric with the age-out failure mode -
missing = not frozen, present = frozen. See the file-level
comment for why this gate and the allowlist stay separate:
unfreeze reverses a SENDER-side restriction and allow
reverses a RECIPIENT-side one, so neither substitutes for the
other.
fn unfreeze(env: soroban_sdk::Env, id: soroban_sdk::Address)
Admin marks an address as a venue AND allowlists it in a single
call. ADD-ONLY - there is deliberately NO remove_venue function:
if venue status could be removed, an admin could strip the marker
from a venue and then call freeze on it, which would defeat the
freeze guard and re-open the strand-every-supplier failure mode
this contract is built to refuse. The marker is monotone-additive
and, since disallow exists, it is now the ONLY monotone record
here: the allowlist is revocable for ordinary admittees, while
this marker must stay one-way because both guards it backs
(#9 on freeze, #16 on disallow) are one-way trips.
Marking and allowlisting happen together because a venue that is
not on the allowlist cannot receive wrapped tokens at all
(transfer rejects with #1), so splitting them across two
calls would leave a window in which the two records disagree.
Both entries are bumped to the network maximum TTL for the same
reason allow is: a quiet venue that ages out silently would
strand its
fn add_venue(env: soroban_sdk::Env, id: soroban_sdk::Address)
fn is_frozen(env: soroban_sdk::Env, id: soroban_sdk::Address) -> bool
fn is_allowed(env: soroban_sdk::Env, id: soroban_sdk::Address) -> bool
The ONLY pause in this contract. Gates the ENTRY side (wrap)
only. There is deliberately no exit pause - see the file-level
comment for why a halt on unwrap would be a trap power. If
redemption needs to be halted the issuer does it at the asset
layer (SAC set_authorized).
fn set_paused(env: soroban_sdk::Env, paused: bool)
Constructor. Runs ATOMICALLY as part of contract creation, which
is the only window in which admin / underlying / name / symbol
can be set. There is no separate init step and so no init
front-run window for an attacker to claim admin: the only way
admin is set is the deploy transaction itself. Requires
--admin <A> --underlying <U> --name <N> --symbol <S> at
deploy time.
fn __constructor(
env: soroban_sdk::Env,
admin: soroban_sdk::Address,
underlying: soroban_sdk::Address,
name: soroban_sdk::String,
symbol: soroban_sdk::String,
)
Like unwrap, but with a caller-chosen upper bound on amount
that the contract binds the auth to. Sibling of unwrap; the
existing unwrap is unchanged.
WHY THIS EXISTS. The unwrap auth is require_auth(), which
infers the signed args from the invocation. For a facade that
computes amount from live pool state AFTER calling the pool
(e.g. an overpaid Repay where the residue is the pool refund),
the actual amount argument can drift between simulation and
inclusion (~17 stroops/second of accruing interest on this
debt) and the signed auth no longer matches the invocation
Error(auth, invalid_action) on
the unwrap sub-invocation. require_auth_for_args lets the
CONTRACT decide which values belong to the auth, so we authorise
over cap only (a stable value the facade knows from the
member's own request vector before any sub-invocation) and
allow amount to drift without invalidating the tree.SAFETY. Cap-only authorisation is safe here precisely because proceeds go
fn unwrap_capped(
env: soroban_sdk::Env,
from: soroban_sdk::Address,
amount: i128,
cap: i128,
)