Skip to content

Instantly share code, notes, and snippets.

@tonyofbyteball
Last active June 11, 2020 11:46
Show Gist options
  • Save tonyofbyteball/0a09dbb016377c5099b29bc25c705a8a to your computer and use it in GitHub Desktop.
Save tonyofbyteball/0a09dbb016377c5099b29bc25c705a8a to your computer and use it in GitHub Desktop.
Oscript 2.0

Changes in Oscript 2.0

Functions

$f = ($x) => {
	 $a = var['a'];
	 $x * $a
};

// one-line syntax for functions that have only one expression
$sq = $x => $x^2;

$res = $f(2);
$nine = $sq(3);

Functions are local variables and the same rules apply to them as to local variables.

The return value is the value of the last expression or a return statement.

A function sees all other local vars and functions declared before it. Because of this, recursion is impossible.

Variables declared before the function cannot be shadowed by its arguments or other local variables declared within the function.

Complexity of a function is the sum of complexities of all operations within it. Every time a function is called, its complexity is added to the total complexity of the AA. If a function is declared but never called, its complexity doesn't affect the complexity of the AA.

Remote functions (getters)

Getters are read-only functions available to other AAs and non-AA code.

They are meant to extract information about an AA state that is not directly available through state vars. E.g. in order to fetch this information one needs to perform some calculation over state vars or access several state vars and have good knowledge about the way information is stored in the AA.

Examples:

  • oswap could expose functions that would calculate the price of a future exchange. Otherwise, clients would have to do non-trivial math themselves.
  • the token registry could expose a single getter $getTokenDescription($asset) for reading a token description, and a similar one for decimals. Otherwise, one has to read first $desc_hash = var['current_desc_' || $asset], then var['description_' || $desc_hash].

Getters are declared in a top-level getters section which is evaluated before everything else.

['autonomous agent', {
	getters: `{
		$sq = $x => $x^2;
		$g = ($x, $y) => $x + 2*$y;
		$h = ($x, $y) => $x||$y;
		$r = ($acc, $x) => $x + $acc;
	}`,
	init: `{
		// uncomment if the AA serves as library only
		// bounce("library only");
		...
	}`,
	...
}]

The code in getters section can contain only function declarations and constants. Request-specific information such as trigger.data, trigger.outputs, etc is not available in getters.

In the AA which declares them, getters can be accessed like normal functions.

Other AAs can call them by specifying the remote AA address before the function name using this syntax:

$nine = MXMEKGN37H5QO2AWHT7XRG6LHJVVTAWU.$sq(3);

or

$remote_aa = "MXMEKGN37H5QO2AWHT7XRG6LHJVVTAWU";
$nine = $remote_aa.$sq(3);

where $remote_aa variable must be a constant (it must be known at deploy time in order calculate the complexity).

The complexity of a remote call is the complexity of its function, plus one.

All functions declared in the getters section are publicly available. If some of them are not meant for public use, one can indicate this by a naming convention, e.g. by starting their names with an underscore $_callPrivateFunction() but information hiding cannot be really enforced since all getters operate on public data anyway.

Getters can also be conveniently called from non-AAs. In node.js code:

const { executeGetter } = require('ocore/formula/evaluation.js');
const db = require('ocore/db.js');

const args = ["arg1", "arg2"];
const res = await executeGetter(db, aa_address, getter, args);

For remote clients, there is a light/execute_getter command in websocket API, hopefully it will be shortly available through obyte.js.

Initializing objects and arrays

Objects and arrays can be initialized using familiar syntax:

$obj = {a: 3, b: 7 };
$arr = [7, 2, 's', {a: 6}];

Mutating objects and arrays

Although all local variables are single-assignment, objects and arrays can be mutated by modifying, adding, or deleting their fields:

$obj = {a: 3, b: 7 };
$obj.a = 4; // modifies an existing field
$obj.c = 10; // adds a new field
delete($obj, 'b'); // deletes a field
freeze($obj); // prohibits further mutations of the object

$arr = [7, 2, 's', {a: 6}];
$arr[0] = 8; // modifies an existing element
$arr[] = 5; // adds a new element
delete($arr, 1); // removes element 1
freeze($arr); // prohibits further mutations of the array

