Coder Social home page Coder Social logo

near / near-sdk-as Goto Github PK

View Code? Open in Web Editor NEW
114.0 6.0 43.0 24.34 MB

Tools for building NEAR smart contracts in AssemblyScript

Home Page: https://near.github.io/near-sdk-as/

License: Other

TypeScript 65.70% JavaScript 21.76% Shell 0.38% Rust 12.17%

near-sdk-as's Introduction

near-sdk-as

Collection of packages used in developing NEAR smart contracts in AssemblyScript including:

  • runtime library - AssemblyScript near runtime library
  • bindgen - AssemblyScript transformer that adds the bindings needed to (de)serialize input and outputs.
  • near-mock-vm - Core of the NEAR VM compiled to WebAssembly used for running unit tests.
  • @as-pect/cli - AssemblyScript testing framework similar to jest.

To Install

yarn add -D near-sdk-as

Project Setup

To set up a AS project to compile with the sdk add the following asconfig.json file to the root:

{
  "extends": "near-sdk-as/asconfig.json"
}

Then if your main file is assembly/index.ts, then the project can be build with asbuild:

yarn asb

will create a release build and place it ./build/release/<name-in-package.json>.wasm

yarn asb --target debug

will create a debug build and place it in ./build/debug/..

Testing

Unit Testing

See the sdk's as-pect tests for an example of creating unit tests. Must be ending in .spec.ts in a assembly/__tests__.

License

near-sdk-as is distributed under the terms of both the MIT license and the Apache License (Version 2.0).

See LICENSE-MIT and LICENSE-APACHE for details.

near-sdk-as's People

Contributors

ailisp avatar aluhning avatar amgando avatar ashutoshvarma avatar behaviary avatar bestatigen avatar bowenwang1996 avatar chadoh avatar chefsale avatar danielrx avatar dependabot-preview[bot] avatar encody avatar evgenykuzyakov avatar frol avatar jacqueswww avatar janedegtiareva avatar k06a avatar kaiba42 avatar luciotato avatar maksymzavershynskyi avatar mehtaphysical avatar mikedotexe avatar petersalomonsen avatar vgrichina avatar willemneal avatar yjhmelody avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

near-sdk-as's Issues

Use base58 and base64 libraries.

Such as Base58 and Base64 etc.
Being able to reuse these basic libraries is very useful for the ecological development of AssemblyScript.

Add access to data from collections and storage

Some of our users requested an ability to have a contract method that returns an element from the persistent collection without going through encoding/decoding process. This requires adding raw getter setters for the collections, e.g.: Vector.raw_get that would return storage value without decoding it. And adding a function that allows to return a raw value, e.g. return_raw.
The contract code then will look like:

export function token_get(tokenId: TokenId): void 

{ Token.get(tokenId); }


public static get(tokenId: TokenId): void { assert(tokenId < Item.count(PREFIX.Token), 'Invalid tokenId'); runtime_api.return_raw(tokens.get_raw(tokenId)); }

Alternatively we can leverage registers described here: https://nomicon.io/RuntimeSpec/Components/BindingsSpec/TrieAPI.html to even avoid copying values into Wasm memory, e.g.:


export function token_get(tokenId: TokenId): void { Token.get(tokenId); } 

public static get(tokenId: TokenId): void 

{ assert(tokenId < Item.count(PREFIX.Token), 'Invalid tokenId'); tokens.get_into_register(tokenId, 0); runtime_api.return_from_register(0); } 

Unfortunately, in both cases this will mess up with the method signature which is not nice if it used for doc generation.

Windows compatibility issue with transform

Paired with Willem and he was able to narrow down an issue on Windows to this area, in particular this line:

this.parser.parseFile(entryFile, relativePath, true);

during the pair session we changed it to:
this.parser.parseFile("import \"near-sdk-as/bindgen\"", "bindgen", true);

A way to test this is to clone the counter example on Windows with:
git clone [email protected]:near-examples/counter.git
and run yarn && yarn dev

A VM of Windows can be found here:
https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/

Willem, I'm happy to do whatever I can to help out. I'd really like to see this patched up so we can get Windows users on board in the forthcoming online hackathons. :)

Some collection types use prefix incorrectly

The constructors of collection types have a parameter named prefix. A user may assume that all storage keys used to store the collection's data begin with prefix.

This doesn't hold for some collection types. This may result in storage collisions between different collections, as well as between collections and direct storage use.

Also, it will be nice if the collections in near-sdk-as had the same storage representation as in near-sdk-rs.

Exporting functions from submodules

Right now, there is no simple way to export a function that was imported from a submodule. This makes it hard to modularize an assembly contract, therefore complicating the whole developing process.

I would love if we could have the option to make:

import {f1, f2, f3 ... fn} from './bar'}
export {f1, f2, f3 ... fn}

[Bounty] Add raw storage operations

    1. NEAR Bounty Terms

Before beginning work on the bounty, you must submit a proposal. Only if your proposal is accepted will you be able to claim the reward of the bounty.

    1. Description

Currently when writing to and reading from storage data is serialized and deserialized. This is useful when the the contract needs to understand or process the data. However, in many use cases the data doesn't need to be altered, just written and read by the caller of the contract.

For example, consider a contract registry, which allows users to store contract binaries that can be use in a generic factory for deploying contracts.

    1. Context

The two host functions for reading/writing storage are:

 function storage_write(
 key_len: u64,
 key_ptr: u64,
 value_len: u64,
 value_ptr: u64,
 register_id: u64
 ): u64;

function storage_read(
 key_len: u64,
 key_ptr: u64,
 register_id: u64
 ): u64;

This C-like interface informs the host of the size and location of the bytes to be stored and read. In the case of storage_read, the corresponding value read is placed into a register. The host provides a hash map of registers, which maps id's to byte arrays. There are two more functions needed to interact with the registers:

 function register_len(register_id: u64): u64;
 function read_register(register_id: u64, ptr: u64): void;

The first function returns the number of bytes that should be allocated and the second writes those bytes to ptr.

Thus typical interaction with storage requires both serialization and memory. However, in any function that takes a length and a pointer, if a length of zero is passed, the pointer is treated as a register_id. The register is read instead of the contracts memory. For example,

