Skip to content
This repository was archived by the owner on Jun 23, 2020. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions templates/06-cross-contract-calls/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Using Token in AssemblyScript

## Description

This project contains example of contract using token similar to [ERC20](https://theethereum.wiki/w/index.php/ERC20_Token_Standard).

The contract allows multiple participants to lock up same amount of funds in a "deal". They cannot unlock finances until they all confirm that "deal" conditions have been upheld.

This is a back-end contract only so there is no front-end included.


## To Run

*In NEAR Studio (https://studio.nearprotocol.com)*

1. Click the "Run" button on the top of the Studio window

2. You will be redirected to the output for the JavaScript tests described in `src/main.js` to show that the contract is performing properly. This is a fully back-end contract so there is no front end.


## To Explore

See `assembly/main.ts` for the contract code and `src/main.js` for the JavaScript tests which define its usage.

## Usage Example (from JS)

Create a deal to lock 5 tokens between <code>test1.near</code> and <code>vasya.near</code>:

```
await contract.makeDeal({ dealId: DEAL_ID, amount: "5", participants: ["test1.near", "vasya.near"] });
```

Lock funds into the deal:
```
await tokenContract.approve({ spender: "studio-853u597r1", tokens: "5" });
await contract.fundDeal({ dealId: DEAL_ID });
```

To see current state of the deal:
```
await contract.getDeal({ dealId: DEAL_ID });
```

Funds are returned back when all participants confirm deal completion:
```
await contract.confirmDeal({ dealId: DEAL_ID })
```
<code>DEAL_ID</code> can be any unique string used to identify given deal.
92 changes: 92 additions & 0 deletions templates/06-cross-contract-calls/assembly/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import "allocator/arena";
export { memory };

import { contractContext, globalStorage, near, ContractPromise } from "./near";
import { Deal, TransferCompleteArgs } from "./model.near";
import { Token } from "./token";

// --- contract code goes below

// TODO: Replace contract address with actual DAI bridge or any other token
let token = new Token("studio-31fz6jnjv");

// TODO: Replace with name of current contract
let CONTRACT_NAME = "studio-853u597r1";

export function getDeal(dealId: string): Deal {
let dealKey = "deal:" + dealId;
return Deal.decode(globalStorage.getBytes(dealKey));
}

export function makeDeal(dealId: string, amount: string, participants: string[]): void {
near.log("makeDeal: " + dealId + " amount: " + amount + " participants: " + participants.toString());

let dealKey = "deal:" + dealId;
let existingDeal = globalStorage.getBytes(dealKey);
assert(!existingDeal, "Deal " + dealId + " already exists");

let everythingFalse: bool[] = new Array(participants.length);
let deal: Deal = { amount, participants, approved: everythingFalse, deposited: everythingFalse, confirmed: everythingFalse };
globalStorage.setBytes(dealKey, deal.encode());
}

export function fundDeal(dealId: string): void {
near.log("fundDeal: " + dealId);

let dealKey = "deal:" + dealId;
let deal = Deal.decode(globalStorage.getBytes(dealKey));
let senderIndex = deal.participants.indexOf(contractContext.sender);

assert(senderIndex != -1, contractContext.sender + " is not part of the deal");
assert(!deal.approved[senderIndex], contractContext.sender + " already funding the deal");

deal.approved[senderIndex] = true;
globalStorage.setBytes(dealKey, deal.encode());

let args: TransferCompleteArgs = { dealId, sender: contractContext.sender };
token.transferFrom(contractContext.sender, CONTRACT_NAME, deal.amount).then("transferComplete", args.encode(), 1).returnAsResult();
}

export function transferComplete(dealId: string, sender: string): void {
near.log("transferComplete: " + dealId + " sender: " + sender);

let transferResults = ContractPromise.getResults();
assert(transferResults.length == 1, "Expect only one result from transfer call");

let dealKey = "deal:" + dealId;
let deal = Deal.decode(globalStorage.getBytes(dealKey));
let senderIndex = deal.participants.indexOf(sender);
assert(senderIndex != -1, sender + " is not part of the deal");

if (transferResults[0].success) {
deal.deposited[senderIndex] = true;
} else {
deal.approved[senderIndex] = false;
}

globalStorage.setBytes(dealKey, deal.encode());
}

export function cancelDeal(dealId: string): void {
// TODO: Allow to get out of deal if not everybody funded yet
}

export function confirmDeal(dealId: string): void {
let dealKey = "deal:" + dealId;
let deal = Deal.decode(globalStorage.getBytes(dealKey));
let senderIndex = deal.participants.indexOf(contractContext.sender);
assert(senderIndex != -1, contractContext.sender + " is not part of the deal");
assert(deal.confirmed[senderIndex] == false, contractContext.sender + " already confirmed the deal")

deal.confirmed[senderIndex] = true;
globalStorage.setBytes(dealKey, deal.encode());

let allConfirmed = deal.confirmed.every((confirmed: bool, index: i32, arr: Array<bool>): bool => confirmed);
if (allConfirmed) {
// TODO: Robust implementation should also track who actually got paid out
for (let i = 0; i < deal.participants.length; i++) {
let participant = deal.participants[i];
token.transfer(participant, deal.amount);
}
}
}
40 changes: 40 additions & 0 deletions templates/06-cross-contract-calls/assembly/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export class Deal {
amount: string;
participants: string[];
approved: bool[];
deposited: bool[];
confirmed: bool[];
}

export class TransferCompleteArgs {
sender: string;
dealId: string;
}

export class BalanceOfArgs {
tokenOwner: string;
}

export class TransferArgs {
to: string;
tokens: string;
}

export class AllowanceArgs {
tokenOwner: string;
spender: string;
}

export class ApproveArgs {
spender: string;
tokens: string;
}

export class TransferFromArgs {
from: string;
to: string;
tokens: string;
}

export class EmptyArgs {
}
41 changes: 41 additions & 0 deletions templates/06-cross-contract-calls/assembly/token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import "allocator/arena";
import { contractContext, globalStorage, near, ContractPromise } from "./near";
import { BalanceOfArgs, TransferArgs, AllowanceArgs, ApproveArgs, TransferFromArgs, EmptyArgs } from "./model.near";

export class Token {
contract: string;

constructor(contract: string) {
this.contract = contract;
}

totalSupply(): ContractPromise {
let args: EmptyArgs = {};
return ContractPromise.create(this.contract, "totalSupply", args.encode(), 1);
}

balanceOf(tokenOwner: string): ContractPromise {
let args: BalanceOfArgs = { tokenOwner };
return ContractPromise.create(this.contract, "balanceOf", args.encode(), 1);
}

allowance(tokenOwner: string, spender: string): ContractPromise {
let args: AllowanceArgs = { tokenOwner, spender };
return ContractPromise.create(this.contract, "allowance", args.encode(), 1);
}

transfer(to: string, tokens: string): ContractPromise {
let args: TransferArgs = { to, tokens };
return ContractPromise.create(this.contract, "transfer", args.encode(), 1);
}

approve(spender: string, tokens: string): boolean {
let args: ApproveArgs = { spender, tokens };
return ContractPromise.create(this.contract, "approve", args.encode(), 1);
}

transferFrom(from: string, to: string, tokens: string): ContractPromise {
let args: TransferFromArgs = { from, to, tokens };
return ContractPromise.create(this.contract, "transferFrom", args.encode(), 1);
}
}
6 changes: 6 additions & 0 deletions templates/06-cross-contract-calls/assembly/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "../node_modules/assemblyscript/std/assembly.json",
"include": [
"./**/*.ts"
]
}
48 changes: 48 additions & 0 deletions templates/06-cross-contract-calls/gulpfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const gulp = require("gulp");

function generateBindings(inputFile, outputFile, callback) {
const asc = require("assemblyscript/bin/asc");
asc.main([
inputFile,
"--baseDir", "assembly",
"--nearFile", outputFile,
"--measure"
], callback);
}

function compile(inputFile, outputFile, callback) {
const asc = require("assemblyscript/bin/asc");
asc.main([
inputFile,
"--baseDir", "assembly",
"--binaryFile", outputFile,
"--sourceMap",
"--measure"
], callback);
}

gulp.task("build:model", callback => {
generateBindings("model.ts", "../out/model.near.ts", callback);
});

gulp.task("build:bindings", ["build:model"], callback => {
generateBindings("main.ts", "../out/main.near.ts", callback);
});

gulp.task("build", ["build:bindings"], callback => {
compile("../out/main.near.ts", "../out/main.wasm", callback);
});

gulp.task("default", ["build"]);

// This task is not required when running the project locally. Its purpose is to set up the
// AssemblyScript compiler when a new project has been loaded in WebAssembly Studio.
gulp.task("project:load", () => {
const utils = require("@wasm/studio-utils");
utils.eval(utils.project.getFile("setup.js").getData(), {
logLn,
project,
monaco,
fileTypeForExtension,
});
});
17 changes: 17 additions & 0 deletions templates/06-cross-contract-calls/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@wasm/token_contract_ts",
"description": "",
"version": "1.0.0",
"scripts": {
"build": "gulp"
},
"devDependencies": {
"assemblyscript": "AssemblyScript/assemblyscript",
"gulp": "^3"
},
"wasmStudio": {
"name": "Token Smart Contract",
"description": "# Token Smart Contract\n\nThis project contains implementation of token contract similar to ERC20. The contract allows you to launch a new token on top of the NEAR blockchain which users can interact with as if it were any other token -- checking balances, transferring, etc.\n\n[AssemblyScript](https://github.com/AssemblyScript/assemblyscript) compiles strictly typed TypeScript to WebAssembly using Binaryen. See the [AssemblyScript wiki](https://github.com/AssemblyScript/assemblyscript/wiki) for further instructions and documentation.",
"icon": "typescript-lang-file-icon"
}
}
80 changes: 80 additions & 0 deletions templates/06-cross-contract-calls/setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// This file is not required when running the project locally. Its purpose is to set up the
// AssemblyScript compiler when a new project has been loaded in WebAssembly Studio.

// Path manipulation lifted from https://gist.github.com/creationix/7435851

// Joins path segments. Preserves initial "/" and resolves ".." and "."
// Does not support using ".." to go above/outside the root.
// This means that join("foo", "../../bar") will not resolve to "../bar"
function join(/* path segments */) {
// Split the inputs into a list of path commands.
var parts = [];
for (var i = 0, l = arguments.length; i < l; i++) {
parts = parts.concat(arguments[i].split("/"));
}
// Interpret the path commands to get the new resolved path.
var newParts = [];
for (i = 0, l = parts.length; i < l; i++) {
var part = parts[i];
// Remove leading and trailing slashes
// Also remove "." segments
if (!part || part === ".") continue;
// Interpret ".." to pop the last segment
if (part === "..") newParts.pop();
// Push new path segments.
else newParts.push(part);
}
// Preserve the initial slash if there was one.
if (parts[0] === "") newParts.unshift("");
// Turn back into a single string path.
return newParts.join("/") || (newParts.length ? "/" : ".");
}

// A simple function to get the dirname of a path
// Trailing slashes are ignored. Leading slash is preserved.
function dirname(path) {
return join(path, "..");
}

require.config({
paths: {
"binaryen": "https://cdn.jsdelivr.net/npm/binaryen@64.0.0/index",
"assemblyscript": "https://cdn.jsdelivr.net/gh/nearprotocol/assemblyscript@2337262883226a5431b80610ade7b4d379e82a30/dist/assemblyscript",
"assemblyscript/bin/asc": "https://cdn.jsdelivr.net/gh/nearprotocol/assemblyscript@2337262883226a5431b80610ade7b4d379e82a30/dist/asc",
}
});
logLn("Loading AssemblyScript compiler ...");
require(["assemblyscript/bin/asc"], asc => {
monaco.languages.typescript.typescriptDefaults.addExtraLib(asc.definitionFiles.assembly);
asc.main = (main => (args, options, fn) => {
if (typeof options === "function") {
fn = options;
options = undefined;
}

return main(args, options || {
stdout: asc.createMemoryStream(),
stderr: asc.createMemoryStream(logLn),
readFile: (filename, baseDir) => {
let path = join(baseDir, filename);
console.log("readFile", path);
if (path.startsWith("out/") && path.indexOf(".near.ts") == -1) {
path = path.replace(/^out/, baseDir );
console.log("path", path);
} else if (path.startsWith(baseDir) && path.indexOf(".near.ts") != -1) {
path = path.replace(new RegExp("^" + baseDir), "out");
console.log("path", path);
}
const file = project.getFile(path);
return file ? file.data : null;
},
writeFile: (filename, contents) => {
const name = filename.startsWith("../") ? filename.substring(3) : filename;
const type = fileTypeForExtension(name.substring(name.lastIndexOf(".") + 1));
project.newFile(name, type, true).setData(contents);
},
listFiles: () => []
}, fn);
})(asc.main);
logLn("AssemblyScript compiler is ready!");
});
11 changes: 11 additions & 0 deletions templates/06-cross-contract-calls/src/loader.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<head>
<script>
window.paceOptions = {
elements: {
selectors: ['.never-appears']
}
};
</script>
<script src="https://cdn.jsdelivr.net/gh/HubSpot/pace@v0.5.3/pace.min.js"></script>
<link href="https://cdn.jsdelivr.net/gh/HubSpot/pace@v0.5.3/themes/black/pace-theme-center-radar.css" rel="stylesheet" />
</head>
Loading