Class Context<T extends ACell>

java.lang.Object
convex.core.data.AObject
convex.core.lang.Context<T>
Type Parameters:
T - Result type of Context

public class Context<T extends ACell> extends AObject
Representation of CVM execution context.

Execution context includes: - The current on-Chain state, including the defined execution environment for each Address - Local lexical bindings for the current execution position - The identity (as an Address) for the origin, caller and currently executing actor - Juice and execution depth current status for - Result of the last operation executed (which may be exceptional)

Interestingly, this behaves like Scala's ZIO[Context-Stuff, AExceptional, T]

Contexts maintain checks on execution depth and juice to control against arbitrary on-chain execution. Coupled with limits on total juice and limits on memory allocation per unit juice, this places an upper bound on execution time and space.

Contexts also support returning exceptional values. Exceptional results may come from arbitrary nested depth (which requires a bit of complexity to reset depth when catching exceptional values). We avoid using Java exceptions here, because exceptionals are "normal" in the context of on-chain execution, and we'd like to avoid the overhead of exception handling - may be especially important in DoS scenarios.

"If you have a procedure with 10 parameters, you probably missed some" - Alan Perlis

  • Field Details

    • EMPTY_BINDINGS

      public static final AVector<ACell> EMPTY_BINDINGS
  • Method Details

    • createFake

      public static <R extends ACell> Context<R> createFake(State state)
      Creates an execution context with a default actor address. Useful for Testing
      Parameters:
      state - State to use for this Context
      Returns:
      Fake context
    • createFake

      public static <R extends ACell> Context<R> createFake(State state, Address origin)
      Creates a "fake" execution context for the given address. Not valid for use in real transactions, but can be used to compute stuff off-chain "as-if" the actor made the call.
      Parameters:
      state - State to use for this Context
      origin - Origin address to use
      Returns:
      Fake context
    • createInitial

      public static <T extends ACell> Context<T> createInitial(State state, Address origin, long juice)
      Creates an initial execution context with the specified actor as origin, and reserving the appropriate amount of juice. Juice reserve is extracted from the actor's current balance.
      Parameters:
      state - Initial State for Context
      origin - Origin Address for Context
      juice - Initial juice for Context
      Returns:
      Initial execution context with reserved juice.
    • completeTransaction

      public Context<T> completeTransaction(State initialState, long initialJuice)
      Performs key actions at the end of a transaction:
      • Refunds juice
      • Accumulates used juice fees in globals
      Parameters:
      initialState - State before transaction execution (after prepare)
      initialJuice - total juice reserved at start of transaction
      Returns:
      Updated context
    • withState

      public <R extends ACell> Context<R> withState(State newState)
    • getState

      public State getState()
      Get the latest state from this Context
      Returns:
      State instance
    • getJuice

      public long getJuice()
      Get the juice available in this Context
      Returns:
      Juice available
    • getOffer

      public long getOffer()
      Get the current offer from this Context
      Returns:
      Offered amount in Convex copper
    • getEnvironment

      public AHashMap<Symbol,​ACell> getEnvironment()
      Gets the current Environment
      Returns:
      Environment map
    • getCompilerState

      public Context.CompilerState getCompilerState()
      Gets the compiler state
      Returns:
      CompilerState instance
    • getMetadata

      public AHashMap<Symbol,​AHashMap<ACell,​ACell>> getMetadata()
      Gets the metadata for the current Account
      Returns:
      Metadata map
    • consumeJuice

      public <R extends ACell> Context<R> consumeJuice(long gulp)
      Consumes juice, returning an updated context if sufficient juice remains or an exceptional JUICE error.
      Type Parameters:
      R - Result type
      Parameters:
      gulp - Amount of jjuice to consume
      Returns:
      Updated context with juice consumed
    • checkJuice

      public boolean checkJuice(long gulp)
      Checks if there is sufficient juice for a given gulp of consumption. Does not alter context in any way.
      Parameters:
      gulp - Amount of juice to be consumed.
      Returns:
      true if juice is sufficient, false otherwise.
    • lookup

      public <R extends ACell> Context<R> lookup(Symbol symbol)
      Looks up a symbol's value in the current execution context, without any effect on the Context (no juice consumed etc.)
      Type Parameters:
      R - Type of value associated with the given symbol
      Parameters:
      symbol - Symbol to look up. May be qualified
      Returns:
      Context with the result of the lookup (may be an undeclared exception)
    • lookupDynamic

      public <R extends ACell> Context<R> lookupDynamic(Symbol symbol)
      Looks up a value in the dynamic environment. Consumes no juice. Returns an UNDECLARED exception if the symbol cannot be resolved.
      Type Parameters:
      R - Result type
      Parameters:
      symbol - Symbol to look up
      Returns:
      Updated Context
    • lookupDynamic

      public <R extends ACell> Context<R> lookupDynamic(Address address, Symbol symbol)
      Looks up a value in the dynamic environment. Consumes no juice. Returns an UNDECLARED exception if the symbol cannot be resolved. Returns a NOBODY exception if the specified Account does not exist
      Type Parameters:
      R - Type of value result
      Parameters:
      address - Address of account in which to look up value
      symbol - Symbol to look up
      Returns:
      Updated Context
    • lookupMeta

      public AHashMap<ACell,​ACell> lookupMeta(Symbol sym)
      Looks up Metadata for the given symbol in this context
      Parameters:
      sym - Symbol to look up
      Returns:
      Metadata for given symbol (may be empty) or null if undeclared
    • lookupMeta

      public AHashMap<ACell,​ACell> lookupMeta(Address address, Symbol sym)
      Looks up Metadata for the given symbol in this context
      Parameters:
      address - Address to use for lookup (may pass null for current environment)
      sym - Symbol to look up
      Returns:
      Metadata for given symbol (may be empty) or null if undeclared
    • lookupDefiningAccount

      public AccountStatus lookupDefiningAccount(Address address, Symbol sym)
      Looks up the account the defines a given Symbol
      Parameters:
      sym - Symbol to look up
      address - Address to look up in first instance (null for current address).
      Returns:
      AccountStatus for given symbol (may be empty) or null if undeclared
    • lookupValue

      public ACell lookupValue(Symbol sym)
      Looks up value for the given symbol in this context
      Parameters:
      sym - Symbol to look up
      Returns:
      Value for the given symbol or null if undeclared
    • lookupValue

      public ACell lookupValue(Address address, Symbol sym)
      Looks up value for the given symbol in this context
      Parameters:
      address - Address to look up in (may be null for current environment)
      sym - Symbol to look up
      Returns:
      Value for the given symbol or null if undeclared
    • lookupDynamicEntry

      public MapEntry<Symbol,​ACell> lookupDynamicEntry(Address address, Symbol sym)
      Looks up an environment entry for a specific address without consuming juice.
      Parameters:
      address - Address of Account in which to look up entry
      sym - Symbol to look up
      Returns:
      Environment entry
    • getAccountStatus

      public AccountStatus getAccountStatus()
      Gets the account status for the current Address
      Returns:
      AccountStatus object, or null if not found
    • getHoldings

      public ABlobMap<Address,​ACell> getHoldings()
      Gets the holdings map for the current account.
      Returns:
      Map of holdings, or null if the current account does not exist.
    • getBalance

      public long getBalance()
    • getBalance

      public long getBalance(Address address)
    • getCaller

      public Address getCaller()
      Gets the caller of the currently executing context. Will be null if this context was not called from elsewhere (e.g. is an origin context)
      Returns:
      Caller of the currently executing context
    • getAddress

      public Address getAddress()
      Gets the address of the currently executing Account. May be the current actor, or the address of the account that executed this transaction if no Actors have been called.
      Returns:
      Address of the current account, cannot be null, must be a valid existing account
    • getResult

      public T getResult()
      Gets the result from this context. Throws an Error if the context return value is exceptional.
      Returns:
      Result value from this Context.
    • getValue

      public Object getValue()
      Gets the resulting value from this context. May be either exceptional or a normal result.
      Returns:
      Either the normal result, or an AExceptional instance
    • getExceptional

      public AExceptional getExceptional()
      Gets the exceptional value from this context. Throws an Error is the context return value is normal.
      Returns:
      an AExceptional instance
    • withResult

      public <R extends ACell> Context<R> withResult(ACell value)
      Returns a context updated with the specified result. Context may become exceptional depending on the result type.
      Type Parameters:
      R - Result type
      Parameters:
      value - Value
      Returns:
      Context updated with the specified result.
    • withValue

      public <R extends ACell> Context<R> withValue(Object value)
      Updates this context with a given value, which may either be a normal result or exceptional value
      Type Parameters:
      R - Result type
      Parameters:
      value - Value
      Returns:
      Context updated with the specified result value.
    • withResult

      public <R extends ACell> Context<R> withResult(long gulp, R value)
    • withJuiceError

      public <R extends ACell> Context<R> withJuiceError()
      Returns this context with a JUICE error, consuming all juice.
      Type Parameters:
      R - Result type
      Returns:
      Exceptional Context signalling JUICE error.
    • withException

      public <R extends ACell> Context<R> withException(AExceptional exception)
    • withException

      public <R extends ACell> Context<R> withException(long gulp, AExceptional value)
    • execute

      public <R extends ACell> Context<R> execute(AOp<R> op)
      Executes an Op within this context, returning an updated context.
      Type Parameters:
      R - Return type of the Op
      Parameters:
      op - Op to execute
      Returns:
      Updated Context
    • run

      public <R extends ACell> Context<R> run(AOp<R> op)
      Executes an Op at the top level in a new forked Context. Handles top level halt, recur and return. Returning an updated context containing the result or an exceptional error.
      Type Parameters:
      R - Return type of the Op
      Parameters:
      op - Op to execute
      Returns:
      Updated Context
    • run

      public <R extends ACell> Context<R> run(ACell code)
      Executes a form at the top level in a new forked Context. Handles top level halt, recur and return. Returning an updated context containing the result or an exceptional error.
      Type Parameters:
      R - Return type of the Op
      Parameters:
      code - Code to execute
      Returns:
      Updated Context
    • invoke

      public <R extends ACell> Context<R> invoke(AFn<R> fn, ACell... args)
      Invokes a function within this context, returning an updated context. Handles function recur and return values. Keeps depth constant upon return.
      Type Parameters:
      R - Return type of the function
      Parameters:
      fn - Function to execute
      args - Arguments for function
      Returns:
      Updated Context
    • executeLocalBinding

      public <I extends ACell> Context<I> executeLocalBinding(ACell bindingForm, AOp<I> op)
      Execute an op, and bind the result to the given binding form in the lexical environment Binding form may be a destructuring form
      Type Parameters:
      I - Result type of Context
      Parameters:
      bindingForm - Binding form
      op - Op to execute to get binding values
      Returns:
      Context with local bindings updated
    • updateBindings

      public <R extends ACell> Context<R> updateBindings(ACell bindingForm, Object args)
      Updates local bindings with a given binding form
      Type Parameters:
      R - Result type of Context
      Parameters:
      bindingForm - Binding form
      args - Arguments to bind
      Returns:
      Non-exceptional Context with local bindings updated, or an exceptional result if bindings fail
    • print

      public void print(StringBuilder sb)
      Description copied from class: AObject
      Prints this Object to a readable String Representation
      Specified by:
      print in class AObject
      Parameters:
      sb - StringBuilder to append to
    • getLocalBindings

      public AVector<ACell> getLocalBindings()
    • withLocalBindings

      public <R extends ACell> Context<R> withLocalBindings(AVector<ACell> newBindings)
      Updates this Context with new local bindings. Doesn't affact result state (exceptional or otherwise)
      Type Parameters:
      R - Return type of Context
      Parameters:
      newBindings - New local bindings map to use.
      Returns:
      Updated context
    • getAccountStatus

      public AccountStatus getAccountStatus(Address address)
      Gets the account status record, or null if not found
      Parameters:
      address - Address of account
      Returns:
      AccountStatus for the specified address, or null if the account does not exist
    • getDepth

      public int getDepth()
    • getOrigin

      public Address getOrigin()
    • define

      public Context<T> define(Symbol key, ACell value)
      Defines a value in the environment of the current address
      Parameters:
      key - Symbol of the mapping to create
      value - Value to define
      Returns:
      Updated context with symbol defined in environment
    • defineWithSyntax

      public Context<T> defineWithSyntax(Syntax syn, ACell value)
      Defines a value in the environment of the current address, updating the metadata
      Parameters:
      syn - Syntax Object to define, containing a Symbol value
      value - Value to set of the given Symbol
      Returns:
      Updated context with symbol defined in environment
    • undefine

      public Context<T> undefine(Symbol key)
      Removes a definition mapping in the environment of the current address
      Parameters:
      key - Symbol of the environment mapping to remove
      Returns:
      Updated context with symbol definition removed from the environment, or this context if unchanged
    • expandCompile

      public <R extends ACell> Context<AOp<R>> expandCompile(ACell form)
      Expand and compile a form in this Context.
      Type Parameters:
      R - Return type of compiled op
      Parameters:
      form - Form to expand and compile
      Returns:
      Updated Context with compiled Op as result
    • compile

      public <R extends ACell> Context<AOp<R>> compile(ACell expandedForm)
      Compile a form in this Context. Form must already be fully expanded to a Syntax Object
      Type Parameters:
      R - Return type of compiled op
      Parameters:
      expandedForm - Form to compile
      Returns:
      Updated Context with compiled Op as result
    • eval

      public <R extends ACell> Context<R> eval(ACell form)
      Executes a form in the current context. Ops are executed directly. Other forms will be expanded and compiled before execution, unless *lang* is set, in which case they will be executed via the *lang* function.
      Type Parameters:
      R - Return type of evaluation
      Parameters:
      form - Form to evaluate
      Returns:
      Context containing the result of evaluating the specified form
    • evalAs

      public <R extends ACell> Context<R> evalAs(Address address, ACell form)
      Evaluates a form as another Address. Causes TRUST error if the Address is not controlled by the current address.
      Type Parameters:
      R - Result type
      Parameters:
      address - Address of Account in which to evaluate
      form - Form to evaluate
      Returns:
      Updated Context
    • query

      public <R extends ACell> Context<R> query(ACell form)
      Executes code as if run in the current account, but always discarding state changes.
      Type Parameters:
      R - Result type
      Parameters:
      form - Code to execute.
      Returns:
      Context updated with only query result and juice consumed
    • queryAs

      public <R extends ACell> Context<R> queryAs(Address address, ACell form)
      Executes code as if run in the specified account, but always discarding state changes.
      Type Parameters:
      R - Result type
      Parameters:
      address - Address of Account in which to execute the query
      form - Code to execute.
      Returns:
      Context updated with only query result and juice consumed
    • handleQueryResult

      protected <R extends ACell> Context<R> handleQueryResult(Context<R> ctx)
      Just take result and juice from query. Log and state not kept.
      Type Parameters:
      R -
      Parameters:
      ctx -
      Returns:
    • compileAll

      public <R extends ACell> Context<AVector<AOp<R>>> compileAll(ASequence<ACell> forms)
      Compiles a sequence of forms in the current context. Returns a vector of ops in the updated Context. Maintains depth.
      Type Parameters:
      R - Return type of compiled op.
      Parameters:
      forms - A sequence of forms to compile
      Returns:
      Updated context with vector of compiled forms
    • withJuice

      public <R extends ACell> Context<R> withJuice(long newJuice)
    • withCompilerState

      public <R extends ACell> Context<R> withCompilerState(Context.CompilerState comp)
    • isExceptional

      public boolean isExceptional()
      Tests if this Context holds an exceptional result. Ops should cancel and return exceptional results unchanged, unless they can handle them.
      Returns:
      true if context has an exceptional value, false otherwise
    • isValidAccount

      public boolean isValidAccount(Address address)
      Tests if an Address is valid, i.e. refers to an existing Account
      Parameters:
      address - Address to check. May be null
      Returns:
      true if Account exists, false otherwise
    • isError

      public boolean isError()
      Tests if this Context's current status contains an Error. Errors are an uncatchable subset of Exceptions.
      Returns:
      true if context has an Error value, false otherwise
    • transfer

      public Context<CVMLong> transfer(Address target, long amount)
      Transfers funds from the current address to the target. Uses no juice
      Parameters:
      target - Target Address, will be created if does not already exist.
      amount - Amount to transfer, must be between 0 and Amount.MAX_VALUE inclusive
      Returns:
      Context with sent amount if the transaction succeeds, or an exceptional value if the transfer fails
    • transferMemoryAllowance

      public Context<CVMLong> transferMemoryAllowance(Address target, CVMLong amountToSend)
      Transfers memory allowance from the current address to the target. Uses no juice
      Parameters:
      target - Target Address, must already exist
      amountToSend - Amount of memory to transfer, must be between 0 and Amount.MAX_VALUE inclusive
      Returns:
      Context with a null result if the transaction succeeds, or an exceptional value if the transfer fails
    • setMemory

      public Context<CVMLong> setMemory(long allowance)
      Sets the memory allowance for the current account, buying / selling from the pool as necessary to ensure the correct final allowance
      Parameters:
      allowance - New memory allowance
      Returns:
      Context indicating the price paid for the allowance change (may be zero or negative for refund)
    • acceptFunds

      public <R extends ACell> Context<R> acceptFunds(long amount)
      Accepts offered funds for the given address. STATE error if offered amount is insufficient. ARGUMENT error if acceptance is negative.
      Type Parameters:
      R - Type of result
      Parameters:
      amount - Amount to accept
      Returns:
      Updated context, with long amount accepted as result
    • actorCall

      public <R extends ACell> Context<R> actorCall(Address target, long offer, String functionName, ACell... args)
      Executes a call to an Actor. Utility function which convert a java String function name
      Type Parameters:
      R - Return type of Actor call
      Parameters:
      target - Target Actor address
      offer - Amount of Convex Coins to offer in Actor call
      functionName - Symbol of function name defined by Actor
      args - Arguments to Actor function invocation
      Returns:
      Context with result of Actor call (may be exceptional)
    • actorCall

      public <R extends ACell> Context<R> actorCall(Address target, long offer, ACell functionName, ACell... args)
      Executes a call to an Actor.
      Type Parameters:
      R - Return type of Actor call
      Parameters:
      target - Target Actor address
      offer - Amount of Convex Coins to offer in Actor call
      functionName - Symbol of function name defined by Actor
      args - Arguments to Actor function invocation
      Returns:
      Context with result of Actor call (may be exceptional)
    • deployActor

      public Context<Address> deployActor(ACell code)
      Deploys an Actor in this context. Argument argument must be an Actor generation code, which will be evaluated in the new Actor account to initialise the Actor Result will contain the new Actor address if successful, an exception otherwise.
      Parameters:
      code - Actor initialisation code
      Returns:
      Updated Context with Actor deployed, or an exceptional result
    • createAccount

      public Context<Address> createAccount(AccountKey key)
      Create a new Account with a given AccountKey (may be null for actors etc.)
      Parameters:
      key - New Account Key
      Returns:
      Updated context with new Account added
    • withError

      public <R extends ACell> Context<R> withError(Keyword error)
    • withError

      public <R extends ACell> Context<R> withError(Keyword errorCode, String message)
    • withError

      public <R extends ACell> Context<R> withError(ErrorValue error)
    • withArityError

      public <R extends ACell> Context<R> withArityError(String message)
    • withCompileError

      public <R extends ACell> Context<R> withCompileError(String message)
    • withBoundsError

      public <R extends ACell> Context<R> withBoundsError(long index)
    • withCastError

      public <R extends ACell> Context<R> withCastError(int argIndex, AType klass)
    • withCastError

      public <R extends ACell> Context<R> withCastError(int argIndex, ACell[] args, AType klass)
    • withCastError

      public <R extends ACell> Context<R> withCastError(ACell a, AType klass)
    • withCastError

      public <R extends ACell> Context<R> withCastError(AType klass)
    • withCastError

      public <R extends ACell> Context<R> withCastError(ACell a, String message)
    • getErrorCode

      public ACell getErrorCode()
      Gets the error code of this context's return value
      Returns:
      The ErrorType of the current exceptional value, or null if there is no error.
    • getError

      public ErrorValue getError()
      Gets the Error from this Context, or null if not an Error
      Returns:
      The ErrorType of the current exceptional value, or null if there is no error.
    • withAssertError

      public <R extends ACell> Context<R> withAssertError(String message)
    • withFundsError

      public <R extends ACell> Context<R> withFundsError(String message)
    • withArgumentError

      public <R extends ACell> Context<R> withArgumentError(String message)
    • getTimeStamp

      public CVMLong getTimeStamp()
      Gets the current timestamp for this context. The timestamp is the greatest timestamp of all blocks in consensus (including the currently executing block).
      Returns:
      Timestamp in milliseconds since UNIX epoch
    • schedule

      public Context<CVMLong> schedule(long time, AOp<ACell> op)
      Schedules an operation for the specified future timestamp. Handles integrity checks and schedule juice.
      Parameters:
      time - Timestamp at which to schedule the op.
      op - Operation to schedule.
      Returns:
      Updated context, with scheduled time as the result
    • setDelegatedStake

      public <R extends ACell> Context<R> setDelegatedStake(AccountKey peerKey, long newStake)
      Sets the delegated stake on a specified peer to the specified level. May set to zero to remove stake. Stake will be capped by current balance.
      Parameters:
      peerKey - Peer Account key on which to stake
      newStake - Amount to stake
      Returns:
      Context with amount of coins transferred to Peer as result (may be negative if stake withdrawn)
    • setPeerStake

      public <R extends ACell> Context<R> setPeerStake(AccountKey peerKey, long newStake)
      Sets the stake for a given Peer, transferring coins from the current address.
      Parameters:
      peerKey - Peer Account Key for which to update Stake
      newStake - New stake for Peer
      Returns:
      Updated Context
    • createPeer

      public <R extends ACell> Context<R> createPeer(AccountKey accountKey, long initialStake)
      Creates a new peer with the specified stake. The accountKey must not be in the list of peers. The accountKey must be assigend to the current transaction address Stake must be greater than 0. Stake must be less than to the account balance.
      Parameters:
      accountKey - Peer Account key to create the PeerStatus
      initialStake - Initial stake amount
      Returns:
      Context with final take set
    • setPeerData

      public <R extends ACell> Context<R> setPeerData(AccountKey peerKey, AMap<ACell,​ACell> data)
      Sets peer data.
      Parameters:
      peerKey - Peer to set data for
      data - Map of data to set for the peer
      Returns:
      Context with final peer data set
    • setHolding

      public Context<T> setHolding(Address targetAddress, ACell value)
      Sets the holding for a specified target account. Returns NOBODY exception if account does not exist.
      Parameters:
      targetAddress - Account address at which to set the holding
      value - Value to set for the holding.
      Returns:
      Updated context
    • setController

      public <R extends ACell> Context<R> setController(Address address)
      Sets the controller for the current Account
      Type Parameters:
      R - Result type
      Parameters:
      address - New controller Address
      Returns:
      Context with current Account controller set
    • setAccountKey

      public <R extends ACell> Context<R> setAccountKey(AccountKey publicKey)
      Sets the public key for the current account
      Type Parameters:
      R - Result type
      Parameters:
      publicKey - New Account Public Key
      Returns:
      Context with current Account Key set
    • withAccountStatus

      protected <R extends ACell> Context<R> withAccountStatus(Address target, AccountStatus accountStatus)
    • forkWithAddress

      public <R extends ACell> Context<R> forkWithAddress(Address newAddress)
      Switches the context to a new address, creating a new execution context. Suitable for testing.
      Type Parameters:
      R - Result type
      Parameters:
      newAddress - New Address to use.
      Returns:
      Result type of new Context
    • fork

      public <R extends ACell> Context<R> fork()
      Forks this context, creating a new copy of all local state
      Type Parameters:
      R - Result type of new Context
      Returns:
      A new forked Context
    • createEncoding

      public Blob createEncoding()
      Description copied from class: AObject
      Creates a Blob object representing this object. Should be called only after the cached encoding has been checked.
      Specified by:
      createEncoding in class AObject
      Returns:
      Blob Encoding of Object
    • appendLog

      public Context<T> appendLog(AVector<ACell> values)
      Appends a log entry for the current address.
      Parameters:
      values - Values to log
      Returns:
      Updated Context
    • getLog

      public AVector<AVector<ACell>> getLog()
      Gets the log map for the current context.
      Returns:
      BlobMap of addresses to log entries created in the course of current execution context.
    • lookupCNS

      public Context<?> lookupCNS(String name)
    • expand

      public Context<ACell> expand(ACell form)
      Expands a form with the default *initial-expander*
      Parameters:
      form - Form to expand
      Returns:
      Syntax Object resulting from expansion.
    • expand

      public <R extends ACell> Context<R> expand(AFn<?> expander, ACell form, AFn<?> cont)
    • lookupExpander

      public AFn<ACell> lookupExpander(ACell form)
      Looks up an expander from a form in this context
      Parameters:
      form - Form which might be an expander reference
      Returns:
      Expander instance, or null if no expander found