export function setFoo()

 { input(1); // Reads input into register 1\. storage_write(key_len, key_ptr, 0, 1, 1); // Uses contents of register 1 and if an old value was present it is written into register1 value_return(0, 1); // Returns the old value of Foo // value_return(value_len: 64, value_ptr: u64): void } 

This can be very useful for large values as you can skip the expensive copying and serializing.

    1. Relevant repos or issues
    1. API
class Storage 

 { ...... /** * Write value of key into register. Return value is errorcode. `1` means sucess. * Key will need to be encoded into bytes see `sdk/util`. */ static read_raw(key: string, register_id: u64): u64; static write_raw(key: string, register_id: u64): u64; /** * Write input into register and then write that into storage at key. */ static write_input(key: string, register_id: u64 = 0): void; } 

namespace env 

 { ... export function return_raw(register_id: u64): void; //Hint in above example. } 

The same is true for every collection class. For example,

class PersistentMap<K, V>

 { ................................ // Returns the register used, default is 0\. // Show throw an error if key is not present. get_raw(key: string, register_id: u64 = 0): u64; // Write contents of register to corresponding key set_raw(key: string, register_id: u64): u64; } 
    1. Acceptance Criteria

Each of the follow also includes tests

  • Storage static methods
  • env function
  • PersistentMap
  • PersistentVector
  • PersistentDeque
  • PersistentSet
  • PersistentUnorderedMap
  • AVL Tree
    1. Bounty

300 DAI

Improve error handling in VM Runner runtime

Currently if an account is not present in a call, the error says:

Error: Failed to run successfully: ,,thread 'main' panicked at 'called Result::unwrap() on an Err value: Os { code: 2, kind: NotFound, message: "No such file or directory" }', runtime/near-vm-runner-standalone/src/main.rs:211:9

This is not clear that the problem is the missing account.

Disable storage_iter*

We should remove storage_iter* usage from SDK.
Storage iterators makes it expensive and restricts underlying data structure on the Node side to be sorted tree. We should be able to achieve TPS increase, by replacing underlying storage with key-value hash-based storage in the future, which will make storage access much cheaper.

See near/nearcore#2234 for background

npx asb run error

WARNING AS100: Not implemented: Template Literals can only be used for multi-line strings. Interpolation is not supported.

   throw new Error(`key is less than mininum key in tree`);
else return root.key;

}

/**

  • Returns the minimum key that is strictly greater than the key.
  • Throws if empty or if key is higher than or equal to this.max().
  • @param key Key for upper bound (exclusive).
  • @returns Minimum key that is strictly greater than given key.
    */
    higher(key: K): K {
    let root = this.rootNode!;
while (root.left || root.right) {
  if (root.key <= key && root.right) {
    root = this.node(root.right)!;
  } else if (root.right) {
    const leftNode = this.node(root.left)!;
    if (leftNode.key > key) {
      root = leftNode;
    } else {
      break;
    }
  }
}

if (root.key <= key)
  throw new Error(`key is greater than maximum key in tree`);
else return root.key;

}

/**

  • Returns the maximum key that is less or equal than the key.
  • Throws if empty or if key is lower than this.min().
  • @param key Key for lower bound (inclusive).
  • @returns Maximum key that is less than or equal to given key.
    */
    lowerOrEqual(key: K): K {
    return this.has(key) ? key : this.lower(key);
    }
    // alias to match rust sdk
    floorKey(key: K): K {
    return this.lowerOrEqual(key);
    }

/**

  • Returns the minimum key that is greater or equal than the key.
  • Throws if empty or if key is higher than this.max().
  • @param key Key for upper bound (inclusive).
  • @returns Minimum key that is greater or equal to given key.
    */
    higherOrEqual(key: K): K {
    return this.has(key) ? key : this.higher(key);
    }
    // alias to match rust sdk
    ceilKey(key: K): K {
    return this.higherOrEqual(key);
    }

/**

  • Removes all key-value pairs from the tree
    */
    clear(): void {
    while (this.size > 0) {
    this._val.delete(this._tree.popBack().key);
    }
    this.rootId = null;
    }

// useful for debugging
private toString(): string {
const a: string[] = ["\n"];
for (let i: i32 = 0; i < i32(this.size); ++i) {
const node = this._tree[i];
const key = u32(node.key).toString();
const index = node.id.toString();
const leftKey = node.left
? u32(this.node(node.left)!.key).toString()
: "null";
const rightKey = node.right
? u32(this.node(node.right)!.key).toString()
: "null";
const isRoot = node.id === this.rootId!.val ? "true" : "false";
const childrenProperties: string[] = [leftKey, rightKey, isRoot];
const nodeProperties: string[] = [
key,
",",
index,
":",
childrenProperties.join(),
];
a.push(nodeProperties.join(" "));
}
return a.join("\n");
}

/**


  •  AVL Tree core routines
    

*/

// returns root key of the tree.
get rootKey(): K {
assert(!isNull(this.rootNode), "rootNode must be defined");
return this.rootNode!.key;
}

private set rootId(rootId: Nullable | null) {
this._rootId = rootId;
storage.set(this._elementPrefix + "root", this._rootId);
}

private get rootId(): Nullable | null {
return this._rootId;
}

// returns the root node of the tree, if it exists.
// returns null otherwise
private get rootNode(): AVLTreeNode | null {
return this.node(this.rootId);
}

// returns the height for a given node
private nodeHeight(id: Nullable | null): u32 {
return id ? this._tree[id.val].height : 0;
}

// returns the difference in heights between a node's left and right subtrees
private balance(node: AVLTreeNode): i32 {
return this.nodeHeight(node.left) - this.nodeHeight(node.right);
}

// updates the height for a given node based on the heights of its subtrees
private updateHeight(node: AVLTreeNode): void {
node.height =
1 + max(this.nodeHeight(node.left), this.nodeHeight(node.right));
this._tree[node.id] = node;
}

// returns the node for the given id (index into underlying array this._tree)
private node(id: Nullable | null): AVLTreeNode | null {
return id ? this._tree[id.val] : null;
}

// inserts a new key into the tree and
// recursively updates the height of each node in the tree,
// performing rotations as needed from bottom to top
// to maintain the AVL tree balance invariant
private insertAt(parentNode: AVLTreeNode | null, key: K): AVLTreeNode {
if (!parentNode) {
const node = new AVLTreeNode(this.size, key);
this._tree.push(node);
return node;
} else {
if (key < parentNode.key) {
parentNode.left = new Nullable(
this.insertAt(this.node(parentNode.left), key).id
);
} else if (key > parentNode.key) {
parentNode.right = new Nullable(
this.insertAt(this.node(parentNode.right), key).id
);
} else {
throw new Error(
"Key already exists, but does not have an associated value"
);
}

  this.updateHeight(parentNode);

  return this.enforceBalance(parentNode);
}

}

// given a node
// performs a single set left and right rotations to maintain AVL tree balance invariant
private enforceBalance(node: AVLTreeNode): AVLTreeNode {
const balance = this.balance(node);
if (balance > 1) {
// implies left child must exist, since balance = left.height - right.height
const leftChildNode = this.node(node.left)!;
if (this.balance(leftChildNode) < 0) {
node.left = new Nullable(this.rotateRight(leftChildNode).id);
}
return this.rotateLeft(node);
} else if (balance < -1) {
// implies right child must exist
const rightChildNode = this.node(node.right)!;
if (this.balance(rightChildNode) > 0) {
node.right = new Nullable(this.rotateLeft(rightChildNode).id);
}
return this.rotateRight(node);
} else {
// node is already balanced
return node;
}
}

// given a node
// performs a righthand rotation
// node child
// \ /
// child -> node
// /
// child.left child.right
private rotateRight(node: AVLTreeNode): AVLTreeNode {
const childNode = this.node(node.right)!;
node.right = childNode.left;
childNode.left = new Nullable(node.id);

this.updateHeight(node);
this.updateHeight(childNode);

return childNode;

}

// given a node
// performs a lefthand rotation
// node child
// /
// child -> node
// \ /
// child.right child.right
private rotateLeft(node: AVLTreeNode): AVLTreeNode {
const childNode = this.node(node.left)!;
node.left = childNode.right;
childNode.right = new Nullable(node.id);

this.updateHeight(node);
this.updateHeight(childNode);

return childNode;

}

// removes the given key from the tree, maintaining the AVL balance invariant
private doRemove(key: K): Nullable | null {
const nodeAndParent = this.lookupAt(this.rootNode!, key);
let node = nodeAndParent.child;
let parentNode = nodeAndParent.parent;
let successorId: Nullable | null;

if (!node.left && !node.right) {
  // node to remove is a leaf node
  if (parentNode.key < node.key) {
    parentNode.right = null;
  } else {
    parentNode.left = null;
  }
  successorId = null;
} else if (!node.left) {
  // node to remove has 1 right child
  // replace node to remove with its right child
  if (parentNode.key < node.key) {
    parentNode.right = node.right;
  } else {
    parentNode.left = node.right;
  }
  successorId = node.right;
} else if (!node.right) {
  // node to remove has 1 left child
  // replace node to remove with its left child
  if (parentNode.key < node.key) {
    parentNode.right = node.left;
  } else {
    parentNode.left = node.left;
  }
  successorId = node.left;
} else {
  // node has 2 children, search for successor
  const isLeftLeaning = this.balance(node) >= 0;
  const nodes = isLeftLeaning
    ? // node to remove is left leaning, so search left subtree
      this.maxAt(this.node(node.left)!, node)
    : // node to remove is right leaning, so search right subtree
      this.minAt(this.node(node.right)!, node);

  const successor = nodes.child;

  // node to remove and parentNode can be the same node on small trees (2 levels, 2-3 nodes)
  // if so, make parentNode point to node
  parentNode = nodes.parent.id === node.id ? node : nodes.parent;

  const successorIsLeftChild = parentNode.left
    ? parentNode.left!.val === successor.id
    : false;

  // remove successor from its parent, and link the successor's child to its grandparent
  if (successorIsLeftChild) {
    parentNode.left = isLeftLeaning ? successor.left : successor.right;
  } else {
    parentNode.right = isLeftLeaning ? successor.left : successor.right;
  }

  // take successor's key, and update the node to remove
  node.key = successor.key;
  this._tree[node.id] = node;
  successorId = new Nullable(node.id);

  // set node to point to successor, so it is removed from the tree
  node = successor;
}

this.updateHeight(parentNode);
this.swapRemove(node);

return this.size > 0 && this.rootNode
  ? new Nullable(this.rebalanceAt(this.rootNode!, parentNode.key).id)
  : successorId;

}

// removes the given node from the tree,
// and replaces it with the last node in the underlying array (this._tree)
private swapRemove(node: AVLTreeNode): void {
if (node.id === this.size - 1) {
// node is last element in tree, so no swapping needed
if (node.id === this.rootId!.val) {
this.rootId = null;
}
} else {
const lastNode = this._tree[this.size - 1];
const parentNode = this.lookupAt(this.rootNode!, lastNode.key).parent;

  if (lastNode.id === this.rootId!.val) {
    this.rootId = new Nullable(node.id);
  }

  // check to make sure that parentNode and lastNode do not overlap
  if (parentNode.id !== lastNode.id) {
    // make lastNode's parent point to new index (index of node that lastNode is replacing)
    if (parentNode.left ? parentNode.left!.val === lastNode.id : false) {
      parentNode.left = new Nullable(node.id);
    } else {
      parentNode.right = new Nullable(node.id);
    }

    // update the parentNode
    this._tree[parentNode.id] = parentNode;
  }

  // update index of lastNode
  lastNode.id = node.id;
  this._tree[lastNode.id] = lastNode;
}

this._tree.pop();

}

// given a starting node
// returns the leftmost (min) descendant node, and its parent
private minAt(
root: AVLTreeNode,
parentNode: AVLTreeNode | null = null
): ChildParentPair {
return root.left
? this.minAt(this.node(root.left)!, root)
: new ChildParentPair(root, parentNode ? parentNode : root);
}

// given a starting node
// returns the rightmost (max) descendant node, and its parent
private maxAt(
root: AVLTreeNode,
parentNode: AVLTreeNode | null = null
): ChildParentPair {
return root.right
? this.maxAt(this.node(root.right)!, root)
: new ChildParentPair(root, parentNode ? parentNode : root);
}

// given a key and a starting node
// returns the node with the associated key, as well as its parent (if it exists)
// caution: this method assumes the key exists in the tree, and will throw otherwise
private lookupAt(
root: AVLTreeNode,
key: K,
parentNode: AVLTreeNode | null = null
): ChildParentPair {
return root.key === key
? new ChildParentPair(root, parentNode ? parentNode : root)
: key < root.key
? this.lookupAt(this.node(root.left)!, key, root)
: this.lookupAt(this.node(root.right)!, key, root);
}

// recursively updates the height of each node in the tree,
// and performs rotations as needed from bottom to top
// to maintain the AVL tree balance invariant
private rebalanceAt(root: AVLTreeNode, key: K): AVLTreeNode {
if (root.key > key) {
const leftChild = this.node(root.left);
if (leftChild) {
root.left = new Nullable(this.rebalanceAt(leftChild, key).id);
}
} else if (root.key < key) {
const rightChild = this.node(root.right);
if (rightChild) {
root.right = new Nullable(this.rebalanceAt(rightChild, key).id);
}
}

this.updateHeight(root);

return this.enforceBalance(root);

}

// recursively checks that each node in the tree is balanced by checking that
// the AVL tree invariant holds:
// any node's left and right subtrees must have a difference in height <= 1
private isBalanced(root: AVLTreeNode | null = this.rootNode): bool {
const b = root ? this.balance(root) : 0;
return b >= -1 && b <= 1 && root
? this.isBalanced(this.node(root.left)) &&
this.isBalanced(this.node(root.right))
: true;
}
}

                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

in ~lib/near-sdk-core/collections/avlTree.ts(267,23)

WARNING AS100: Not implemented: Template Literals can only be used for multi-line strings. Interpolation is not supported.

   throw new Error(`key is greater than maximum key in tree`);
else return root.key;

}

/**

  • Returns the maximum key that is less or equal than the key.
  • Throws if empty or if key is lower than this.min().
  • @param key Key for lower bound (inclusive).
  • @returns Maximum key that is less than or equal to given key.
    */
    lowerOrEqual(key: K): K {
    return this.has(key) ? key : this.lower(key);
    }
    // alias to match rust sdk
    floorKey(key: K): K {
    return this.lowerOrEqual(key);
    }

/**

  • Returns the minimum key that is greater or equal than the key.
  • Throws if empty or if key is higher than this.max().
  • @param key Key for upper bound (inclusive).
  • @returns Minimum key that is greater or equal to given key.
    */
    higherOrEqual(key: K): K {
    return this.has(key) ? key : this.higher(key);
    }
    // alias to match rust sdk
    ceilKey(key: K): K {
    return this.higherOrEqual(key);
    }

/**

  • Removes all key-value pairs from the tree
    */
    clear(): void {
    while (this.size > 0) {
    this._val.delete(this._tree.popBack().key);
    }
    this.rootId = null;
    }

// useful for debugging
private toString(): string {
const a: string[] = ["\n"];
for (let i: i32 = 0; i < i32(this.size); ++i) {
const node = this._tree[i];
const key = u32(node.key).toString();
const index = node.id.toString();
const leftKey = node.left
? u32(this.node(node.left)!.key).toString()
: "null";
const rightKey = node.right
? u32(this.node(node.right)!.key).toString()
: "null";
const isRoot = node.id === this.rootId!.val ? "true" : "false";
const childrenProperties: string[] = [leftKey, rightKey, isRoot];
const nodeProperties: string[] = [
key,
",",
index,
":",
childrenProperties.join(),
];
a.push(nodeProperties.join(" "));
}
return a.join("\n");
}

/**


  •  AVL Tree core routines
    

*/

// returns root key of the tree.
get rootKey(): K {
assert(!isNull(this.rootNode), "rootNode must be defined");
return this.rootNode!.key;
}

private set rootId(rootId: Nullable | null) {
this._rootId = rootId;
storage.set(this._elementPrefix + "root", this._rootId);
}

private get rootId(): Nullable | null {
return this._rootId;
}

// returns the root node of the tree, if it exists.
// returns null otherwise
private get rootNode(): AVLTreeNode | null {
return this.node(this.rootId);
}

// returns the height for a given node
private nodeHeight(id: Nullable | null): u32 {
return id ? this._tree[id.val].height : 0;
}

// returns the difference in heights between a node's left and right subtrees
private balance(node: AVLTreeNode): i32 {
return this.nodeHeight(node.left) - this.nodeHeight(node.right);
}

// updates the height for a given node based on the heights of its subtrees
private updateHeight(node: AVLTreeNode): void {
node.height =
1 + max(this.nodeHeight(node.left), this.nodeHeight(node.right));
this._tree[node.id] = node;
}

// returns the node for the given id (index into underlying array this._tree)
private node(id: Nullable | null): AVLTreeNode | null {
return id ? this._tree[id.val] : null;
}

// inserts a new key into the tree and
// recursively updates the height of each node in the tree,
// performing rotations as needed from bottom to top
// to maintain the AVL tree balance invariant
private insertAt(parentNode: AVLTreeNode | null, key: K): AVLTreeNode {
if (!parentNode) {
const node = new AVLTreeNode(this.size, key);
this._tree.push(node);
return node;
} else {
if (key < parentNode.key) {
parentNode.left = new Nullable(
this.insertAt(this.node(parentNode.left), key).id
);
} else if (key > parentNode.key) {
parentNode.right = new Nullable(
this.insertAt(this.node(parentNode.right), key).id
);
} else {
throw new Error(
"Key already exists, but does not have an associated value"
);
}

  this.updateHeight(parentNode);

  return this.enforceBalance(parentNode);
}

}

// given a node
// performs a single set left and right rotations to maintain AVL tree balance invariant
private enforceBalance(node: AVLTreeNode): AVLTreeNode {
const balance = this.balance(node);
if (balance > 1) {
// implies left child must exist, since balance = left.height - right.height
const leftChildNode = this.node(node.left)!;
if (this.balance(leftChildNode) < 0) {
node.left = new Nullable(this.rotateRight(leftChildNode).id);
}
return this.rotateLeft(node);
} else if (balance < -1) {
// implies right child must exist
const rightChildNode = this.node(node.right)!;
if (this.balance(rightChildNode) > 0) {
node.right = new Nullable(this.rotateLeft(rightChildNode).id);
}
return this.rotateRight(node);
} else {
// node is already balanced
return node;
}
}

// given a node
// performs a righthand rotation
// node child
// \ /
// child -> node
// /
// child.left child.right
private rotateRight(node: AVLTreeNode): AVLTreeNode {
const childNode = this.node(node.right)!;
node.right = childNode.left;
childNode.left = new Nullable(node.id);

this.updateHeight(node);
this.updateHeight(childNode);

return childNode;

}