The left-hand selectors can be arbitrarily long .a.b[2].c.3.d[].e. If some elements do not exist, an empty object or array is created automatically. [] adds to the end of an array. Array indexes cannot be skipped, i.e. if an array has length 5, you cannot assign to element 7. Once you are dome mutating an object, can call freeze() to prevent further accidental mutations.

Hashes of objects

$hash = sha256($obj);

$definition = ['autonomous agent', {
  ...
}];
$address = chash160($definition);

sha256 now works on objects too and returns sha256 of the object's json.

chash160 is a new function that returns a 160-bit checksummed hash of an object, it is most useful for calculating an address when you know its definition, e.g. if you programmatically define a new AA and want to know its address in order to immediately trigger it. Previously, it was possible only by using various tricks e.g. a forwarder AA.

map, reduce, foreach, filter

$ar = [2, 5, 9];
$ar2 = map($ar, 3, $x => $x^2);

A function is executed over each element of an array or object. The callback function can be anonymous like in the example above, or referenced by name:

$f = $x => $x^2;
$ar = [2, 5, 9];
$ar2 = map($ar, 3, $f);

It can also be a remote getter:

$ar2 = map($ar, 3, $remote_aa.$f);

The function for map, foreach, and filter accepts 1 or 2 arguments. If it accepts 1 argument, the value of each element is passed to it. If it accepts 2 arguments, key and value for objects or index and elememt for arrays are passed.

The second argument is the maximum number of elements that an array or object can have. If it is larger, the script fails. This number must be a constant so that it can be known at deploy time, and the complexity of the entire operation is the complexity of the callback function times maximum number of elements. If the function has 0 complexity, the total complexity of map/reduce/foreach/filter is assumed to be 1 independently of the max number of elements. Max number of elements cannot exceed 100.

reduce has one additional argument for initial value:

$c = 3;
$ar = [2, 5, 9];
$acc = reduce($ar, $c, ($acc, $x) => $acc + $x, 0); // sums all elements, will return 16

The callback function for reduce accepts 2 or 3 arguments: accumulator and value or accumulator, key, and value (accumulator, index, and element for arrays).

These functions are similar to their javascript counterparts but unlike javascript they can also operate on objects, not just arrays.

Concatenation of arrays and objects

Concat operator || can now be used for arrays and objects too:

[4, 6] || [3, 1]    // [4, 6, 3, 1]
{x: 1, y: 7} || {y: 8, a:9}   // {x: 1, y: 8, a:9}

If the same key is found in both objects, the value from the right-hand one prevails.

Trying to concat an array and object results in error.

If either operand is a scalar, both are converted to strings and concatenated as strings. Objects/arrays become "true".

split, join

split("let-there-be-light", "-")  // ["let", "there", "be", "light"]
join(["let", "there", "be", "light"], "-")  // "let-there-be-light"

split("let-there-be-light", "-", 2)  // ["let", "there"]

The functions are similar to their conterparts in other languages. join can be applied to objects too, in this case the elements are sorted by key and their values are joined.

reverse array

reverse([4, 8, 3])  // [3, 8, 4]

The function reverses an array and returns a new one. Deep copies of all elements are created.

Passing a non-array to this function results in error.

length of arrays and objects

length([3, 7, 9])   // 3

This function now operates on objects and arrays too. When passed an object or array, it returns the number of elements in object or array. Scalar types are converted to strings as before and the length of the string is returned.

keys of an object

keys({b: 3, a: 8}) // ['a', 'b']

Returns the keys of an object. The keys are sorted. Passing anything but an object results in error.

replace

replace(str, search_str, replacement)

Replaces all occurences of search_str in str with replacement and returns the new string.

has_only

has_only(str, allowed_chars)

Returns true if str consists only of characters in allowed_chars. allowed_chars is a group of characters recognized by regular expressions, examples: a-z0-9, \w. has_only adds +1 to complexity.

State vars can now store objects

var['s'] = {a:8, b:2};
var['s'] ||= {c:6};  // concat an object

Internally, objects are stored as json strings and their length is limited. Don't try to store a structure in a state var if this structure can grow indefinitely.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment