Skip to main content

Environment & Invocation

Access runtime information about the current call and execution environment.

Invocation​

Get information about the current endpoint call:

MethodReturnsPISA targetDescription
Invocation.ID()id IdentifierallUnique ID of this invocation
Invocation.Caller()caller Identifier0.5.0 and laterImmediate caller (differs from Sender in cross-logic calls)

Both methods take no arguments. Passing one is a compile error: "invalid argument ID takes exactly 0 argument(s) (), found 1".

Sender vs Caller​

  • Sender — The actor who initiated the interaction (stays the same through cross-logic calls)
  • Invocation.Caller() — The immediate caller (changes in cross-logic calls)
endpoint CheckCaller() -> (sender Identifier, caller Identifier):
sender = Sender // Original actor
caller = Invocation.Caller() // Immediate caller (could be another logic)

Environment​

Access runtime environment data:

MethodReturnsPISA targetDescription
Environment.Timestamp()timestamp U64allCurrent interaction timestamp, in nanoseconds
Environment.EffortCapacity()effort_capacity U640.4.0 and laterTotal fuel available
Environment.EffortAvailable()effort_available U640.4.0 and laterRemaining fuel
Environment.VolumeCapacity()volume_capacity U640.4.0 – 0.7.1Total storage space
Environment.VolumeAvailable()volume_available U640.4.0 – 0.7.1Remaining storage space
Environment.StorageResult(account_id, payer_id)added U64, removed U640.8.0 onlyStorage bytes added and removed in this interaction
endpoint GetEnvInfo() -> (
timestamp U64,
fuel_remaining U64,
invocation_id Identifier
):
timestamp = Environment.Timestamp()
fuel_remaining = Environment.EffortAvailable()
invocation_id = Invocation.ID()
Storage metering changed in PISA v0.8.0

VolumeCapacity() and VolumeAvailable() are not supported on 0.8.0 targets — v0.8.0 meters storage per account and payer instead of globally, and StorageResult() replaces them. On a 0.8.0 target they're compile errors ("VolumeCapacity is not a valid environment function"; v0.9.1 misspelled it as "envrionment"). StorageResult() in turn is not available on earlier targets ("not implemented in PISA v0.7.1, implemented in v0.8.0").

Environment.* calls are checked at compile time: the number of arguments, their names and their types must match the method. Passing a U64 where an Identifier is expected, for example, reports "type mismatch: argument 'payer_id' of 'StorageResult' expects type identifier, found u64".

Timestamp()​

Environment.Timestamp() returns the interaction's timestamp in nanoseconds since the Unix epoch. A current value looks like 1789365536769825000, not 1789365536. Durations you add to it or compare it against must be in nanoseconds too:

const NS_PER_SECOND U64 = 1000000000

endpoint Deadline(seconds U64) -> (deadline U64):
deadline = Environment.Timestamp() + seconds * NS_PER_SECOND
Changed in v0.9.1

The PISA runtime defines the timestamp in nanoseconds. Before v0.9.1, Cocolab supplied seconds to 0.8.0 targets, so lab tests written against seconds are off by a factor of 10⁹. Logics compiled for 0.5.0 – 0.7.1 targets still get seconds in Cocolab.

Argument names​

Arguments of Environment.* calls, Builtins.* calls and actor methods may be passed with or without a label. When an argument carries a name, it must match the parameter at that position:

ArgumentCarries a name?Result
Literal, cast, field access or keyword — "tier", Identifier(Token), p.id, SenderNoAlways accepted
Labelled — payer_id: xYes, the labelLabel must equal the parameter name
Bare variable — acctYes, the variable's own nameVariable name must equal the parameter name

The last row is easy to trip over. A bare variable isn't an unnamed argument, because its name is taken as the argument name:

endpoint dynamic Rename(name String, acct Identifier, pay Identifier) -> (added, removed U64):
mutate name -> Token.Logic.name
// ERROR: argument 0 of 'StorageResult' expected name 'account_id', found 'acct'
// added, removed = Environment.StorageResult(acct, pay)
added, removed = Environment.StorageResult(account_id: acct, payer_id: pay)

Labels are matched by position, not used to reorder arguments, so StorageResult(payer_id: a, account_id: b) is an error as well. A variable that already has the parameter's name needs no label.

Changed in v0.9.1

v0.9.0 ignored argument names of Environment.* calls. Calls that pass literals and keywords, such as StorageResult(Identifier(Token), Sender), compile unchanged. Calls that pass variables with other names need labels. A label produces identical bytecode, so adding one doesn't require redeploying the logic.

Named outputs​

Since v0.9.2, Environment.*, Invocation.*, Builtins.* and asset.* calls accept the same capture syntax as user functions: (outputs) <- Superglobal.Method(args). The capture is optional. The plain form still assigns the results by position, to variables of any name:

memory ts = Environment.Timestamp()                   // plain: any variable name
memory ts = (timestamp) <- Environment.Timestamp() // capture: the output name is checked
a, r = (added, removed) <- Environment.StorageResult(Identifier(Token), Sender)
added, _ = (added, removed) <- Environment.StorageResult(Identifier(Token), Sender)
memory c = (caller) <- Invocation.Caller()
h = (hash) <- Builtins.Sha256(data)
valid = (ok) <- Builtins.Sigverify(data, signature, pubkey)
b = (balance) <- asset.BalanceOf(token_id: 0, address) // asset logics only

The output names are:

CallOutput names
Environment.Timestamp()timestamp
Environment.EffortCapacity() / EffortAvailable()effort_capacity / effort_available
Environment.StorageResult(account_id, payer_id)added, removed
Environment.VolumeCapacity() / VolumeAvailable() (0.4.0 – 0.7.1)volume_capacity / volume_available
Invocation.ID() / Caller()id / caller
Builtins.Sha256 / Keccak / Blake2bhash
Builtins.Sigverifyok
asset.BalanceOfbalance
asset.Symbol / Creator / Manager / Decimalssymbol / creator / manager / decimals
asset.MaxSupply / CirculatingSupply / EnableEventsmax_supply / circulating_supply / enable_events
asset.GetStaticMetadata / GetDynamicMetadata / GetStaticTokenMetadata / GetDynamicTokenMetadatavalue
Asset write methods (Transfer, Mint, …)None

When you write the capture list, it must match the method exactly:

MistakeError
Wrong name"invalid argument 'Timestamp' returns value named 'timestamp' at position 0, used output 'tstamp'". Builtins.* and asset.* use the same message
Wrong name on Invocation.*"invalid argument 'Caller' returns value named 'caller', used output 'who'"
Too few or too many names"invalid argument 'StorageResult' returns 2 value(s), but 1 output(s) were captured"

To discard an output, list every name in the capture and discard on the left: a, _ = (added, removed) <- Environment.StorageResult(...).

StorageResult()​

Returns two values: how many storage bytes the current interaction has added and removed so far, for one storage account charged to one payer.

added, removed = Environment.StorageResult(account_id, payer_id)
ArgumentTypeMeaning
account_idIdentifierWhose storage is being measured
payer_idIdentifierWho is being charged — matches the payer clause on the write
coco Token

state logic:
name String

endpoint deploy Init(name String) -> (added, removed U64):
mutate name -> Token.Logic.name payer Logic
added, removed = Environment.StorageResult(Identifier(Token), Identifier(Token))
// Init(name: "noname") -> added = 39, removed = 0

endpoint dynamic Clear() -> (added, removed U64):
mutate "" -> Token.Logic.name // payer Sender is the default
added, removed = Environment.StorageResult(Identifier(Token), Sender)
// added = 1, removed = 7

The first write to a storage key costs 32 bytes for the key plus the encoded value, so storing "noname" (7 bytes when polorized) into the empty field reports added = 39. Later writes to an existing key are charged only for the change in content: clearing the field replaces the 7-byte value with a 1-byte one.

StorageResult() reports the running total at the point where it is called, so call it after the mutate you want to measure. Both values are returned together. To keep only one, discard the other with _:

endpoint dynamic ClearName() -> (removed U64):
mutate "" -> Token.Logic.name
_, removed = Environment.StorageResult(Identifier(Token), Sender)
Fixed in v0.9.2

Before v0.9.2, the optimizer at -O2 (the default for coco compile and coco test) deleted the whole call when only one of its outputs was used, and the endpoint failed at runtime with builtin.CallFailure::invalid outputs.

Example: Time-based Logic​

coco TimeLock

const NS_PER_SECOND U64 = 1000000000

state actor:
locked_until U64

endpoint dynamic Lock(seconds U64):
memory unlock_time = Environment.Timestamp() + seconds * NS_PER_SECOND
mutate unlock_time -> TimeLock.Sender.locked_until

endpoint dynamic Withdraw():
observe locked <- TimeLock.Sender.locked_until:
if Environment.Timestamp() < locked:
throw "Still locked"
mutate 0 -> TimeLock.Sender.locked_until
// proceed with withdrawal