// given a node
// performs a lefthand rotation
// node child
// /
// child -> node
// \ /
// child.right child.right
private rotateLeft(node: AVLTreeNode): AVLTreeNode {
const childNode = this.node(node.left)!;
node.left = childNode.right;
childNode.right = new Nullable(node.id);

this.updateHeight(node);
this.updateHeight(childNode);

return childNode;

}

// removes the given key from the tree, maintaining the AVL balance invariant
private doRemove(key: K): Nullable | null {
const nodeAndParent = this.lookupAt(this.rootNode!, key);
let node = nodeAndParent.child;
let parentNode = nodeAndParent.parent;
let successorId: Nullable | null;

if (!node.left && !node.right) {
  // node to remove is a leaf node
  if (parentNode.key < node.key) {
    parentNode.right = null;
  } else {
    parentNode.left = null;
  }
  successorId = null;
} else if (!node.left) {
  // node to remove has 1 right child
  // replace node to remove with its right child
  if (parentNode.key < node.key) {
    parentNode.right = node.right;
  } else {
    parentNode.left = node.right;
  }
  successorId = node.right;
} else if (!node.right) {
  // node to remove has 1 left child
  // replace node to remove with its left child
  if (parentNode.key < node.key) {
    parentNode.right = node.left;
  } else {
    parentNode.left = node.left;
  }
  successorId = node.left;
} else {
  // node has 2 children, search for successor
  const isLeftLeaning = this.balance(node) >= 0;
  const nodes = isLeftLeaning
    ? // node to remove is left leaning, so search left subtree
      this.maxAt(this.node(node.left)!, node)
    : // node to remove is right leaning, so search right subtree
      this.minAt(this.node(node.right)!, node);

  const successor = nodes.child;

  // node to remove and parentNode can be the same node on small trees (2 levels, 2-3 nodes)
  // if so, make parentNode point to node
  parentNode = nodes.parent.id === node.id ? node : nodes.parent;

  const successorIsLeftChild = parentNode.left
    ? parentNode.left!.val === successor.id
    : false;

  // remove successor from its parent, and link the successor's child to its grandparent
  if (successorIsLeftChild) {
    parentNode.left = isLeftLeaning ? successor.left : successor.right;
  } else {
    parentNode.right = isLeftLeaning ? successor.left : successor.right;
  }

  // take successor's key, and update the node to remove
  node.key = successor.key;
  this._tree[node.id] = node;
  successorId = new Nullable(node.id);

  // set node to point to successor, so it is removed from the tree
  node = successor;
}

this.updateHeight(parentNode);
this.swapRemove(node);

return this.size > 0 && this.rootNode
  ? new Nullable(this.rebalanceAt(this.rootNode!, parentNode.key).id)
  : successorId;

}

// removes the given node from the tree,
// and replaces it with the last node in the underlying array (this._tree)
private swapRemove(node: AVLTreeNode): void {
if (node.id === this.size - 1) {
// node is last element in tree, so no swapping needed
if (node.id === this.rootId!.val) {
this.rootId = null;
}
} else {
const lastNode = this._tree[this.size - 1];
const parentNode = this.lookupAt(this.rootNode!, lastNode.key).parent;

  if (lastNode.id === this.rootId!.val) {
    this.rootId = new Nullable(node.id);
  }

  // check to make sure that parentNode and lastNode do not overlap
  if (parentNode.id !== lastNode.id) {
    // make lastNode's parent point to new index (index of node that lastNode is replacing)
    if (parentNode.left ? parentNode.left!.val === lastNode.id : false) {
      parentNode.left = new Nullable(node.id);
    } else {
      parentNode.right = new Nullable(node.id);
    }

    // update the parentNode
    this._tree[parentNode.id] = parentNode;
  }

  // update index of lastNode
  lastNode.id = node.id;
  this._tree[lastNode.id] = lastNode;
}

this._tree.pop();

}

// given a starting node
// returns the leftmost (min) descendant node, and its parent
private minAt(
root: AVLTreeNode,
parentNode: AVLTreeNode | null = null
): ChildParentPair {
return root.left
? this.minAt(this.node(root.left)!, root)
: new ChildParentPair(root, parentNode ? parentNode : root);
}

// given a starting node
// returns the rightmost (max) descendant node, and its parent
private maxAt(
root: AVLTreeNode,
parentNode: AVLTreeNode | null = null
): ChildParentPair {
return root.right
? this.maxAt(this.node(root.right)!, root)
: new ChildParentPair(root, parentNode ? parentNode : root);
}

// given a key and a starting node
// returns the node with the associated key, as well as its parent (if it exists)
// caution: this method assumes the key exists in the tree, and will throw otherwise
private lookupAt(
root: AVLTreeNode,
key: K,
parentNode: AVLTreeNode | null = null
): ChildParentPair {
return root.key === key
? new ChildParentPair(root, parentNode ? parentNode : root)
: key < root.key
? this.lookupAt(this.node(root.left)!, key, root)
: this.lookupAt(this.node(root.right)!, key, root);
}

// recursively updates the height of each node in the tree,
// and performs rotations as needed from bottom to top
// to maintain the AVL tree balance invariant
private rebalanceAt(root: AVLTreeNode, key: K): AVLTreeNode {
if (root.key > key) {
const leftChild = this.node(root.left);
if (leftChild) {
root.left = new Nullable(this.rebalanceAt(leftChild, key).id);
}
} else if (root.key < key) {
const rightChild = this.node(root.right);
if (rightChild) {
root.right = new Nullable(this.rebalanceAt(rightChild, key).id);
}
}

this.updateHeight(root);

return this.enforceBalance(root);

}

// recursively checks that each node in the tree is balanced by checking that
// the AVL tree invariant holds:
// any node's left and right subtrees must have a difference in height <= 1
private isBalanced(root: AVLTreeNode | null = this.rootNode): bool {
const b = root ? this.balance(root) : 0;
return b >= -1 && b <= 1 && root
? this.isBalanced(this.node(root.left)) &&
this.isBalanced(this.node(root.right))
: true;
}
}

                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

in ~lib/near-sdk-core/collections/avlTree.ts(294,23)

Expected behavior
A clear and concise description of what you expected to happen.

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • OS: [e.g. iOS]
  • Browser [e.g. chrome, safari]
  • Version [e.g. 22]

Alternatively, please attach your package.json file here for more detailed versioning
{
"name": "near",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"dotenv": "^8.2.0",
"js-sha256": "^0.9.0",
"near-api-js": "^0.35.0",
"near-sdk-as": "^3.0.0-alpha.0"
}
}

Unable to persist @nearBindgen classes with static properties

Describe the bug

Compiler error when attempting to persist a @nearBindgen class that has a static property. Can't say for sure that this is a bug as I'm not sure what the expected behavior should be -- either ignoring static properties or storing seperately.

To Reproduce

Attempt to persist, via persist(this) or storage.set("KEY", this), a @nearBindgen'd class that has a static property.

import { persist } from 'near-sdk-as';

@nearBindgen
class Contract {
  static account: string = "contract.near"; 
}

export function init(): void {
  let c = new Contract();
  persist(c);
}

Expected behavior

Unclear what expected behavior should be.

Screenshots

> [email protected] build
> asb

ERROR TS2339: Property 'account' does not exist on type 'assembly/index/BaseContract'.

     this.account = obj.has("account") ? decode<string, JSON.Obj>(obj, "account"): "contract.near";
          ~~~~~~~
 in assembly/index.ts(25,10)

ERROR TS2339: Property 'account' does not exist on type 'assembly/index/BaseContract'.

     encode<string, JSONEncoder>(this.account, "account", encoder);
                                      ~~~~~~~
 in assembly/index.ts(32,38)

Desktop (please complete the following information):

  • OS: Mac OS 11.1, Arm64
  • Version [e.g. 22]: 2.2.0

Move assemblyscript to dependencies

Assertions:

  • Projects don't need it: Having both near-sdk-as and assemblyscript as separate dependencies in a project provides flexibility that, for most projects, is a hassle without any benefit
  • near-sdk-as suffers: Updating near-sdks-as in a way that gives projects this flexibility adds complexity and slows down its evolution

Projects don't need it

