Standard Library

NEO•ONE includes a library of standard smart contract values, functions, interfaces and classes.

This chapter will go into detail on some of the more common values and types that we will use throughout the guide. While working through the rest of the chapters you may want to keep this page handy.

For more details, check out the Smart Contract reference.



Value Types

The standard library includes several specialized value types which are defined in a way that makes them difficult to use incorrectly. For example, it’s a static compile time error to pass an Address where a Hash256 is expected, even though both are really just Buffers underneath the hood.

  • Address - a Buffer that represents a NEO address.
  • Hash256 - a Buffer that represents a NEO 256 bit hash, most commonly used for asset ids like NEO or GAS asset ids.
  • PublicKey - a Buffer that represents a public key.

Each of the value types can be created from a string literal using the from static method, for example, Address.from('APyEx5f4Zm4oCHwFWiSTaph1fPBxZacYVR'). Hash256 also contains static properties for the NEO and GAS Hash256 values.

The Address type has a commonly used static method, isCaller, which is used to check that the passed Address directly called and approved the current method invocation.

Address.isCaller(address);

Address.isCaller should be used whenever you want to take an action for a given Address. For example, you would want to check Address.isCaller(address) before transferring tokens from the address to another party.


Tagged Types

Tagged types are the same as their underlying type, but we’ve "tagged" them with a piece of compile-time data. NEO•ONE currently contains two tagged types, and their tags are used exclusively for generating the corresponding NEO•ONE client APIs for the smart contract. The most common tagged type and the only one we’ll use throughout the guide is the Fixed type.

The Fixed<T> type tags a number with the number of decimals that it represents. All numbers in TypeScript smart contracts are integers since there are no floating point values in smart contracts. However, we typically consider the values we’re working with to have decimals from the user’s point of view and Fixed<T> helps capture that notion.

For example, the Fixed<2> type tells the NEO•ONE toolchain that the integer value represents a fixed point decimal with 2 places. In other words, when we have the value 1250 in a smart contract, it really means 12.50 to the user. The NEO•ONE toolchain uses this information to generate client APIs that automatically convert from the integer representation to the decimal point representation and visa-versa.

This makes it easy to do things like display the result of a smart contract method invocation that returns a Fixed<2>. Simply convert it to a string since the client APIs have already converted it to the decimal representation. Similarly, for dapp inputs, simply take the user’s decimal value and pass it directly to the NEO•ONE client APIs, under the hood it will convert appropriately.


Blockchain and Transaction Information

The Blockchain value contains several properties pertaining to the current state of the blockchain, the current transaction and the current invocation:

  • Blockchain.currentBlockTime - the timestamp of the Block that this Transaction will be included in.
  • Blockchain.currentHeight - index of the last Block persisted to the blockchain.
  • Blockchain.currentTransaction - the current InvocationTransaction.
  • Blockchain.currentCallerContract - the Address of the smart contract that directly invoked this contract. May be undefined if the current invocation was not from another smart contract.

Opaque Tag Symbol

If you look at the definition file for the standard library, you might notice a property OpaqueTagSymbol0, OpaqueTagSymbol1, one0 or one1 that is present on all types, including global types like Array or Map. In order to emit the most efficient NEO VM bytecode possible, we have specialized implementations of all of the standard library types. One limitation of this approach, however, is that you must explicitly use the types. For example, you can’t pass an Array where an Array-like but not Array interface is expected. Adding the OpaqueTagSymbol0, OpaqueTagSymbol1, one0, or one1 properties helps enforce this as a static type error, but it does not catch all cases (though we’re working on improving this).

As a rule of thumb, don’t rely on "Duck Typing", instead always be explicit about the types you’re using.

JavaScript Semantics

The NEO•ONE compiler is as close to regular TypeScript as possible. But there are a few areas where our compiler does not exactly match normal JavaScript semantics. These areas almost never come up, but could cause strange behavior if you are expecting your Smart Contract code to behave exactly like JavaScript. One of those is when using native Maps and Sets. In normal JavaScript if you use a structural type as a key in a Map or a member in a Set, you can only retrieve that key/member later if it’s the same reference. Trying to retrieve that key/member with the same value of a different reference will not work in regular JavaScript. But it WILL work in our compiler. The best way to demonstrate this is with examples.

Set Example

This test would pass in regular TS:

const x = new Set<ReadonlyArray<number>>();
const y = [0];
const z = [0];
x.add(y).add(z);

assertEqual(x.has(y), true);
assertEqual(x.has(z), true);
assertEqual(x.has([0]), false); // notice difference here
assertEqual(x.size, 2); // notice difference here

x.delete(y);
assertEqual(x.delete(z), true); // notice difference here
assertEqual(x.delete(z), false);
assertEqual(x.size, 0);
assertEqual(x.has(y), false);
assertEqual(x.has(z), false);

But this test will pass in our TS compiler:

const x = new Set<ReadonlyArray<number>>();
const y = [0];
const z = [0];
x.add(y).add(z);

assertEqual(x.has(y), true);
assertEqual(x.has(z), true);
assertEqual(x.has([0]), true); // notice difference here
assertEqual(x.size, 1); // notice difference here

x.delete(y);
assertEqual(x.delete(z), false); // notice difference here
assertEqual(x.delete(z), false);
assertEqual(x.size, 0);
assertEqual(x.has(y), false);
assertEqual(x.has(z), false);

Map Example

This test would pass in regular TS:

const x = new Map<ReadonlyArray<number>, string>();
const y = [0];
const z = [0];
x.set(y, 'bar').set(z, 'baz');

assertEqual(x.get(y), 'bar'); // notice difference here
assertEqual(x.has(y), true);
assertEqual(x.get(z), 'baz');
assertEqual(x.has(z), true);
assertEqual(x.get([0]), undefined); // notice difference here
assertEqual(x.has([0]), false); // notice difference here
assertEqual(x.size, 2); // notice difference here

x.delete(y);
assertEqual(x.delete(z), true); // notice difference here
assertEqual(x.delete(z), false);
assertEqual(x.size, 0);
assertEqual(x.get(y), undefined);
assertEqual(x.has(y), false);
assertEqual(x.get(z), undefined);
assertEqual(x.has(z), false);

But this test will pass in our TS compiler:

const x = new Map<ReadonlyArray<number>, string>();
const y = [0];
const z = [0];
x.set(y, 'bar').set(z, 'baz');

assertEqual(x.get(y), 'baz'); // notice difference here
assertEqual(x.has(y), true);
assertEqual(x.get(z), 'baz');
assertEqual(x.has(z), true);
assertEqual(x.get([0]), 'baz'); // notice difference here
assertEqual(x.has([0]), true); // notice difference here
assertEqual(x.size, 1); // notice difference here

x.delete(y);
assertEqual(x.delete(z), false); // notice difference here
assertEqual(x.delete(z), false);
assertEqual(x.size, 0);
assertEqual(x.get(y), undefined);
assertEqual(x.has(y), false);
assertEqual(x.get(z), undefined);
assertEqual(x.has(z), false);
Edit this page
Previous Article
Smart Contract Basics
Next Article
Properties and Storage
DOCS
InstallationMain ConceptsAdvanced GuidesAPI ReferenceContributing
CHANNELS
GitHubStack OverflowDiscord ChatTwitterYouTube
COPYRIGHT © 2021 NEO•ONE