# Far(), Remotable, and Marshaling
Let's look more closely at an eventual send between vats:
In the exporting vat, we'll take the makeCounter
Hardened JavaScript example and refine it to make remotable counters
by marking them with Far:
const makeCounter = () => {
let count = 0;
return Far('counter', {
incr: () => (count += 1),
decr: () => (count -= 1),
});
};
const publicFacet = Far('makeCounter', { makeCounter });
assert(passStyleOf(publicFacet) === 'remotable');
# Marshaling by Copy or by Presence
Recall that the first step in an eventual send is
to marshal the method name and arguments. Marshalling (opens new window) is transforming a data structure into a format suitable for storage or transmission.
The @endo/marshal (opens new window) package uses JSON (opens new window), but it can handle
Javascript values that cannot be expressed directly as JSON,
such as undefined
and BigInt
s.
const m = makeMarshal(undefined, undefined, smallCaps);
const stuff = harden([1, 2, 3n, undefined, NaN]);
const capData = m.toCapData(stuff);
t.deepEqual(m.fromCapData(capData), stuff);
Also, while many forms of data are copied between vats, remotables are marshalled so that they become remote presences when unmarshaled. Another vat can then make and use the exported counters:
const counter = E(publicFacet).makeCounter();
const n = await E(counter).incr();
assert(n === 1);
# Pass Styles and harden
Calls to remote presences must only contain passable arguments and return passable results. There are three kinds of passables:
- Remotables: objects with methods that can be called using
E()
eventual send notation. - Pass-by-copy data, such as numbers or hardened structures.
- Promises for passables.
Every object exported from a smart contract, such as publicFacet
or
creatorFacet
, must be passable. All objects used in your contract's external API must
be passable. All passables must be hardened.
Consider what might happen if we had a remote item
and we did not harden
some pass-by-copy data that we passed to it:
let amount1 = { brand: brand1, value: 10n };
await E(item).setPrice(amount1); // Throws an error, but let's imagine it doesn't.
amount1.value = 20n;
Now amount1
is supposedly both in the local and the remote vat, but the value
is 20n
in the local vat but 10n
in the remote vat. (Worse: the remote vat
might be the same as the local vat.) Requiring harden()
for pass-by-copy
data leads to behavior across vats that is straightforward to reason about.
# passStyleOf API
import { passStyleOf } from '@endo/pass-style';
passStyleOf(passable)
passable
{Passable}
- Returns:
{PassStyle}
A Passable is a hardened value that may be marshalled.
It is classified as one of the following PassStyle
values:
- Atomic pass-by-copy primitives (
"undefined" | "null" | "boolean" | "number" | "bigint" | "string" | "symbol"
). - Pass-by-copy containers that contain other Passables (
"copyArray" | "copyRecord"
). - Special cases, which also contain other Passables (
"error"
). - So-called
PassableCap
leafs ("remotable" | "promise"
).
Check `passStyleOf` when handling untrusted structured data
Just as you would use typeof
to check that an argument is
a string or number, use passStyleOf
when you expect, say, a copyRecord
;
this prevents malicious clients from playing tricks with cyclic data etc.
# Far() API
import { Far } from '@endo/far';
Far(farName, object-with-methods)
farName
{ String }
object-with-methods
{ Object }
[remotable={}]
- Returns: A
Remotable
object.
The farName
parameter gives the Remotable
an interface name for debugging purposes, which only shows
up when logged through the console
, for example with console.log
.
The object-with-methods
parameter should be an object whose properties are the functions serving
as the object's methods.
The Far()
function marks an object as remotable. Far()
also:
- Hardens the object.
- Checks that all property values are functions and throws an error otherwise.
- Accessors (i.e.,
get()
andset()
) are not allowed.
- Accessors (i.e.,
- Records the object's interface name.
Avoid accidental exports
If an object should never be exposed to other vats, you should make it
a point not to use Far()
on it. If an object is not marked as a remotable but is accidentally
exposed, an error is thrown. This prevents any vulnerability from such accidental exposure.