Right now, many NEAR apps have separate dependabot-created PRs to update near-sdk-as & assemblyscript. The assemblyscript update came first, and introduced bugs (https://github.com/near-examples/guest-book/pull/133) unless the project waits for a subsequent near-sdk-as update and uses it first.

I can think of a couple situations where projects would want to have a version of assemblyscript that mismatches the one used in near-sdk-as:

  1. One contract written with near-sdk-as, another written with raw assemblyscript for performance reasons
  2. A contract written with near-sdk-as, and some other aspect of the project (such as the frontend) built with assemblyscript

In both situations, if near-sdk-as came packaged with its own version of assemblyscript that mismatched what the project needed elsewhere, the project could easily work around that by using yarn workspaces, lerna, or git submodules.

My guess is that most existing projects that use near-sdk-as are not including assemblyscript for any other reason. If that's true (how can we test this assumption?), then an upgrade to near-sdk-as that requires removing assemblyscript from package.json would be a simple and welcome breaking change.

near-sdk-as suffers

As mentioned in https://github.com/near-examples/guest-book/pull/133#issuecomment-637619401, we could add assemblyscript to peerDependencies for near-sdk-as. This would be a great way to give projects the flexibility to update assemblyscript at a different rate than they update near-sdk-as.

However, it comes with costs. near-sdk-as would need to make sure changes are backwards-compatible for several versions of assemblyscript. This complicates the code, and means that it takes longer to offer new, powerful features of assemblyscript, such as interfaces and abstract methods.

If it's true that most projects do not need this flexibility, then this slower evolution comes with no upside.

Error in env.validator_stake(accountName)

Describe the bug
There appears to be an error in env/env.ts#validator_stake(accountId:string) that causes a compile-time error.

To Reproduce
Compile the following code with asb:

import {env, logging, context} from 'near-sdk-as';

export function foo():void {
  logging.log(env.validator_stake("something.near"));
}

The observed output is:

> [email protected] build:env_test
> asb assembly/env_test.ts

ERROR AS202: Type '~lib/arraybuffer/ArrayBuffer' cannot be changed to type 'u64'.

     let id_ptr = changetype<u64>(id);
                  ~~~~~~~~~~~~~~~~~~~
 in ~lib/near-sdk-core/env/env.ts(337,18)

Expected behavior
It compiles.

Screenshots
Issue appears to be with the unsafechangetype<u64>

export function validator_stake(account_id: string): u128 {
    let data = new Uint8Array(sizeof<u128>());
    let id = String.UTF8.encode(account_id);
    let id_ptr = changetype<u64>(id);
    _validator_stake(id.byteLength, id_ptr, data.dataStart);
    return u128.from(data);
  }

Desktop (please complete the following information):

  • OS: osx 11.1
  • Version: near-sdk-as 2.2.0

Export snake case function names

Per near/NEPs#96 meta standard update, all the function names should be snake case. SDKs and APIs on both side should normalize to whatever is the language.

This should allow still to write public export functions in camelCase per JS standard but AS transformer should either replace or better add snake case export functions.

Add support for Darwin arm64

Describe the bug
getBinary() in near-vm throws an error if run on M1 / Arm mac os. This exception is not necessary; near-vm-runner-standalone 1.1.0 runs fine on ARM macs.

To Reproduce
Run yarn install from any project that imports near-sdk-as on an m1 mac.

Expected behavior
It compiles.

Screenshots

$ yarn install
. . . 
near-vm/getBinary.js:17
throw new Error(`Unsupported platform: $

{type} 

$

{arch} 

`);
^

Error: Unsupported platform: Darwin arm64

Desktop (please complete the following information):

  • OS: Mac 11+, M1 / ARM mac mini
  • Version <= 2.2.0

Windows issues - add windows to near-sdk-as ci

At the time of this writing, two examples give the following error while running tests:

Internal discussions with Willem ended with the conclusion:

I guess I'll have to virtualize windows.

 The command "yarn build" exited with 0.
 3.14s$ yarn test
 yarn run v1.22.4
 $ yarn asp && yarn jest
 $ asp --verbose
 ___ _____ __ 
 / | / ___/ ____ ___ _____/ /_ 
 / /| | __ ______/ __ \/ _ \/ ___/ __/ 
 / ___ |___/ /_____/ /_/ / __/ /__/ /_ 
 /_/ |_/____/ / .___/___/___/__/ 
 /_/ 
 โšกAS-pectโšก Test suite runner <span class="error">[3.1.4]</span>
 <span class="error">[Log]</span> Loading asc compiler
 ERROR: Import 'index' not found.
 at parseBacklog (C:\Users\travis\build\near-examples\token-contract-as\node_modules\assemblyscript\cli\asc.js:498:34)
 at main (C:\Users\travis\build\near-examples\token-contract-as\node_modules\assemblyscript\cli\asc.js:566:16)
 at Object.main (C:\Users\travis\build\near-examples\token-contract-as\node_modules\near-bindgen-as\node_modules\near-sdk-as\bindgen\compiler.js:76:12)
 at module.exports.compile (C:\Users\travis\build\near-examples\token-contract-as\node_modules\near-bindgen-as\node_modules\near-sdk-as\bindgen\compiler.js:98:7)
 at Object.<anonymous> (C:\Users\travis\build\near-examples\token-contract-as\asconfig.js:3:1)
 at Module._compile (internal/modules/cjs/loader.js:1158:30)
 at Object.Module._extensions..js (internal/modules/cjs/loader.js:1178:10)
 at Module.load (internal/modules/cjs/loader.js:1002:32)
 at Function.Module._load (internal/modules/cjs/loader.js:901:14)
 at Function.executeUserEntryPoint <span class="error">[as runMain]</span> (internal/modules/run_main.js:74:12)
 warning package.json: No license field
 warning package.json: No license field
 <span class="error">[Log]</span> Compiler loaded in 551.034ms.
 <span class="error">[Log]</span> Using configuration C:\Users\travis\build\near-examples\token-contract-as\as-pect.config.js
 <span class="error">[Log]</span> Using VerboseReporter
 <span class="error">[Log]</span> Including files: assembly/__tests__/***/**.spec.ts
 <span class="error">[Log]</span> Running tests that match: (:?)
 <span class="error">[Log]</span> Running groups that match: (:?)
 <span class="error">[Log]</span> Effective command line args:
 <span class="error">[TestFile.ts]</span> node_modules@as-pect\cli\node_modules@as-pect\assembly\assembly\index.ts C:\Users\travis\build\near-examples\token-contract-as\node_modules\wasm-mock-vm\assembly\entry.ts --validate --debug --binaryFile output.wasm --runtime stub --baseDir C:\Users\travis\build\near-examples\token-contract-as --runPasses inlining,dce --transform near-bindgen-as,C:\Users\travis\build\near-examples\token-contract-as\node_modules@as-pect\cli\node_modules@as-pect\core\lib\transform\index.js --explicitStart --use ASC_RTRACE=1 --exportTable --importMemory
 info Visit [https://yarnpkg.com/en/docs/cli/run](https://yarnpkg.com/en/docs/cli/run) for documentation about this command.
 info Visit [https://yarnpkg.com/en/docs/cli/run](https://yarnpkg.com/en/docs/cli/run) for documentation about this command.
 The command "yarn test" exited with 1.

u128 division not working for some big number

Describe the bug
u128 division not working as expected for number bigger than 300 N (300 * 10^24).
Used to calculate commission/royalty fee.

To Reproduce
Steps to reproduce the behavior:

const mul = u128.mul(
u128.from('5000000000000000000000000000'),
u128.from(95)
)
log(mul.toString()) // true return 475000000000000000000000000000

const div100 = u128.div(mul, u128.from(100))
log(div100.toString()) // false return 0

const _div10 = u128.div10(mul)
const _div100 = u128.div10(_div10)
log(_div100.toString()) // true return 4750000000000000000000000000

Expected behavior
u128 division to return expected calculated value

[Bounty] Add includeBytes function

NEAR Bounty Terms

Before beginning work on the bounty, you must submit a proposal. Only if your proposal is accepted will you be able to claim the reward of the bounty.

Description

Add includeBytes function in AssemblyScript, similar to Rust's include_bytes, which during compilation reads the contents of a file and places it into the data section of the binary.

Context

AssemblyScript recently added a StaticArray, which is similar to arrays in C, which includes the length and continuous bytes.

Relevant repos or issues

The tools needed are located in the visitor-as package as it contains the transformer and a SimpleParser. See FunctionCallTransform for an example of how to replace a function call.

API

// add to as_types.d.ts
declare function includeBytes(path: string): StaticArray<u8>;

Acceptance Criteria

  • Create a new transform visitor that will handle converting the function into as StaticArray<u8> of the data in the file.
  • Tests in as-pect

Bounty

300 DAI

Error calling validator_stake from contract

Unexpected error from validator_stake method in env.ts on build: Type ArrayBuffer cannot be converted to type 'u64' - in line 337 of near-sdk-core/env/env.ts

To Reproduce

  1. Write function within contract which calls env.validator_stake(account_id: string), passing in a string
  2. Attempt to build

Expected behavior
Should return stake as type u128

Version:
near-sdk-as: 2.2.4

storage.set - i32 can't be converted to u8

Have tried storage.set('precision', precision) where precision passed in is typed as u8. Get an error during compile saying i32 can't be converted to u8.

I know I can import u128 from near-sdk-as and use that, but seems like overkill for a small positive number.

Get same compiler error with u16.

Peter | Near Protocol thinks I've run into an issue that storage.set doesn't support "small integer* types" from AssemblyScript - asked me to submit this issue.

Does not work with npm

Describe the bug
Adding near-sdk-as to a project currently fails when using npm (it works with yarn)

 โฎ€ npm i -S near-sdk-as

> [email protected] preinstall /Users/chadoh/code/proj/node_modules/near-vm
 > node ./uninstall.js

npm WARN [email protected] requires a peer of assemblyscript@^0.9.4 but none is installed. You must install peer dependen
 cies yourself.
 npm WARN [email protected] requires a peer of assemblyscript@^0.13.3 but none is installed. You must install peer dependencies
 yourself.
 npm WARN @as-pect/[email protected] requires a peer of assemblyscript@^0.12.0 but none is installed. You must install peer dependencie
 s yourself.
 npm WARN @as-pect/[email protected] requires a peer of assemblyscript@^0.12.0 but none is installed. You must install peer depend
 encies yourself.
 npm WARN @as-pect/[email protected] requires a peer of assemblyscript@^0.12.0 but none is installed. You must install peer dependenci
 es yourself.
 npm WARN [email protected] No description
 npm WARN [email protected] No repository field.

npm ERR! code ENOENT
 npm ERR! syscall chmod
 npm ERR! path /Users/chadoh/code/proj/node_modules/asbuild/bin/asp
 npm ERR! errno -2
 npm ERR! enoent ENOENT: no such file or directory, chmod '/Users/chadoh/code/proj/node_modules/asbuild/bin/asp'
 npm ERR! enoent This is related to npm not being able to find a file.
 npm ERR! enoent

To Reproduce
Steps to reproduce the behavior:
1. initialize a new project with npm init. I used Node 12.
2. Run npm i -S near-sdk-as

Expected behavior
near-sdk-as should always work with npm in addition to yarn

Screenshots

Screen Shot 2020-12-15 at 20 19 29

Cannot serialize Map

I have attempted to create a simple version of a PersistentOrderedMap in https://github.com/near-examples/guest-book/pull/124

I have this call in my frontend:

contract.getMessages().then(setMessages)

Here's getMessages in main.ts:

export function getMessages(): Map<string, Message> {
  return messages.last(MESSAGE_LIMIT)
}

Here's messages in models.ts:

export const messages = new PersistentOrderedMap<string, Message>('messages')

And finally, here's last in PersistentOrderedMap:

last(count: i32): Map<K, V> {
  const n = min(count, this.length)
  const startIndex = this.length - n
  const result = new Map<K, V>()
  for (let i = startIndex; i < this.length; i++) {
    const key = this._keys[i]
    result.set(key, this._map.getSome(key))
  }
  return result
}

You can currently see in the integration tests that rather than returning an object to the frontend, this returns false:

    Expected: {"1": {"premium": false, "sender": "test1590864637142", "text": "aloha"}}
    Received: false

Implement AVL Tree for near-sdk-as

An AVL tree is a balanced binary tree 1, which swaps nodes in the tree as elements are inserted and deleted in order to maintain the minimum height of the tree. This ensures that the worst case for accessing an element is O(log n), versus O(n) for a normal binary tree.

Currently smart contracts on NEAR provide a Key-Value storage but does not provide an API for accessing/iterating over the keys. Thus this data structure can be used to create an ordered map by inserting into the tree keys as values and their key as an increasing counter of all entries.

PersistentOrderedMap provides an example of iterating over the last n elements, but to maintain order it only allows the last entry to be deleted.

Please use an equivalent implementation of AVL Map in Rust for guidance: near/near-sdk-rs#154 It is okay to literally rewrite the above Rust code in AssemblyScript.

API

class AVLTree<K,V> {

  /**
   * A string name is used as a prefix for writing keys to storage.
   */
  constructor(name: string){}

  /**
   * Number of elements in the tree.
   */
  get size(): u32;

  /**
   * Returns whether the key is present in the tree.
   */
  has(key: K): boolean;
  //alias to match rust sdk
  containsKey(key: K): boolean;

  /**
   * If key is not present in the tree, a new node is added with the value.
   * Otherwise update the node with the new value.
   */
  set(key: K, value: V): void;
  //alias to match rust sdk
  insert(key: K, value: V): void;

  /**
   * Get value with key and throw if the key is not in the tree.
   */
  get(key: K): V;

  /**
   * Get value with key and return default value if key is not in the tree.
   */
  getSome(key: K, default: V): V;

  /**
   * Delete element with key if present, otherwise do nothing.
   */
  delete(key: K): void;
  //alias to match rust sdk
  remove(key: K): void;


  /**
   * Get a range of values from a start key to an end key exclusive.
   * If end is greater than max key, include start to max inclusive.
   */
  range(start: K, end: K): V[];

  /**
   * Returns minimum key.
   * Throws if tree is empty.
   */
  min(): K;

  /**
   * Returns maximum key.
   * Throws if tree is empty.
   */
  max(): K;

  /**
   * Returns the minimum key that is strictly greater than the key.
   * Throws if empty or if key is lower than `this.min()`.
   */
  lower(key: K): K;

  /**
   * Returns the maximum key that is strictly less than the key.
   * Throws if empty or if key is higher than `this.max()`.
   */
  higher(key: K): K;

  /**
   * Returns the minimum key that is greater or equal than the key.
   * Throws if empty or if key is lower than `this.min()`.
   */
  lowerOrEqual(key: K): K;

  /**
   * Returns the maximum key that is less or equal than the key.
   * Throws if empty or if key is higher than `this.max()`.
   */
  higherOrEqual(key: K): K;
}

Implementation

Other than the API above and the requirement for the same algorithmic characteristics of a AVL tree, the implementation is open to interpretation.

Testing

There must be tests that cover each edge case. Use PersistentSet as a guide for how to write tests.

Please make sure the implementation contains all tests of AVL tree that were implemented in near/near-sdk-rs#154

using floats in @nearBindgen classes causes compilation error

Describe the bug

Whenever a @nearBindgen class has a f32 or f64 property without a default value causes a compilation error.

Error

To Reproduce

@nearBindgen
class Foo {
  someFloat: f32
}

Expected behavior

Use zero as the default value if none is provided (similar to u64 for example).

Screenshots

Some Code

Desktop (please complete the following information):

  • OS: Linux

does not work with npm@7

NodeJS 15 ships with [NPM 7](https://blog.npmjs.org/post/617484925547986944/npm-v7-series-introduction). Perhaps the biggest change in NPM 7 is that it automatically installs peerDependencies. If peerDependencies are well-specified this causes no issues, but it seems that the peerDependencies within the near-sdk-as packages are not quite correct.

on 'master' branch of 'near-sdk-as', running the command 'rm -rf node_modules package-lock.json yarn.lock && npm i' results in errors 'ERESOLVE unable to resolve dependency tree'

    1. To Reproduce
      As shown in the screenshot above:

1. install nodejs 15, which comes with npm 7
2. check out the latest master branch of near-sdk-as
3. remove yarn.lock and node_modules
4. run npm install

This is the fastest way to reproduce this issue, but note that the issue also arises for projects that depend on near-sdk-as. See the same error as shown in the screenshot above in the output from a fresh project created with create-near-app here: near/create-near-app#533

I've worked around the problem in create-near-app by adding npm install --legacy-peer-deps to the command: near/create-near-app@38020ab

Once we address the bug here in near-sdk-as, we can get rid of this flag.

Function_Call results in AssertionError/Unexpected Upcast

Describe the bug
Having a hard time getting function_call to work with AS. Trying to call from contract results in an assertion failed and unexpected upcast errors regardless of how the arguments are passed in.

To Reproduce
Steps to reproduce the behavior:
1. Create a function_call in a contract such as:

promise.function_call(
'init',
args,
u128.Zero,
env.prepaid_gas() - CREATE_CALL_GAS
)

2. Pass in some args either from frontend like:
`const argsList =

{ "purpose": purpose.value, "council": council.value.split('\n'), "bond": utils.format.parseNearAmount(bond.value), "vote_period": (parseFloat(votePeriod.value) * 3600 * 1000000000).toFixed().toString(), "grace_period": (parseFloat(gracePeriod.value) * 3600 * 1000000000).toFixed().toString() }

`

or just add some args in the contract such as:

` ContractPromiseBatch.create(context.contractName).function_call(
"tick",
"

{"counter":"10"}

",
u128.Zero,
context.prepaidGas - context.usedGas - RESERVE_GAS
)`

3. Will fail with AssertionError and unexpected upcast error

Expected behavior
Expecting the function call to work.

Additional context
Additional information from Discord - user: theophoric

`import

{ ContractPromiseBatch, context, logging, u128 }

from 'near-sdk-as';
const RESERVE_GAS = 10_000_000_000_000;
@nearBindgen
export class Timer {

tick(counter: u32 = 1): void

{ // return if there is not enough gas to process -- instead of running out logging.log("tick: " + counter.toString()); this.tock(counter + 1); }

private tock(counter: u32): void {
// return if
logging.log("tock: " + counter.toString());
counter++;
ContractPromiseBatch.create(context.contractName).function_call<

{counter: u32}>(

"tick",

{counter}

,
u128.Zero,
context.prepaidGas - context.usedGas - RESERVE_GAS
)
}
}`

compiling that outputs pages of minified JS and then:
`...

Error [AssertionError]: assertion failed
at i.assert (/Users/theophoric/Development/NEAR/theophoric/near-boilerplate-as/contract/node_modules/assemblyscript/dist/assemblyscript.js:7:701252)
at h.resolveFunction (/Users/theophoric/Development/NEAR/theophoric/near-boilerplate-as/contract/node_modules/assemblyscript/dist/assemblyscript.js:7:635274)
at T.compilePropertyAccessExpression (/Users/theophoric/Development/NEAR/theophoric/near-boilerplate-as/contract/node_modules/assemblyscript/dist/assemblyscript.js:7:326466)
at T.compileExpression (/Users/theophoric/Development/NEAR/theophoric/near-boilerplate-as/contract/node_modules/assemblyscript/dist/assemblyscript.js:7:238203)
at T.compileBinaryExpression (/Users/theophoric/Development/NEAR/theophoric/near-boilerplate-as/contract/node_modules/assemblyscript/dist/assemblyscript.js:7:242966)
at T.compileExpression (/Users/theophoric/Development/NEAR/theophoric/near-boilerplate-as/contract/node_modules/assemblyscript/dist/assemblyscript.js:7:237442)
at T.compileBinaryExpression (/Users/theophoric/Development/NEAR/theophoric/near-boilerplate-as/contract/node_modules/assemblyscript/dist/assemblyscript.js:7:243584)
at T.compileExpression (/Users/theophoric/Development/NEAR/theophoric/near-boilerplate-as/contract/node_modules/assemblyscript/dist/assemblyscript.js:7:237442)
at T.compileExpressionStatement (/Users/theophoric/Development/NEAR/theophoric/near-boilerplate-as/contract/node_modules/assemblyscript/dist/assemblyscript.js:7:224985)
at T.compileStatement (/Users/theophoric/Development/NEAR/theophoric/near-boilerplate-as/contract/node_modules/assemblyscript/dist/assemblyscript.js:7:220824)`

[message.txt](https://github.com/near/near-sdk-as/files/6061093/message.txt)

Changing the function call in tock to the following:

ContractPromiseBatch.create(context.contractName).function_call<u32>( "tick", 0, u128.Zero, context.prepaidGas - context.usedGas - RESERVE_GAS )

allows it to compile but throws the following error on contract execution:

Log <span class="error">[dev-1614111710072-2942979]</span>: ABORT: unexpected upcast, filename: "~lib/near-sdk-bindgen/index.ts" line: 71 col: 20 Failure <span class="error">[dev-1614111710072-2942979]</span>: Error: Smart contract panicked: unexpected upcast, filename: "~lib/near-sdk-bindgen/index.ts" line: 71 col: 20

using a json encoding of the arguments has the same effect:

`private tock(counter: u32): void {
// return if
logging.log("tock: " + counter.toString());
counter++;
ContractPromiseBatch.create(context.contractName).function_call(
"tick",
"

{"counter":"10"}

",
u128.Zero,
context.prepaidGas - context.usedGas - RESERVE_GAS
)
}`

exported functions can not have default parameter values

this error is raised when calling the customize function below because this is the entry function to the contract

Scheduling a call: dev-1586182981781.customize()
Error:  TypedError: Transaction FCpyq6UJS73rzEHS9jURr4dcF97SqFLmSdZy1YktS5zR failed. 
        String encoding is bad UTF-16 sequence.

from @willemneal

The exported functions cannot have default values. You could just make a default function that calls the customize function. If you add //@nearfile out at the top the file, when compiling you can see the code output that is about to compiled. Every exported function getโ€™s a helper __wrapper function that deserializes the inputs and then calls the function. As of now this wrapper function requires that you have all arguments in the JSON Object.

below is the function that throws the error

/**
 * This function supports the customization of this ERC-20 token before initialization
 * It may or may not be called.  If not called, the contract uses sensible defaults
 * If called, it can only be called once (!) and prevents repeat calls
 *
 * NOTE: this function uses storage keys with _underscore prefix since these are guaranteed not
 * to conflict with accounts on the NEAR platform. see https://nomicon.io/DataStructures/Account.html#examples
 *
 * THIS IS NOT part of the ERC-20 spec
 *
 * @param name of the token
 * @param symbol for the token
 * @param decimals places used when rendering the token
 * @param supply of the tokens in total at launch
 */
export function customize(
  name: string = "Solidus Wonder Token",        // awesome name for a token
  symbol: string = "SWT",                       // pronounced "sweet", rhymes with "treat"
  decimals: u8 = 2,                             // number of decimal places to assume for rendering,
  supply: u128 = u128.from(100_000_000),        // <raised pinky> one meeeeellion coins ... divisible in 100ths,
  exchangeRate: u8 = 100                        // of these ERC-20 tokens per NEAR token
  ) : void {
  DEBUG ? logging.log("[call] customize('" + name + "', '" + symbol + "', " + decimals.toString() + ", " + supply.toString() + ", " + exchangeRate.toString() + ")") : false;
  const owner = assertTrueOwner()
  // only set values that are provided, otherwise ignore
  storage.setString("_bank", owner);
  storage.setString("_name", name);
  storage.setString("_symbol", symbol);
  storage.set<u8>("_decimals", decimals);
  storage.set<u128>("_totalSupply", supply);
  storage.set<u8>("_exchangeRate", exchangeRate);
}

persistentVector.replace always throws index out of bounds

Describe the bug
Originally described in Discord - https://discordapp.com/channels/490367152054992913/710764455398735906/759545682784026646

To Reproduce

  1. Create a persistentVector (vec) and add something to it
  2. Attempt to use vec.replace(0, new element)
  3. Will throw an index out of bounds error.

I believe it's because of this:
replace(index: i32, new_element: T): T { assert(index >= this.length, "Index out of bounds"); let evicted = this.__unchecked_get(index); storage.set(this._key(index), new_element); return evicted; }

I think that assert statement will always trigger because the vector length will always be > the index passed. Think maybe it should read assert(index < this.length, "Index out of bounds");

Expected behavior
I expect to be able to replace the element at the identified index with a new element.

Add batch storage operations

Some of our users requested to have a way to copy, move or remove large volumes of storage values bypassing encoding/decoding or even copying them into Wasm memory. This can be useful e.g. when they want to move all elements from one collection into another. This can be done using registers as discussed here: #44 which should make it much faster and cost fewer gas.

Error while compiling AssemblyScript in near-sdk-core

Describe the bug
Error is thrown in build time inside near-sdk-core package.

To Reproduce
Easily reproduced in FT example provided on near-examples
https://github.com/near-examples/FT

  1. build using npm run build:as
  2. observe results in terminal

Expected behavior
Build should not fail in SDK library, I guess :)

Additional context
I am just escalating the issue reported on my behalf in near-examples/FT repository since it seems to me that issue may not be in example code at all.

Terminal output:

yarn build:as
yarn run v1.22.5
$ ./contracts/assemblyscript/build.js

compiling contract [ nep21-basic/main.ts ] to [ out/nep21-basic-as.wasm ]
 asconfig.js is deprecated use `asb`to build files.  NOTE: output now in ./build/release/ 
asc /Users/jlwaugh/Desktop/FT/contracts/assemblyscript/nep21-basic/main.ts -O3z --debug --measure --runPasses inlining-optimizing,dce --optimizeLevel 3 --shrinkLevel 3 --target release --binaryFile build/release/main.wasm
ERROR TS2304: Cannot find name 'encode'.

       this.setBytes(key, encode<T>(value));
                          ~~~~~~
 in ~lib/near-sdk-core/storage.ts(158,26)

ERROR TS2304: Cannot find name 'decode'.

     return decode<T>(bytes);
            ~~~~~~
 in ~lib/near-sdk-core/util.ts(46,12)

/Users/jlwaugh/Desktop/FT/node_modules/near-sdk-bindgen/compiler.js:19
        throw new Error(err);
        ^

Error: Error: 2 compile error(s)
    at cb (/Users/jlwaugh/Desktop/FT/node_modules/near-sdk-bindgen/compiler.js:19:15)
    at Object.main (/Users/jlwaugh/Desktop/FT/node_modules/assemblyscript/cli/asc.js:717:12)
    at compileProject (/Users/jlwaugh/Desktop/FT/node_modules/asbuild/dist/cli.js:191:9)
    at main (/Users/jlwaugh/Desktop/FT/node_modules/asbuild/dist/cli.js:92:9)
    at module.exports.compile (/Users/jlwaugh/Desktop/FT/node_modules/near-sdk-bindgen/compiler.js:42:3)
    at compileContract (/Users/jlwaugh/Desktop/FT/contracts/assemblyscript/build.js:23:3)
    at Array.map (<anonymous>)
    at Object.<anonymous> (/Users/jlwaugh/Desktop/FT/contracts/assemblyscript/build.js:6:16)
    at Module._compile (internal/modules/cjs/loader.js:1015:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1035:10)
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Provide test/example for promise_batch_action_* calls

Use case
As I user, I want to use the methods listed around here:

@external("env", "promise_batch_action_transfer")

in order to make batch promise calls. I would like to take an example from either a test (probably simulation test in this case) that uses batch promises, or have some examples in comments to model after.

Acceptance Criteria
Code is available in near-sdk-as in a way that a user can copy/paste and modify batch promise calls in order to use in their own smart contract.

[Bounty] Add String Interpolation to AssemblyScript

NEAR Bounty Terms

Before beginning work on the bounty, you must submit a proposal. Only if your proposal is accepted will you be able to claim the reward of the bounty.

Background

A key feature in AssemblyScript that many developers expect is string interpolation, or a string with special variables that are inlined.

For example,

let world = "world";
let str = `Hello ${world}`

Work on this has located in the following PR: AssemblyScript/assemblyscript#1115

You will make a new PR against this PR. This way we can approve it.

As github points out at the bottom of the PR, there are merge conflicts with the current master branch. Thus the first step is to merge with master.

API

As mentioned here, there needs to be new AST node added for template strings:

Adding the following to src/ast.ts

/** Represents a string literal expression. */
export class TemplateLiteralExpression extends LiteralExpression {
  constructor(
    /** String value without quotes. */
    public expresssionParts: Expression[],
    /** Source range. */
    range: Range
  ) {
    super(LiteralKind.TEMPLATE, range);
  }
}

Then update the following in the src/parser.ts to return the new TemplateLiteralExpression.

Update the following method in the Node class in src/ast.ts

static createTemplateLiteralExpression(
    value: string,
    range: Range
  ): StringLiteralExpression {
    return new TemplateLiteralExpression(parts, range);
  }

And the following to src/compiler.ts

compileTemplateLiteral(expr: TemplateLiteralExpression, constraints: Constraints): ExpressionRef {
    const innerExpressions: ExpressionRef[] = expr.expressionParts;
    ....

  }

As also mentioned in the PR, expr.toString() should be compiled for each part and then each must be concatenated.

Testing

Add to tests already present in the PR to test more edge cases.

Proposal

Briefly describe how you would design complieTemplateLiteral and some edge test cases you would add.

Change name for Context

There are two name to import: context and Context. After I read the repo, I can know which one is which and use it differently. But their name is only one capital letter difference, which is really not friendly used. I see many file use Context as VMContext. Can we just change to VMContext or other better name?

Cannot find name 'encode' in near-sdk-core/promise.ts

cc @theophoric

when compiling Staking Pool contract, an AssemblyScript port of the NEAR core contract written in Rust and having same name, this error is observed:

ERROR TS2304: Cannot find name 'encode'.

   return encode<T>(t);
          ~~~~~~
 in ~lib/near-sdk-core/promise.ts(9,10)

adding these imports and functions to near-sdk-core/promise.ts resolves the issue

import { JSONEncoder, JSON } from "assemblyscript-json";

function isNull<T>(t: T): bool {
  if (isNullable<T>() || isReference<T>()) {
    return changetype<usize>(t) == 0;
  }
  return false;
}

function encode<T, Output = Uint8Array>(
  value: T,
  name: string | null = "",
  encoder: JSONEncoder = new JSONEncoder()
): Output {
  if (isBoolean<T>()) {
    // @ts-ignore
    encoder.setBoolean(name, value);
  } else if (isInteger<T>()) {
    if (value instanceof i64 || value instanceof u64) {
      // @ts-ignore
      encoder.setString(name, value.toString());
    } else {
      // @ts-ignore
      encoder.setInteger(name, value);
    }
  } else if (isString<T>()) {
    if (isNull<T>(value)) {
      encoder.setNull(name);
    } else {
      // @ts-ignore
      encoder.setString(name, value);
    }
  } else if (isReference<T>()) {
    // @ts-ignore
    if (isNull<T>(value)) {
      encoder.setNull(name);
    } else {
      // @ts-ignore
      if (isDefined(value._encode)) {
        if (isNullable<T>()) {
          if (value != null) {
            // @ts-ignore
            value._encode(name, encoder);
          } else {
            encoder.setNull(name);
          }
        } else {
          // @ts-ignore
          value._encode(name, encoder);
        }
      } else if (isArrayLike<T>(value)) {
        if (value instanceof Uint8Array) {
          // @ts-ignore
          encoder.setString(name, base64.encode(<Uint8Array>value));
        } else {
          encoder.pushArray(name);
          for (let i: i32 = 0; i < value.length; i++) {
            // @ts-ignore
            encode<valueof<T>, JSONEncoder>(value[i], null, encoder);
          }
          encoder.popArray();
        }
      } else {
        // Is an object
        if (value instanceof u128) {
          // @ts-ignore
          encoder.setString(name, value.toString());
        } else if (value instanceof Map) {
          assert(
            // @ts-ignore
            nameof<indexof<T>>() == "String",
            "Can only encode maps with string keys"
          );
          let keys = value.keys();
          encoder.pushObject(name);
          for (let i = 0; i < keys.length; i++) {
            // @ts-ignore
            encode<valueof<T>, JSONEncoder>(
              value.get(keys[i]),
              keys[i],
              encoder
            );
          }
          encoder.popObject();
        } else if (value instanceof Set) {
          // @ts-ignore
          let values: Array<indexof<T>> = value.values();
          encoder.pushArray(name);
          for (let i = 0; i < values.length; i++) {
            // @ts-ignore
            encode<indexof<T>, JSONEncoder>(values[i], null, encoder);
          }
          encoder.popArray();
        }
      }
    }
  } else {
    throw new Error(
      "Encoding failed " +
      (name != null && name != "" ? " for " + name : "") +
      " with type " +
      nameof<T>()
    );
  }
  var output: Output;
  // @ts-ignore
  if (output instanceof Uint8Array) {
    // @ts-ignore
    return <Output>encoder.serialize();
  }
  assert(
    // @ts-ignore
    output instanceof JSONEncoder,
    // @ts-ignore
    "Bad return type " + nameof < Output > +" for encoder"
  );
  // @ts-ignore
  return <Output>encoder;
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.