diff --git a/.github/workflows/examples.yml b/.github/workflows/examples.yml index 9f80f0bf9..ba6e3db18 100644 --- a/.github/workflows/examples.yml +++ b/.github/workflows/examples.yml @@ -42,3 +42,7 @@ jobs: run: cd examples && yarn build:parking-lot && yarn test:parking-lot - name: standard nft run: cd examples && yarn build-nft && yarn test:nft + - name: programmatic-update + run: cd examples && yarn build:programmatic-update && yarn test:programmatic-update + - name: cross-contract-call-loop + run: cd examples && yarn build:cross-contract-call-loop && yarn test:cross-contract-call-loop diff --git a/README.md b/README.md index 7463d4fb0..c17bfb38a 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ There are a couple of contract examples in the project: - [Non fungible token receiver contract](https://github.com/near/near-sdk-js/tree/develop/examples/src/non-fungible-token-receiver.js) - [Status message board](https://github.com/near/near-sdk-js/tree/develop/examples/src/status-message.js) - [Status message board with unique messages](https://github.com/near/near-sdk-js/tree/develop/examples/src/status-message-collections.js) +- [Programmatic Update After Locking The Contract](https://github.com/near/near-sdk-js/tree/develop/examples/src/programmatic-update.js) To build all examples, run `yarn build` in `examples/`. To test all examples, run `yarn test`. You can also build and test one specific example with `yarn build:` and `yarn test:`, see `examples/package.json`. diff --git a/examples/__tests__/standard-nft/test_approval.ava.js b/examples/__tests__/standard-nft/test_approval.ava.js index 8ceaf33b5..ef58308e3 100644 --- a/examples/__tests__/standard-nft/test_approval.ava.js +++ b/examples/__tests__/standard-nft/test_approval.ava.js @@ -85,7 +85,6 @@ test("Simple approve", async (t) => { { token_id: "0", account_id: ali.accountId, - msg: null, }, { attachedDeposit: "510000000000000000000" } ); @@ -94,7 +93,6 @@ test("Simple approve", async (t) => { let alice_approved = await nft.view("nft_is_approved", { token_id: "0", approved_account_id: ali.accountId, - approval_id: null, }); t.assert(alice_approved); @@ -118,7 +116,6 @@ test("Simple approve", async (t) => { { token_id: "0", account_id: ali.accountId, - msg: null, }, { attachedDeposit: "1", @@ -138,7 +135,6 @@ test("Simple approve", async (t) => { { token_id: "0", account_id: bob.accountId, - msg: null, }, { attachedDeposit: "550000000000000000000", @@ -191,7 +187,6 @@ test("Approved account transfers token", async (t) => { { token_id: "0", account_id: ali.accountId, - msg: null, }, { attachedDeposit: "510000000000000000000" } ); @@ -206,7 +201,6 @@ test("Approved account transfers token", async (t) => { { receiver_id: ali.accountId, token_id: "0", - approval_id: null, memo: "gotcha! bahahaha", }, { attachedDeposit: "1" } @@ -226,7 +220,6 @@ test("revoke", async (t) => { { token_id: "0", account_id: ali.accountId, - msg: null, }, { attachedDeposit: "510000000000000000000" } ); @@ -238,7 +231,6 @@ test("revoke", async (t) => { { token_id: "0", account_id: bob.accountId, - msg: null, }, { attachedDeposit: "510000000000000000000", @@ -262,14 +254,12 @@ test("revoke", async (t) => { let alice_approved = await nft.view("nft_is_approved", { token_id: "0", approved_account_id: ali.accountId, - approval_id: null, }); t.assert(!alice_approved); let bob_approved = await nft.view("nft_is_approved", { token_id: "0", approved_account_id: bob.accountId, - approval_id: null, }); t.assert(bob_approved); @@ -289,14 +279,12 @@ test("revoke", async (t) => { alice_approved = await nft.view("nft_is_approved", { token_id: "0", approved_account_id: ali.accountId, - approval_id: null, }); t.assert(!alice_approved); bob_approved = await nft.view("nft_is_approved", { token_id: "0", approved_account_id: bob.accountId, - approval_id: null, }); t.assert(!bob_approved); }); @@ -310,7 +298,6 @@ test("revoke all", async (t) => { { token_id: "0", accountId: ali.accountId, - msg: null, }, { attachedDeposit: "510000000000000000000" } ); @@ -322,7 +309,6 @@ test("revoke all", async (t) => { { token_id: "0", accountId: bob.accountId, - msg: null, }, { attachedDeposit: "510000000000000000000", @@ -343,14 +329,12 @@ test("revoke all", async (t) => { let alice_approved = await nft.view("nft_is_approved", { token_id: "0", approved_account_id: ali.accountId, - approval_id: null, }); t.assert(!alice_approved); let bob_approved = await nft.view("nft_is_approved", { token_id: "0", approved_account_id: bob.accountId, - approval_id: null, }); t.assert(!bob_approved); }); diff --git a/examples/__tests__/standard-nft/test_core.ava.js b/examples/__tests__/standard-nft/test_core.ava.js index 439046030..b91ba23e8 100644 --- a/examples/__tests__/standard-nft/test_core.ava.js +++ b/examples/__tests__/standard-nft/test_core.ava.js @@ -83,7 +83,6 @@ test("Simple transfer", async (t) => { { receiver_id: ali.accountId, token_id: "0", - approval_id: null, memo: "simple transfer", }, { attachedDeposit: "1" } @@ -105,7 +104,6 @@ test("Transfer call fast return to sender", async (t) => { { receiver_id: nftReceiver.accountId, token_id: "0", - approval_id: null, memo: "transfer & call", msg: "return-it-now", }, @@ -129,7 +127,6 @@ test("Transfer call slow return to sender", async (t) => { { receiver_id: nftReceiver.accountId, token_id: "0", - approval_id: null, memo: "transfer & call", msg: "return-it-later", }, @@ -153,7 +150,6 @@ test("Transfer call fast keep with sender", async (t) => { { receiver_id: nftReceiver.accountId, token_id: "0", - approval_id: null, memo: "transfer & call", msg: "keep-it-now", }, @@ -177,7 +173,6 @@ test("Transfer call slow keep with sender", async (t) => { { receiver_id: nftReceiver.accountId, token_id: "0", - approval_id: null, memo: "transfer & call", msg: "keep-it-later", }, @@ -201,7 +196,6 @@ test("Transfer call receiver panics", async (t) => { { receiver_id: nftReceiver.accountId, token_id: "0", - approval_id: null, memo: "transfer & call", msg: "incorrect message", }, @@ -227,7 +221,6 @@ test("Transfer call receiver panics and nft_resolve_transfer produces no log if { receiver_id: nftReceiver.accountId, token_id: "0", - approval_id: null, memo: "transfer & call", msg: "incorrect message", }, @@ -254,7 +247,6 @@ test("Simple transfer no logs on failure", async (t) => { { receiver_id: nftOwner.accountId, token_id: "0", - approval_id: null, memo: "simple transfer", }, { attachedDeposit: "1" } diff --git a/examples/__tests__/standard-nft/test_enumeration.ava.js b/examples/__tests__/standard-nft/test_enumeration.ava.js index 1fd7c171d..128c4a0df 100644 --- a/examples/__tests__/standard-nft/test_enumeration.ava.js +++ b/examples/__tests__/standard-nft/test_enumeration.ava.js @@ -92,13 +92,13 @@ test("Enumerate NFT tokens total supply", async (t) => { test("Enumerate NFT tokens", async (t) => { const { nft } = t.context.accounts; - let nftTokens = await nft.view("nft_tokens", { from_index: 1, limit: null }); + let nftTokens = await nft.view("nft_tokens", { from_index: 1 }); t.is(nftTokens.length, 3); t.is(nftTokens[0].token_id, "1"); t.is(nftTokens[1].token_id, "2"); t.is(nftTokens[2].token_id, "3"); - nftTokens = await nft.view("nft_tokens", { from_index: null, limit: 2 }); + nftTokens = await nft.view("nft_tokens", { limit: 2 }); t.is(nftTokens.length, 2); t.is(nftTokens[0].token_id, "0"); t.is(nftTokens[1].token_id, "1"); @@ -123,8 +123,6 @@ test("Enumerate NFT tokens for owner", async (t) => { let nftTokens = await nft.view("nft_tokens_for_owner", { account_id: nftOwner.accountId, - from_index: null, - limit: null, }); t.is(nftTokens.length, 4); t.is(nftTokens[0].token_id, "0"); @@ -135,7 +133,6 @@ test("Enumerate NFT tokens for owner", async (t) => { nftTokens = await nft.view("nft_tokens_for_owner", { account_id: nftOwner.accountId, from_index: 1, - limit: null, }); t.is(nftTokens.length, 3); t.is(nftTokens[0].token_id, "1"); @@ -144,7 +141,6 @@ test("Enumerate NFT tokens for owner", async (t) => { nftTokens = await nft.view("nft_tokens_for_owner", { account_id: nftOwner.accountId, - from_index: null, limit: 2, }); t.is(nftTokens.length, 2); @@ -157,7 +153,6 @@ test("Enumerate NFT tokens for owner", async (t) => { { receiver_id: ali.accountId, token_id: "0", - approval_id: null, memo: "simple transfer", }, { attachedDeposit: "1" } @@ -166,15 +161,11 @@ test("Enumerate NFT tokens for owner", async (t) => { nftTokens = await nft.view("nft_tokens_for_owner", { account_id: ali.accountId, - from_index: null, - limit: null, }); t.is(nftTokens.length, 1); nftTokens = await nft.view("nft_tokens_for_owner", { account_id: nftOwner.accountId, - from_index: null, - limit: null, }); t.is(nftTokens.length, 3); t.is(nftTokens[0].token_id, "3"); diff --git a/examples/__tests__/test-cross-contract-call-loop.ava.js b/examples/__tests__/test-cross-contract-call-loop.ava.js new file mode 100644 index 000000000..45d62a487 --- /dev/null +++ b/examples/__tests__/test-cross-contract-call-loop.ava.js @@ -0,0 +1,48 @@ +import { Worker, NEAR } from "near-workspaces"; +import test from "ava"; + +test.beforeEach(async (t) => { + const worker = await Worker.init(); + const root = worker.rootAccount; + + const xccLoop = await root.createSubAccount("xcc-loop"); + await xccLoop.deploy("./build/cross-contract-call-loop.wasm"); + + const firstContract = await root.createSubAccount("first-contract"); + const secondContract = await root.createSubAccount("second-contract"); + const thirdContract = await root.createSubAccount("third-contract"); + await firstContract.deploy("./build/counter.wasm"); + await secondContract.deploy("./build/counter.wasm"); + await thirdContract.deploy("./build/counter.wasm"); + + await root.call(firstContract, "increase", {}); + await root.call(secondContract, "increase", {}); + await root.call(thirdContract, "increase", {}); + + const alice = await root.createSubAccount("alice", { + initialBalance: NEAR.parse("100 N").toJSON(), + }); + + t.context.worker = worker; + t.context.accounts = { root, alice, xccLoop }; +}); + +test.afterEach.always(async (t) => { + await t.context.worker.tearDown().catch((error) => { + console.log("Failed to tear down the worker:", error); + }); +}); + +test("should have a count of 3 after calling incrementCount", async (t) => { + const { xccLoop, alice } = t.context.accounts; + const expected = 3; + const callbackResult = await alice.call( + xccLoop, + "incrementCount", + {}, + { gas: "300" + "0".repeat(12) } + ); + t.is(callbackResult, 3); + const result = await xccLoop.view("getCount"); + t.deepEqual(result, expected); +}); diff --git a/examples/__tests__/test-programmatic-update.ava.js b/examples/__tests__/test-programmatic-update.ava.js new file mode 100644 index 000000000..84a48b0c9 --- /dev/null +++ b/examples/__tests__/test-programmatic-update.ava.js @@ -0,0 +1,49 @@ +import { Worker } from "near-workspaces"; +import test from "ava"; +import * as fs from "fs"; +import * as path from "path"; + +test.beforeEach(async (t) => { + const worker = await Worker.init(); + const root = worker.rootAccount; + + const ali = await root.createSubAccount("ali"); + + const contract = await root.devDeploy( + "build/programmatic-update-before.wasm" + ); + + await contract.call(contract, "init", { manager: ali.accountId }); + + t.context.worker = worker; + t.context.accounts = { root, contract, ali }; +}); + +test.afterEach.always(async (t) => { + await t.context.worker.tearDown().catch((error) => { + console.log("Failed to tear down the worker:", error); + }); +}); + +test("the contract can be programmatically updated", async (t) => { + const { ali, contract } = t.context.accounts; + + // ASSERT BEFORE CODE UPDATE + const codeBefore = await contract.viewCodeRaw(); + const beforeDefaultGreeting = await contract.view("get_greeting", {}); + t.is(beforeDefaultGreeting, "Hello"); + + // ACT (UPDATE CODE) + const code = fs.readFileSync( + path.resolve("./build/programmatic-update-after.wasm") + ); + await ali.call(contract, "updateContract", code, { + gas: "300" + "0".repeat(12), // 300 Tgas + }); + + // ASSERT AFTER CODE UPDATE + const codeAfter = await contract.viewCodeRaw(); + const afterDefaultGreeting = await contract.view("view_greeting", {}); + t.not(codeBefore, codeAfter, "code should be different after update"); + t.is(afterDefaultGreeting, "Hi"); +}); diff --git a/examples/package.json b/examples/package.json index 32948a4b6..b884bcfac 100644 --- a/examples/package.json +++ b/examples/package.json @@ -13,11 +13,13 @@ "build:counter-lowlevel": "near-sdk-js build src/counter-lowlevel.js build/counter-lowlevel.wasm", "build:counter-ts": "near-sdk-js build src/counter.ts build/counter-ts.wasm", "build:cross-contract-call": "near-sdk-js build src/status-message.js build/status-message.wasm && near-sdk-js build src/cross-contract-call.js build/cross-contract-call.wasm", + "build:cross-contract-call-loop": "near-sdk-js build src/counter.js build/counter.wasm && near-sdk-js build src/cross-contract-call-loop.js build/cross-contract-call-loop.wasm", "build:fungible-token-lockable": "near-sdk-js build src/fungible-token-lockable.js build/fungible-token-lockable.wasm", "build:fungible-token": "near-sdk-js build src/fungible-token.ts build/fungible-token.wasm && near-sdk-js build src/fungible-token-helper.ts build/fungible-token-helper.wasm", "build:non-fungible-token": "near-sdk-js build src/non-fungible-token-receiver.js build/non-fungible-token-receiver.wasm && near-sdk-js build src/non-fungible-token.js build/non-fungible-token.wasm", "build:status-message-collections": "near-sdk-js build src/status-message-collections.js build/status-message-collections.wasm", "build:parking-lot": "near-sdk-js build src/parking-lot.ts build/parking-lot.wasm", + "build:programmatic-update": "near-sdk-js build src/programmatic-update-before.ts build/programmatic-update-before.wasm && near-sdk-js build src/programmatic-update-after.ts build/programmatic-update-after.wasm", "build:nested-collections": "near-sdk-js build src/nested-collections.ts build/nested-collections.wasm", "build-nft": "run-s build:nft-*", "build:nft-contract": "near-sdk-js build src/standard-nft/my-nft.ts build/my-nft.wasm", @@ -31,11 +33,13 @@ "test:counter-lowlevel": "COUNTER_LOWLEVEL=1 ava __tests__/test-counter.ava.js", "test:counter-ts": "COUNTER_TS=1 ava __tests__/test-counter.ava.js", "test:cross-contract-call": "ava __tests__/test-cross-contract-call.ava.js", + "test:cross-contract-call-loop": "ava __tests__/test-cross-contract-call-loop.ava.js", "test:fungible-token-lockable": "ava __tests__/test-fungible-token-lockable.ava.js", "test:fungible-token": "ava __tests__/test-fungible-token.ava.js", "test:non-fungible-token": "ava __tests__/test-non-fungible-token.ava.js", "test:status-message-collections": "ava __tests__/test-status-message-collections.ava.js", "test:parking-lot": "ava __tests__/test-parking-lot.ava.js", + "test:programmatic-update": "ava __tests__/test-programmatic-update.ava.js", "test:nested-collections": "ava __tests__/test-nested-collections.ava.js" }, "author": "Near Inc ", diff --git a/examples/src/cross-contract-call-loop.js b/examples/src/cross-contract-call-loop.js new file mode 100644 index 000000000..bd46e6d71 --- /dev/null +++ b/examples/src/cross-contract-call-loop.js @@ -0,0 +1,61 @@ +import { call, near, NearBindgen, NearPromise, view } from "near-sdk-js"; + +const CONTRACTS = [ + "first-contract.test.near", + "second-contract.test.near", + "third-contract.test.near", +]; +const NO_ARGS = ""; +const THIRTY_TGAS = BigInt("30" + "0".repeat(12)); + +@NearBindgen({}) +export class LoopXCC { + constructor() { + this.count = 0; + } + + @call({}) + incrementCount() { + let promise = NearPromise.new(CONTRACTS[0]).functionCall( + "getCount", + NO_ARGS, + BigInt(0), + THIRTY_TGAS + ); + for (let i = 1; i < CONTRACTS.length; i++) { + promise = promise.and( + NearPromise.new(CONTRACTS[i]).functionCall( + "getCount", + NO_ARGS, + BigInt(0), + THIRTY_TGAS + ) + ); + } + promise = promise.then( + NearPromise.new(near.currentAccountId()).functionCall( + "_incrementCountCallback", + NO_ARGS, + BigInt(0), + THIRTY_TGAS + ) + ); + return promise.asReturn(); + } + + @call({ privateFunction: true }) + _incrementCountCallback() { + const callCount = near.promiseResultsCount(); + for (let i = 0; i < callCount; i++) { + const promiseResult = near.promiseResult(i); + const result = JSON.parse(promiseResult); + this.count += result; + } + return this.count; + } + + @view({}) + getCount() { + return this.count; + } +} diff --git a/examples/src/programmatic-update-after.ts b/examples/src/programmatic-update-after.ts new file mode 100644 index 000000000..9fa00e7a0 --- /dev/null +++ b/examples/src/programmatic-update-after.ts @@ -0,0 +1,30 @@ +import { NearBindgen, near, initialize, assert, view } from "near-sdk-js"; + +@NearBindgen({ requireInit: true }) +export class ProgrammaticUpdateAfter { + greeting = "Hello"; + + @initialize({ privateFunction: true }) + init({ manager }: { manager: string }) { + near.log(`Setting manager to be ${manager}`); + near.storageWrite("MANAGER", manager); + } + + @view({}) // Method renamed and return "Hi" when greeting is "Hello" + view_greeting(): string { + return this.greeting.replace("Hello", "Hi"); + } +} + +export function updateContract() { + const manager = near.storageRead("MANAGER"); + assert( + near.predecessorAccountId() === manager, + "Only the manager can update the code" + ); + + const promiseId = near.promiseBatchCreate(near.currentAccountId()); + near.promiseBatchActionDeployContract(promiseId, near.input()); + + return near.promiseReturn(promiseId); +} diff --git a/examples/src/programmatic-update-before.ts b/examples/src/programmatic-update-before.ts new file mode 100644 index 000000000..cae836077 --- /dev/null +++ b/examples/src/programmatic-update-before.ts @@ -0,0 +1,30 @@ +import { NearBindgen, near, initialize, assert, view } from "near-sdk-js"; + +@NearBindgen({ requireInit: true }) +export class ProgrammaticUpdateBefore { + greeting = "Hello"; + + @initialize({ privateFunction: true }) + init({ manager }: { manager: string }) { + near.log(`Setting manager to be ${manager}`); + near.storageWrite("MANAGER", manager); + } + + @view({}) // This method will be renamed after update and will return "Hi" if greeting is "Hello" + get_greeting(): string { + return this.greeting; + } +} + +export function updateContract() { + const manager = near.storageRead("MANAGER"); + assert( + near.predecessorAccountId() === manager, + "Only the manager can update the code" + ); + + const promiseId = near.promiseBatchCreate(near.currentAccountId()); + near.promiseBatchActionDeployContract(promiseId, near.input()); + + return near.promiseReturn(promiseId); +} diff --git a/examples/src/standard-nft/my-nft.ts b/examples/src/standard-nft/my-nft.ts index 4c31b04b9..a585ac6b7 100644 --- a/examples/src/standard-nft/my-nft.ts +++ b/examples/src/standard-nft/my-nft.ts @@ -82,8 +82,8 @@ export class MyNFT from_index, limit, }: { - from_index: number; - limit: number; + from_index?: number; + limit?: number; }): Token[] { return this.tokens.nft_tokens({ from_index, limit }); } @@ -100,8 +100,8 @@ export class MyNFT limit, }: { account_id: string; - from_index: number; - limit: number; + from_index?: number; + limit?: number; }): Token[] { return this.tokens.nft_tokens_for_owner({ account_id, from_index, limit }); } @@ -114,7 +114,7 @@ export class MyNFT }: { token_id: string; account_id: string; - msg: string; + msg?: string; }): Option { return this.tokens.nft_approve({ token_id, account_id, msg }); } @@ -143,7 +143,7 @@ export class MyNFT }: { token_id: string; approved_account_id: string; - approval_id: bigint; + approval_id?: bigint; }): boolean { return this.tokens.nft_is_approved({ token_id, @@ -162,7 +162,7 @@ export class MyNFT previous_owner_id: string; receiver_id: string; token_id: string; - approved_account_ids: { [approval: string]: bigint }; + approved_account_ids?: { [approval: string]: bigint }; }): boolean { return this.tokens.nft_resolve_transfer({ previous_owner_id, @@ -187,8 +187,8 @@ export class MyNFT }: { receiver_id: string; token_id: string; - approval_id: bigint; - memo: string; + approval_id?: bigint; + memo?: string; }) { this.tokens.nft_transfer({ receiver_id, token_id, approval_id, memo }); } @@ -203,8 +203,8 @@ export class MyNFT }: { receiver_id: string; token_id: string; - approval_id: bigint; - memo: string; + approval_id?: bigint; + memo?: string; msg: string; }): PromiseOrValue { return this.tokens.nft_transfer_call({ diff --git a/jsconfig.json b/jsconfig.json deleted file mode 100644 index 43c5eea40..000000000 --- a/jsconfig.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "exclude": ["node_modules"], - "include": ["cli"] -} diff --git a/lib/api.d.ts b/lib/api.d.ts index f8eff3c68..04d6ae2bf 100644 --- a/lib/api.d.ts +++ b/lib/api.d.ts @@ -251,16 +251,107 @@ export declare function promiseResult(promiseIndex: PromiseIndex): Bytes; * @param promiseIndex - The index of the promise to execute. */ export declare function promiseReturn(promiseIndex: PromiseIndex): void; +/** + * Returns sha256 hash of given value + * @param value - value to be hashed, in Bytes + * @returns hash result in Bytes + */ export declare function sha256(value: Bytes): Bytes; +/** + * Returns keccak256 hash of given value + * @param value - value to be hashed, in Bytes + * @returns hash result in Bytes + */ export declare function keccak256(value: Bytes): Bytes; +/** + * Returns keccak512 hash of given value + * @param value - value to be hashed, in Bytes + * @returns hash result in Bytes + */ export declare function keccak512(value: Bytes): Bytes; +/** + * Returns ripemd160 hash of given value + * @param value - value to be hashed, in Bytes + * @returns hash result in Bytes + */ export declare function ripemd160(value: Bytes): Bytes; +/** + * Recovers an ECDSA signer address from a 32-byte message hash and a corresponding + * signature along with v recovery byte. Takes in an additional flag to check for + * malleability of the signature which is generally only ideal for transactions. + * + * @param hash - 32-byte message hash + * @param sig - signature + * @param v - number of recovery byte + * @param malleabilityFlag - whether to check malleability + * @returns 64 bytes representing the public key if the recovery was successful. + */ export declare function ecrecover(hash: Bytes, sig: Bytes, v: number, malleabilityFlag: number): Bytes | null; +/** + * Panic the transaction execution with given message + * @param msg - panic message in raw bytes, which should be a valid UTF-8 sequence + */ export declare function panicUtf8(msg: Bytes): never; +/** + * Log the message in transaction logs + * @param msg - message in raw bytes, which should be a valid UTF-8 sequence + */ export declare function logUtf8(msg: Bytes): void; +/** + * Log the message in transaction logs + * @param msg - message in raw bytes, which should be a valid UTF-16 sequence + */ export declare function logUtf16(msg: Bytes): void; +/** + * Returns the number of staked NEAR of given validator, in yoctoNEAR + * @param accountId - validator's AccountID + * @returns - staked amount + */ export declare function validatorStake(accountId: Bytes): bigint; +/** + * Returns the number of staked NEAR of all validators, in yoctoNEAR + * @returns total staked amount + */ export declare function validatorTotalStake(): bigint; +/** + * Computes multiexp on alt_bn128 curve using Pippenger's algorithm \sum_i + * mul_i g_{1 i} should be equal result. + * + * @param value - equence of (g1:G1, fr:Fr), where + * G1 is point (x:Fq, y:Fq) on alt_bn128, + * alt_bn128 is Y^2 = X^3 + 3 curve over Fq. + * `value` is encoded as packed, little-endian + * `[((u256, u256), u256)]` slice. + * + * @returns multi exp sum + */ export declare function altBn128G1Multiexp(value: Bytes): Bytes; +/** + * Computes sum for signed g1 group elements on alt_bn128 curve \sum_i + * (-1)^{sign_i} g_{1 i} should be equal result. + * + * @param value - sequence of (sign:bool, g1:G1), where + * G1 is point (x:Fq, y:Fq) on alt_bn128, + * alt_bn128 is Y^2 = X^3 + 3 curve over Fq. + * value` is encoded a as packed, little-endian + * `[((u256, u256), ((u256, u256), (u256, u256)))]` slice. + * + * @returns sum over Fq. + */ export declare function altBn128G1Sum(value: Bytes): Bytes; +/** + * Computes pairing check on alt_bn128 curve. + * \sum_i e(g_{1 i}, g_{2 i}) should be equal one (in additive notation), e(g1, g2) is Ate pairing + * + * @param value - sequence of (g1:G1, g2:G2), where + * G2 is Fr-ordered subgroup point (x:Fq2, y:Fq2) on alt_bn128 twist, + * alt_bn128 twist is Y^2 = X^3 + 3/(i+9) curve over Fq2 + * Fq2 is complex field element (re: Fq, im: Fq) + * G1 is point (x:Fq, y:Fq) on alt_bn128, + * alt_bn128 is Y^2 = X^3 + 3 curve over Fq + * `value` is encoded a as packed, little-endian + * `[((u256, u256), ((u256, u256), (u256, u256)))]` slice. + * + * @returns whether pairing check pass + */ export declare function altBn128PairingCheck(value: Bytes): boolean; diff --git a/lib/api.js b/lib/api.js index ff051a553..0c5b38caa 100644 --- a/lib/api.js +++ b/lib/api.js @@ -362,22 +362,53 @@ export function promiseResult(promiseIndex) { export function promiseReturn(promiseIndex) { env.promise_return(promiseIndex); } +/** + * Returns sha256 hash of given value + * @param value - value to be hashed, in Bytes + * @returns hash result in Bytes + */ export function sha256(value) { env.sha256(value, 0); return env.read_register(0); } +/** + * Returns keccak256 hash of given value + * @param value - value to be hashed, in Bytes + * @returns hash result in Bytes + */ export function keccak256(value) { env.keccak256(value, 0); return env.read_register(0); } +/** + * Returns keccak512 hash of given value + * @param value - value to be hashed, in Bytes + * @returns hash result in Bytes + */ export function keccak512(value) { env.keccak512(value, 0); return env.read_register(0); } +/** + * Returns ripemd160 hash of given value + * @param value - value to be hashed, in Bytes + * @returns hash result in Bytes + */ export function ripemd160(value) { env.ripemd160(value, 0); return env.read_register(0); } +/** + * Recovers an ECDSA signer address from a 32-byte message hash and a corresponding + * signature along with v recovery byte. Takes in an additional flag to check for + * malleability of the signature which is generally only ideal for transactions. + * + * @param hash - 32-byte message hash + * @param sig - signature + * @param v - number of recovery byte + * @param malleabilityFlag - whether to check malleability + * @returns 64 bytes representing the public key if the recovery was successful. + */ export function ecrecover(hash, sig, v, malleabilityFlag) { const returnValue = env.ecrecover(hash, sig, v, malleabilityFlag, 0); if (returnValue === 0n) { @@ -386,29 +417,89 @@ export function ecrecover(hash, sig, v, malleabilityFlag) { return env.read_register(0); } // NOTE: "env.panic(msg)" is not exported, use "throw Error(msg)" instead +/** + * Panic the transaction execution with given message + * @param msg - panic message in raw bytes, which should be a valid UTF-8 sequence + */ export function panicUtf8(msg) { env.panic_utf8(msg); } +/** + * Log the message in transaction logs + * @param msg - message in raw bytes, which should be a valid UTF-8 sequence + */ export function logUtf8(msg) { env.log_utf8(msg); } +/** + * Log the message in transaction logs + * @param msg - message in raw bytes, which should be a valid UTF-16 sequence + */ export function logUtf16(msg) { env.log_utf16(msg); } +/** + * Returns the number of staked NEAR of given validator, in yoctoNEAR + * @param accountId - validator's AccountID + * @returns - staked amount + */ export function validatorStake(accountId) { return env.validator_stake(accountId); } +/** + * Returns the number of staked NEAR of all validators, in yoctoNEAR + * @returns total staked amount + */ export function validatorTotalStake() { return env.validator_total_stake(); } +/** + * Computes multiexp on alt_bn128 curve using Pippenger's algorithm \sum_i + * mul_i g_{1 i} should be equal result. + * + * @param value - equence of (g1:G1, fr:Fr), where + * G1 is point (x:Fq, y:Fq) on alt_bn128, + * alt_bn128 is Y^2 = X^3 + 3 curve over Fq. + * `value` is encoded as packed, little-endian + * `[((u256, u256), u256)]` slice. + * + * @returns multi exp sum + */ export function altBn128G1Multiexp(value) { env.alt_bn128_g1_multiexp(value, 0); return env.read_register(0); } +/** + * Computes sum for signed g1 group elements on alt_bn128 curve \sum_i + * (-1)^{sign_i} g_{1 i} should be equal result. + * + * @param value - sequence of (sign:bool, g1:G1), where + * G1 is point (x:Fq, y:Fq) on alt_bn128, + * alt_bn128 is Y^2 = X^3 + 3 curve over Fq. + * value` is encoded a as packed, little-endian + * `[((u256, u256), ((u256, u256), (u256, u256)))]` slice. + * + * @returns sum over Fq. + */ export function altBn128G1Sum(value) { env.alt_bn128_g1_sum(value, 0); return env.read_register(0); } +/** + * Computes pairing check on alt_bn128 curve. + * \sum_i e(g_{1 i}, g_{2 i}) should be equal one (in additive notation), e(g1, g2) is Ate pairing + * + * @param value - sequence of (g1:G1, g2:G2), where + * G2 is Fr-ordered subgroup point (x:Fq2, y:Fq2) on alt_bn128 twist, + * alt_bn128 twist is Y^2 = X^3 + 3/(i+9) curve over Fq2 + * Fq2 is complex field element (re: Fq, im: Fq) + * G1 is point (x:Fq, y:Fq) on alt_bn128, + * alt_bn128 is Y^2 = X^3 + 3 curve over Fq + * `value` is encoded a as packed, little-endian + * `[((u256, u256), ((u256, u256), (u256, u256)))]` slice. + * + * @returns whether pairing check pass + */ export function altBn128PairingCheck(value) { return env.alt_bn128_pairing_check(value) === 1n; } diff --git a/lib/cli/cli.d.ts b/lib/cli/cli.d.ts index dd0d00d56..a6f3d4302 100644 --- a/lib/cli/cli.d.ts +++ b/lib/cli/cli.d.ts @@ -5,8 +5,10 @@ export declare function validateCom(source: string, { verbose }: { export declare function checkTypescriptCom(source: string, { verbose }: { verbose: boolean; }): Promise; -export declare function generateAbi(source: string, target: string, packageJson: string, tsConfig: string): Promise; -export declare function createJsFileWithRullupCom(source: string, target: string, { verbose }: { +export declare function generateAbi(source: string, target: string, packageJson: string, tsConfig: string, { verbose }: { + verbose: boolean; +}): Promise; +export declare function createJsFileWithRollupCom(source: string, target: string, { verbose }: { verbose: boolean; }): Promise; export declare function transpileJsAndBuildWasmCom(target: string, { verbose }: { diff --git a/lib/cli/cli.js b/lib/cli/cli.js index db1a22204..13ab11dac 100755 --- a/lib/cli/cli.js +++ b/lib/cli/cli.js @@ -45,7 +45,7 @@ program .argument("[source]", "Contract to build.", "src/index.js") .argument("[target]", "Target file path and name. The default corresponds to contract.js", "build/contract.wasm") .option("--verbose", "Whether to print more verbose output.", false) - .action(createJsFileWithRullupCom)) + .action(createJsFileWithRollupCom)) .addCommand(new Command("transpileJsAndBuildWasm") .usage("[source] [target]") .description("Transpiles the target javascript file into .c and .h using QJSC then compiles that into wasm using clang") @@ -90,58 +90,64 @@ function ensureTargetDirExists(target) { fs.mkdirSync(targetDir, {}); } export async function validateCom(source, { verbose = false }) { - signal.await(`Validating ${source} contract...`); + const signale = new Signale({ scope: "validate", interactive: !verbose }); + signale.await(`Validating ${source} contract...`); if (!await validateContract(source, verbose)) { process.exit(1); } } export async function checkTypescriptCom(source, { verbose = false }) { + const signale = new Signale({ scope: "checkTypescript", interactive: !verbose }); const sourceExt = source.split(".").pop(); if (sourceExt !== "ts") { - signal.info(`Source file is not a typescript file ${source}`); + signale.info(`Source file is not a typescript file ${source}`); return; } - signal.await(`Typechecking ${source} with tsc...`); + signale.await(`Typechecking ${source} with tsc...`); await checkTsBuildWithTsc(source, verbose); } -export async function generateAbi(source, target, packageJson, tsConfig) { +export async function generateAbi(source, target, packageJson, tsConfig, { verbose = false }) { + const signale = new Signale({ scope: "generateAbi", interactive: !verbose }); const sourceExt = source.split(".").pop(); if (sourceExt !== "ts") { - signal.info(`Skipping ABI generation as source file is not a typescript file ${source}`); + signale.info(`Skipping ABI generation as source file is not a typescript file ${source}`); return; } - signal.await("Generating ABI..."); + signale.await("Generating ABI..."); const abi = runAbiCompilerPlugin(source, packageJson, tsConfig); fs.writeFileSync(getContractAbi(target), JSON.stringify(abi, null, 2)); - signal.success(`Generated ${getContractAbi(target)} ABI successfully!`); + signale.success(`Generated ${getContractAbi(target)} ABI successfully!`); } -export async function createJsFileWithRullupCom(source, target, { verbose = false }) { +export async function createJsFileWithRollupCom(source, target, { verbose = false }) { + const signale = new Signale({ scope: "createJsFileWithRollup", interactive: !verbose }); requireTargetExt(target); ensureTargetDirExists(target); - signal.await(`Creating ${source} file with Rollup...`); + signale.await(`Creating ${source} file with Rollup...`); await createJsFileWithRullup(source, getRollupTarget(target), verbose); } export async function transpileJsAndBuildWasmCom(target, { verbose = false }) { + const signale = new Signale({ scope: "transpileJsAndBuildWasm", interactive: !verbose }); requireTargetExt(target); ensureTargetDirExists(target); - signal.await(`Creating ${getQjscTarget(target)} file with QJSC...`); + signale.await(`Creating ${getQjscTarget(target)} file with QJSC...`); await createHeaderFileWithQjsc(getRollupTarget(target), getQjscTarget(target), verbose); - signal.await("Generating methods.h file..."); + signale.await("Generating methods.h file..."); await createMethodsHeaderFile(getRollupTarget(target), verbose); - signal.await(`Creating ${getContractTarget(target)} contract...`); + signale.await(`Creating ${getContractTarget(target)} contract...`); await createWasmContract(getQjscTarget(target), getContractTarget(target), verbose); - signal.await("Executing wasi-stub..."); + signale.await("Executing wasi-stub..."); await wasiStubContract(getContractTarget(target), verbose); - signal.success(`Generated ${getContractTarget(target)} contract successfully!`); + signale.success(`Generated ${getContractTarget(target)} contract successfully!`); } export async function buildCom(source, target, packageJson, tsConfig, { verbose = false }) { + const signale = new Signale({ scope: "build", interactive: !verbose }); requireTargetExt(target); - signal.await(`Building ${source} contract...`); + signale.await(`Building ${source} contract...`); await checkTypescriptCom(source, { verbose }); - await generateAbi(source, target, packageJson, tsConfig); + await generateAbi(source, target, packageJson, tsConfig, { verbose }); ensureTargetDirExists(target); await validateCom(source, { verbose }); - await createJsFileWithRullupCom(source, target, { verbose }); + await createJsFileWithRollupCom(source, target, { verbose }); await transpileJsAndBuildWasmCom(target, { verbose }); } async function checkTsBuildWithTsc(sourceFileWithPath, verbose = false) { diff --git a/near-contract-standards/lib/non_fungible_token/approval/index.d.ts b/near-contract-standards/lib/non_fungible_token/approval/index.d.ts index 50f26c616..31bf77ced 100644 --- a/near-contract-standards/lib/non_fungible_token/approval/index.d.ts +++ b/near-contract-standards/lib/non_fungible_token/approval/index.d.ts @@ -36,7 +36,7 @@ export interface NonFungibleTokenApproval { nft_approve({ token_id, account_id, msg, }: { token_id: TokenId; account_id: AccountId; - msg: Option; + msg?: string; }): Option; /** Revoke an approved account for a specific token. * @@ -80,6 +80,6 @@ export interface NonFungibleTokenApproval { nft_is_approved({ token_id, approved_account_id, approval_id, }: { token_id: TokenId; approved_account_id: AccountId; - approval_id: Option; + approval_id?: bigint; }): boolean; } diff --git a/near-contract-standards/lib/non_fungible_token/core/index.d.ts b/near-contract-standards/lib/non_fungible_token/core/index.d.ts index d12f0030f..e3158367d 100644 --- a/near-contract-standards/lib/non_fungible_token/core/index.d.ts +++ b/near-contract-standards/lib/non_fungible_token/core/index.d.ts @@ -35,8 +35,8 @@ export interface NonFungibleTokenCore { nft_transfer({ receiver_id, token_id, approval_id, memo, }: { receiver_id: AccountId; token_id: TokenId; - approval_id: Option; - memo: Option; + approval_id?: bigint; + memo?: string; }): any; /** Transfer token and call a method on a receiver contract. A successful * workflow will end in a success execution outcome to the callback on the NFT @@ -74,8 +74,8 @@ export interface NonFungibleTokenCore { nft_transfer_call({ receiver_id, token_id, approval_id, memo, }: { receiver_id: AccountId; token_id: TokenId; - approval_id: Option; - memo: Option; + approval_id?: bigint; + memo?: string; msg: string; }): any; /** Returns the token with the given `token_id` or `null` if no such token. */ diff --git a/near-contract-standards/lib/non_fungible_token/core/resolver.d.ts b/near-contract-standards/lib/non_fungible_token/core/resolver.d.ts index d871efa3a..7dd48c2bc 100644 --- a/near-contract-standards/lib/non_fungible_token/core/resolver.d.ts +++ b/near-contract-standards/lib/non_fungible_token/core/resolver.d.ts @@ -1,6 +1,5 @@ import { AccountId } from "near-sdk-js"; import { TokenId } from "../token"; -import { Option } from "../utils"; /** Used when an NFT is transferred using `nft_transfer_call`. This is the method that's called after `nft_on_transfer`. This interface is implemented on the NFT contract. */ export interface NonFungibleTokenResolver { /** Finalize an `nft_transfer_call` chain of cross-contract calls. @@ -33,8 +32,8 @@ export interface NonFungibleTokenResolver { previous_owner_id: AccountId; receiver_id: AccountId; token_id: TokenId; - approved_account_ids: Option<{ + approved_account_ids?: { [approval: AccountId]: bigint; - }>; + }; }): boolean; } diff --git a/near-contract-standards/lib/non_fungible_token/enumeration/index.d.ts b/near-contract-standards/lib/non_fungible_token/enumeration/index.d.ts index 5698989b8..fa5a320b2 100644 --- a/near-contract-standards/lib/non_fungible_token/enumeration/index.d.ts +++ b/near-contract-standards/lib/non_fungible_token/enumeration/index.d.ts @@ -11,8 +11,8 @@ export interface NonFungibleTokenEnumeration { * @returns - An array of Token objects, as described in Core standard */ nft_tokens({ from_index, limit, }: { - from_index: number | null; - limit: number | null; + from_index?: number; + limit?: number; }): Token[]; /** Get number of tokens owned by a given account * @@ -31,7 +31,7 @@ export interface NonFungibleTokenEnumeration { */ nft_tokens_for_owner({ account_id, from_index, limit, }: { account_id: AccountId; - from_index: number; - limit: number; + from_index?: number; + limit?: number; }): Token[]; } diff --git a/near-contract-standards/lib/non_fungible_token/events.d.ts b/near-contract-standards/lib/non_fungible_token/events.d.ts index 9fb2b3eed..59f8b49e2 100644 --- a/near-contract-standards/lib/non_fungible_token/events.d.ts +++ b/near-contract-standards/lib/non_fungible_token/events.d.ts @@ -16,7 +16,6 @@ import { AccountId } from "near-sdk-js"; import { NearEvent } from "../event"; import { TokenId } from "./token"; -import { Option } from "./utils"; export declare type Nep171EventKind = NftMint[] | NftTransfer[] | NftBurn[]; export declare class Nep171Event extends NearEvent { version: string; @@ -27,8 +26,8 @@ export declare class Nep171Event extends NearEvent { export declare class NftMint { owner_id: AccountId; token_ids: TokenId[]; - memo: Option; - constructor(owner_id: AccountId, token_ids: TokenId[], memo: Option); + memo?: string; + constructor(owner_id: AccountId, token_ids: TokenId[], memo?: string); /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit(): void; @@ -42,9 +41,9 @@ export declare class NftTransfer { old_owner_id: AccountId; new_owner_id: AccountId; token_ids: TokenId[]; - authorized_id: Option; - memo: Option; - constructor(old_owner_id: AccountId, new_owner_id: AccountId, token_ids: TokenId[], authorized_id: Option, memo: Option); + authorized_id?: AccountId; + memo?: string; + constructor(old_owner_id: AccountId, new_owner_id: AccountId, token_ids: TokenId[], authorized_id?: AccountId, memo?: string); /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit(): void; @@ -56,8 +55,9 @@ export declare class NftTransfer { export declare class NftBurn { owner_id: AccountId; token_ids: TokenId[]; - memo: Option; - constructor(owner_id: AccountId, token_ids: TokenId[], authorized_id: Option, memo: Option); + authorized_id?: string; + memo?: string; + constructor(owner_id: AccountId, token_ids: TokenId[], authorized_id?: string, memo?: string); /** Logs the event to the host. This is required to ensure that the event is triggered * and to consume the event. */ emit(): void; diff --git a/near-contract-standards/lib/non_fungible_token/events.js b/near-contract-standards/lib/non_fungible_token/events.js index 1bf0dfb3e..69622fd56 100644 --- a/near-contract-standards/lib/non_fungible_token/events.js +++ b/near-contract-standards/lib/non_fungible_token/events.js @@ -50,6 +50,7 @@ export class NftBurn { constructor(owner_id, token_ids, authorized_id, memo) { this.owner_id = owner_id; this.token_ids = token_ids; + this.authorized_id = authorized_id; this.memo = memo; } /** Logs the event to the host. This is required to ensure that the event is triggered diff --git a/near-contract-standards/lib/non_fungible_token/impl.d.ts b/near-contract-standards/lib/non_fungible_token/impl.d.ts index 3f9960079..f4500d937 100644 --- a/near-contract-standards/lib/non_fungible_token/impl.d.ts +++ b/near-contract-standards/lib/non_fungible_token/impl.d.ts @@ -30,16 +30,16 @@ export declare class NonFungibleToken implements NonFungibleTokenCore, NonFungib nft_total_supply(): number; private enum_get_token; nft_tokens({ from_index, limit, }: { - from_index: number | null; - limit: number | null; + from_index?: number; + limit?: number; }): Token[]; nft_supply_for_owner({ account_id }: { account_id: AccountId; }): number; nft_tokens_for_owner({ account_id, from_index, limit, }: { account_id: AccountId; - from_index: number; - limit: number; + from_index?: number; + limit?: number; }): Token[]; nft_approve({ token_id, account_id, msg, }: { token_id: TokenId; @@ -56,29 +56,29 @@ export declare class NonFungibleToken implements NonFungibleTokenCore, NonFungib nft_is_approved({ token_id, approved_account_id, approval_id, }: { token_id: TokenId; approved_account_id: AccountId; - approval_id: Option; + approval_id?: bigint; }): boolean; - init(owner_by_id_prefix: IntoStorageKey, owner_id: AccountId, token_metadata_prefix: Option, enumeration_prefix: Option, approval_prefix: Option): void; + init(owner_by_id_prefix: IntoStorageKey, owner_id: AccountId, token_metadata_prefix?: IntoStorageKey, enumeration_prefix?: IntoStorageKey, approval_prefix?: IntoStorageKey): void; static reconstruct(data: NonFungibleToken): NonFungibleToken; measure_min_token_storage_cost(): void; internal_transfer_unguarded(token_id: TokenId, from: AccountId, to: AccountId): void; - internal_transfer(sender_id: AccountId, receiver_id: AccountId, token_id: TokenId, approval_id: Option, memo: Option): [AccountId, { + internal_transfer(sender_id: AccountId, receiver_id: AccountId, token_id: TokenId, approval_id?: bigint, memo?: string): [AccountId, Option<{ [approvals: AccountId]: bigint; - } | null]; - static emit_transfer(owner_id: AccountId, receiver_id: AccountId, token_id: TokenId, sender_id: Option, memo: Option): void; - internal_mint(token_id: TokenId, token_owner_id: AccountId, token_metadata: Option): Token; - internal_mint_with_refund(token_id: TokenId, token_owner_id: AccountId, token_metadata: Option, refund_id: Option): Token; + }>]; + static emit_transfer(owner_id: AccountId, receiver_id: AccountId, token_id: TokenId, sender_id?: AccountId, memo?: string): void; + internal_mint(token_id: TokenId, token_owner_id: AccountId, token_metadata?: TokenMetadata): Token; + internal_mint_with_refund(token_id: TokenId, token_owner_id: AccountId, token_metadata?: TokenMetadata, refund_id?: string): Token; nft_transfer({ receiver_id, token_id, approval_id, memo, }: { receiver_id: AccountId; token_id: TokenId; - approval_id: Option; - memo: Option; + approval_id?: bigint; + memo?: string; }): void; nft_transfer_call({ receiver_id, token_id, approval_id, memo, msg, }: { receiver_id: AccountId; token_id: TokenId; - approval_id: Option; - memo: Option; + approval_id?: bigint; + memo?: string; msg: string; }): NearPromise; nft_token({ token_id }: { @@ -88,9 +88,9 @@ export declare class NonFungibleToken implements NonFungibleTokenCore, NonFungib previous_owner_id: AccountId; receiver_id: AccountId; token_id: TokenId; - approved_account_ids: Option<{ + approved_account_ids?: { [approvals: AccountId]: bigint; - }>; + }; }): boolean; } export declare type StorageKey = TokensPerOwner | TokenPerOwnerInner; diff --git a/near-contract-standards/lib/non_fungible_token/impl.js b/near-contract-standards/lib/non_fungible_token/impl.js index 2a6217d17..3c98d0eb0 100644 --- a/near-contract-standards/lib/non_fungible_token/impl.js +++ b/near-contract-standards/lib/non_fungible_token/impl.js @@ -54,9 +54,9 @@ export class NonFungibleToken { return new Token(token_id, owner_id, metadata, approved_account_ids); } nft_tokens({ from_index, limit, }) { - const start_index = from_index === null ? 0 : from_index; + const start_index = from_index === undefined ? 0 : from_index; assert(this.owner_by_id.length >= start_index, "Out of bounds, please use a smaller from_index."); - let l = limit === null ? 2 ** 32 : limit; + let l = limit === undefined ? 2 ** 32 : limit; assert(l > 0, "limit must be greater than 0."); l = Math.min(l, this.owner_by_id.length - start_index); const ret = []; @@ -77,14 +77,14 @@ export class NonFungibleToken { } nft_tokens_for_owner({ account_id, from_index, limit, }) { const tokens_per_owner = this.tokens_per_owner; - assert(tokens_per_owner !== null, "Could not find tokens_per_owner when calling a method on the enumeration standard."); + assert(tokens_per_owner !== undefined, "Could not find tokens_per_owner when calling a method on the enumeration standard."); const token_set = tokens_per_owner.get(account_id, { reconstructor: UnorderedSet.reconstruct, }); assert(token_set !== null, "Token set is empty"); - const start_index = from_index === null ? 0 : from_index; + const start_index = from_index === undefined ? 0 : from_index; assert(token_set.length >= start_index, "Out of bounds, please use a smaller from_index."); - let l = limit === null ? 2 ** 32 : limit; + let l = limit === undefined ? 2 ** 32 : limit; assert(l > 0, "limit must be greater than 0."); l = Math.min(l, token_set.length - start_index); const ret = []; @@ -134,8 +134,7 @@ export class NonFungibleToken { delete approved_account_ids[account_id]; if (Object.keys(approved_account_ids).length === 0) { approvals_by_id.remove(token_id); - new_approved_account_ids_size = - serialize(approved_account_ids).length; + new_approved_account_ids_size = serialize(approved_account_ids).length; } else { approvals_by_id.set(token_id, approved_account_ids); @@ -196,7 +195,7 @@ export class NonFungibleToken { this.token_metadata_by_id = token_metadata_prefix ? new LookupMap(token_metadata_prefix.into_storage_key()) : null; - this.tokens_per_owner = new LookupMap(enumeration_prefix.into_storage_key()); + this.tokens_per_owner = enumeration_prefix ? new LookupMap(enumeration_prefix.into_storage_key()) : null; this.approvals_by_id = approvals_by_id; this.next_approval_id_by_id = next_approval_id_by_id; this.measure_min_token_storage_cost(); @@ -304,11 +303,11 @@ export class NonFungibleToken { if (!actual_approval_id) { throw new Error("Sender not approved"); } - assert(approval_id == null || approval_id == actual_approval_id, `The actual approval_id ${actual_approval_id} is different from the given ${approval_id}`); + assert(approval_id === undefined || approval_id == actual_approval_id, `The actual approval_id ${actual_approval_id} is different from the given ${approval_id}`); sender_id_authorized = sender_id; } else { - sender_id_authorized = null; + sender_id_authorized = undefined; } assert(owner_id != receiver_id, "Current and next owner must differ"); this.internal_transfer_unguarded(token_id, owner_id, receiver_id); @@ -316,11 +315,11 @@ export class NonFungibleToken { return [owner_id, approved_account_ids]; } static emit_transfer(owner_id, receiver_id, token_id, sender_id, memo) { - new NftTransfer(owner_id, receiver_id, [token_id], sender_id && sender_id == owner_id ? sender_id : null, memo).emit(); + new NftTransfer(owner_id, receiver_id, [token_id], sender_id && sender_id == owner_id ? sender_id : undefined, memo).emit(); } internal_mint(token_id, token_owner_id, token_metadata) { const token = this.internal_mint_with_refund(token_id, token_owner_id, token_metadata, near.predecessorAccountId()); - new NftMint(token.owner_id, [token.token_id], null).emit(); + new NftMint(token.owner_id, [token.token_id]).emit(); return token; } internal_mint_with_refund(token_id, token_owner_id, token_metadata, refund_id) { @@ -328,7 +327,7 @@ export class NonFungibleToken { if (refund_id) { initial_storage_usage = [refund_id, near.storageUsage()]; } - if (this.token_metadata_by_id && token_metadata == null) { + if (this.token_metadata_by_id && token_metadata === undefined) { throw new Error("Must provide metadata"); } if (this.owner_by_id.get(token_id)) { @@ -347,7 +346,7 @@ export class NonFungibleToken { token_ids.set(token_id); this.tokens_per_owner.set(owner_id, token_ids); } - const approved_account_ids = this.approvals_by_id ? {} : null; + const approved_account_ids = this.approvals_by_id ? {} : undefined; if (initial_storage_usage) { const [id, storage_usage] = initial_storage_usage; refund_deposit_to_account(near.storageUsage() - storage_usage, id); diff --git a/near-contract-standards/lib/non_fungible_token/metadata.d.ts b/near-contract-standards/lib/non_fungible_token/metadata.d.ts index f0d834401..614747f37 100644 --- a/near-contract-standards/lib/non_fungible_token/metadata.d.ts +++ b/near-contract-standards/lib/non_fungible_token/metadata.d.ts @@ -1,5 +1,4 @@ import { Bytes } from "near-sdk-js"; -import { Option } from "./utils"; /** This spec can be treated like a version of the standard. */ export declare const NFT_METADATA_SPEC = "nft-1.0.0"; /** Metadata for the NFT contract itself. */ @@ -7,41 +6,41 @@ export declare class NFTContractMetadata { spec: string; name: string; symbol: string; - icon: Option; - base_uri: Option; - reference: Option; - reference_hash: Option; + icon?: string; + base_uri?: string; + reference?: string; + reference_hash?: Bytes; constructor(); - init(spec: string, name: string, symbol: string, icon: Option, base_uri: Option, reference: Option, reference_hash: Option): void; + init(spec: string, name: string, symbol: string, icon?: string, base_uri?: string, reference?: string, reference_hash?: Bytes): void; assert_valid(): void; static reconstruct(data: NFTContractMetadata): NFTContractMetadata; } /** Metadata on the individual token level. */ export declare class TokenMetadata { - title: Option; - description: Option; - media: Option; - media_hash: Option; - copies: Option; - issued_at: Option; - expires_at: Option; - starts_at: Option; - updated_at: Option; - extra: Option; - reference: Option; - reference_hash: Option; - constructor(title: Option, // ex. "Arch Nemesis: Mail Carrier" or "Parcel #5055" - description: Option, // free-form description - media: Option, // URL to associated media, preferably to decentralized, content-addressed storage - media_hash: Option, // Base64-encoded sha256 hash of content referenced by the `media` field. Required if `media` is included. - copies: Option, // number of copies of this set of metadata in existence when token was minted. - issued_at: Option, // ISO 8601 datetime when token was issued or minted - expires_at: Option, // ISO 8601 datetime when token expires - starts_at: Option, // ISO 8601 datetime when token starts being valid - updated_at: Option, // ISO 8601 datetime when token was last updated - extra: Option, // anything extra the NFT wants to store on-chain. Can be stringified JSON. - reference: Option, // URL to an off-chain JSON file with more info. - reference_hash: Option); + title?: string; + description?: string; + media?: string; + media_hash?: Bytes; + copies?: bigint; + issued_at?: string; + expires_at?: string; + starts_at?: string; + updated_at?: string; + extra?: string; + reference?: string; + reference_hash?: Bytes; + constructor(title?: string, // ex. "Arch Nemesis: Mail Carrier" or "Parcel #5055" + description?: string, // free-form description + media?: string, // URL to associated media, preferably to decentralized, content-addressed storage + media_hash?: Bytes, // Base64-encoded sha256 hash of content referenced by the `media` field. Required if `media` is included. + copies?: bigint, // number of copies of this set of metadata in existence when token was minted. + issued_at?: string, // ISO 8601 datetime when token was issued or minted + expires_at?: string, // ISO 8601 datetime when token expires + starts_at?: string, // ISO 8601 datetime when token starts being valid + updated_at?: string, // ISO 8601 datetime when token was last updated + extra?: string, // anything extra the NFT wants to store on-chain. Can be stringified JSON. + reference?: string, // URL to an off-chain JSON file with more info. + reference_hash?: Bytes); assert_valid(): void; static reconstruct(data: TokenMetadata): TokenMetadata; } diff --git a/near-contract-standards/lib/non_fungible_token/metadata.js b/near-contract-standards/lib/non_fungible_token/metadata.js index e350efd9f..7c28a414d 100644 --- a/near-contract-standards/lib/non_fungible_token/metadata.js +++ b/near-contract-standards/lib/non_fungible_token/metadata.js @@ -7,10 +7,10 @@ export class NFTContractMetadata { this.spec = NFT_METADATA_SPEC; this.name = ""; this.symbol = ""; - this.icon = null; - this.base_uri = null; - this.reference = null; - this.reference_hash = null; + this.icon = undefined; + this.base_uri = undefined; + this.reference = undefined; + this.reference_hash = undefined; } init(spec, name, symbol, icon, base_uri, reference, reference_hash) { this.spec = spec; @@ -23,8 +23,8 @@ export class NFTContractMetadata { } assert_valid() { assert(this.spec == NFT_METADATA_SPEC, "Spec is not NFT metadata"); - assert((this.reference != null) == (this.reference_hash != null), "Reference and reference hash must be present"); - if (this.reference_hash != null) { + assert((this.reference !== undefined) == (this.reference_hash !== undefined), "Reference and reference hash must be present"); + if (this.reference_hash !== undefined) { assert(this.reference_hash.length == 32, "Hash has to be 32 bytes"); } } @@ -63,12 +63,12 @@ export class TokenMetadata { this.reference_hash = reference_hash; } assert_valid() { - assert((this.media != null) == (this.media_hash != null), "Media and media hash must be present"); - if (this.media_hash != null) { + assert((this.media !== undefined) == (this.media_hash !== undefined), "Media and media hash must be present"); + if (this.media_hash !== undefined) { assert(this.media_hash.length == 32, "Media hash has to be 32 bytes"); } - assert((this.reference != null) == (this.reference_hash != null), "Reference and reference hash must be present"); - if (this.reference_hash != null) { + assert((this.reference !== undefined) == (this.reference_hash !== undefined), "Reference and reference hash must be present"); + if (this.reference_hash !== undefined) { assert(this.reference_hash.length == 32, "Reference hash has to be 32 bytes"); } } diff --git a/near-contract-standards/lib/non_fungible_token/token.d.ts b/near-contract-standards/lib/non_fungible_token/token.d.ts index fcbdbfd3d..cbe49f357 100644 --- a/near-contract-standards/lib/non_fungible_token/token.d.ts +++ b/near-contract-standards/lib/non_fungible_token/token.d.ts @@ -1,17 +1,16 @@ import { TokenMetadata } from "./metadata"; import { AccountId } from "near-sdk-js"; -import { Option } from "./utils"; /** Note that token IDs for NFTs are strings on NEAR. It's still fine to use autoincrementing numbers as unique IDs if desired, but they should be stringified. This is to make IDs more future-proof as chain-agnostic conventions and standards arise, and allows for more flexibility with considerations like bridging NFTs across chains, etc. */ export declare type TokenId = string; /** In this implementation, the Token struct takes two extensions standards (metadata and approval) as optional fields, as they are frequently used in modern NFTs. */ export declare class Token { token_id: TokenId; owner_id: AccountId; - metadata: Option; - approved_account_ids: Option<{ + metadata?: TokenMetadata; + approved_account_ids?: { [approved_account_id: AccountId]: bigint; - }>; - constructor(token_id: TokenId, owner_id: AccountId, metadata: Option, approved_account_ids: Option<{ + }; + constructor(token_id: TokenId, owner_id: AccountId, metadata?: TokenMetadata, approved_account_ids?: { [approved_account_id: AccountId]: bigint; - }>); + }); } diff --git a/near-contract-standards/src/non_fungible_token/approval/index.ts b/near-contract-standards/src/non_fungible_token/approval/index.ts index a5a4f0dbe..b148bdb44 100644 --- a/near-contract-standards/src/non_fungible_token/approval/index.ts +++ b/near-contract-standards/src/non_fungible_token/approval/index.ts @@ -41,7 +41,7 @@ export interface NonFungibleTokenApproval { }: { token_id: TokenId; account_id: AccountId; - msg: Option; + msg?: string; }): Option; /** Revoke an approved account for a specific token. @@ -93,6 +93,6 @@ export interface NonFungibleTokenApproval { }: { token_id: TokenId; approved_account_id: AccountId; - approval_id: Option; + approval_id?: bigint; }): boolean; } diff --git a/near-contract-standards/src/non_fungible_token/core/index.ts b/near-contract-standards/src/non_fungible_token/core/index.ts index 630a35240..624be9f97 100644 --- a/near-contract-standards/src/non_fungible_token/core/index.ts +++ b/near-contract-standards/src/non_fungible_token/core/index.ts @@ -41,8 +41,8 @@ export interface NonFungibleTokenCore { }: { receiver_id: AccountId; token_id: TokenId; - approval_id: Option; - memo: Option; + approval_id?: bigint; + memo?: string; }); /** Transfer token and call a method on a receiver contract. A successful @@ -86,8 +86,8 @@ export interface NonFungibleTokenCore { }: { receiver_id: AccountId; token_id: TokenId; - approval_id: Option; - memo: Option; + approval_id?: bigint; + memo?: string; msg: string; }); diff --git a/near-contract-standards/src/non_fungible_token/core/resolver.ts b/near-contract-standards/src/non_fungible_token/core/resolver.ts index e7253e692..f073653c3 100644 --- a/near-contract-standards/src/non_fungible_token/core/resolver.ts +++ b/near-contract-standards/src/non_fungible_token/core/resolver.ts @@ -1,6 +1,5 @@ import { AccountId } from "near-sdk-js"; import { TokenId } from "../token"; -import { Option } from "../utils"; /** Used when an NFT is transferred using `nft_transfer_call`. This is the method that's called after `nft_on_transfer`. This interface is implemented on the NFT contract. */ export interface NonFungibleTokenResolver { @@ -39,6 +38,6 @@ export interface NonFungibleTokenResolver { previous_owner_id: AccountId; receiver_id: AccountId; token_id: TokenId; - approved_account_ids: Option<{ [approval: AccountId]: bigint }>; + approved_account_ids?: { [approval: AccountId]: bigint }; }): boolean; } diff --git a/near-contract-standards/src/non_fungible_token/enumeration/index.ts b/near-contract-standards/src/non_fungible_token/enumeration/index.ts index 83feb2476..d713f78b2 100644 --- a/near-contract-standards/src/non_fungible_token/enumeration/index.ts +++ b/near-contract-standards/src/non_fungible_token/enumeration/index.ts @@ -16,8 +16,8 @@ export interface NonFungibleTokenEnumeration { from_index, limit, }: { - from_index: number | null; // default: "0" - limit: number | null; // default: unlimited (could fail due to gas limit) + from_index?: number; // default: "0" + limit?: number; // default: unlimited (could fail due to gas limit) }): Token[]; /** Get number of tokens owned by a given account @@ -40,7 +40,7 @@ export interface NonFungibleTokenEnumeration { limit, }: { account_id: AccountId; - from_index: number; // default: "0" - limit: number; // default: unlimited (could fail due to gas limit) + from_index?: number; // default: "0" + limit?: number; // default: unlimited (could fail due to gas limit) }): Token[]; } diff --git a/near-contract-standards/src/non_fungible_token/events.ts b/near-contract-standards/src/non_fungible_token/events.ts index 7eea7b2e8..f8d5e8109 100644 --- a/near-contract-standards/src/non_fungible_token/events.ts +++ b/near-contract-standards/src/non_fungible_token/events.ts @@ -16,7 +16,6 @@ import { AccountId } from "near-sdk-js"; import { NearEvent } from "../event"; import { TokenId } from "./token"; -import { Option } from "./utils"; export type Nep171EventKind = NftMint[] | NftTransfer[] | NftBurn[]; @@ -36,7 +35,7 @@ export class NftMint { constructor( public owner_id: AccountId, public token_ids: TokenId[], - public memo: Option + public memo?: string ) {} /** Logs the event to the host. This is required to ensure that the event is triggered @@ -59,8 +58,8 @@ export class NftTransfer { public old_owner_id: AccountId, public new_owner_id: AccountId, public token_ids: TokenId[], - public authorized_id: Option, - public memo: Option + public authorized_id?: AccountId, + public memo?: string ) {} /** Logs the event to the host. This is required to ensure that the event is triggered @@ -81,8 +80,8 @@ export class NftBurn { constructor( public owner_id: AccountId, public token_ids: TokenId[], - authorized_id: Option, - public memo: Option + public authorized_id?: string, + public memo?: string ) {} /** Logs the event to the host. This is required to ensure that the event is triggered diff --git a/near-contract-standards/src/non_fungible_token/impl.ts b/near-contract-standards/src/non_fungible_token/impl.ts index 86f3d7332..47c2dc58e 100644 --- a/near-contract-standards/src/non_fungible_token/impl.ts +++ b/near-contract-standards/src/non_fungible_token/impl.ts @@ -103,15 +103,15 @@ export class NonFungibleToken from_index, limit, }: { - from_index: number | null; - limit: number | null; + from_index?: number; + limit?: number; }): Token[] { - const start_index = from_index === null ? 0 : from_index; + const start_index = from_index === undefined ? 0 : from_index; assert( this.owner_by_id.length >= start_index, "Out of bounds, please use a smaller from_index." ); - let l = limit === null ? 2 ** 32 : limit; + let l = limit === undefined ? 2 ** 32 : limit; assert(l > 0, "limit must be greater than 0."); l = Math.min(l, this.owner_by_id.length - start_index); const ret: Token[] = []; @@ -142,12 +142,12 @@ export class NonFungibleToken limit, }: { account_id: AccountId; - from_index: number; - limit: number; + from_index?: number; + limit?: number; }): Token[] { const tokens_per_owner = this.tokens_per_owner; assert( - tokens_per_owner !== null, + tokens_per_owner !== undefined, "Could not find tokens_per_owner when calling a method on the enumeration standard." ); const token_set = tokens_per_owner.get(account_id, { @@ -155,12 +155,12 @@ export class NonFungibleToken }); assert(token_set !== null, "Token set is empty"); - const start_index = from_index === null ? 0 : from_index; + const start_index = from_index === undefined ? 0 : from_index; assert( token_set.length >= start_index, "Out of bounds, please use a smaller from_index." ); - let l = limit === null ? 2 ** 32 : limit; + let l = limit === undefined ? 2 ** 32 : limit; assert(l > 0, "limit must be greater than 0."); l = Math.min(l, token_set.length - start_index); @@ -295,7 +295,7 @@ export class NonFungibleToken }: { token_id: TokenId; approved_account_id: AccountId; - approval_id: Option; + approval_id?: bigint; }): boolean { expect_token_found(this.owner_by_id.get(token_id)); @@ -323,9 +323,9 @@ export class NonFungibleToken init( owner_by_id_prefix: IntoStorageKey, owner_id: AccountId, - token_metadata_prefix: Option, - enumeration_prefix: Option, - approval_prefix: Option + token_metadata_prefix?: IntoStorageKey, + enumeration_prefix?: IntoStorageKey, + approval_prefix?: IntoStorageKey ) { let approvals_by_id: Option>; let next_approval_id_by_id: Option>; @@ -344,9 +344,9 @@ export class NonFungibleToken this.token_metadata_by_id = token_metadata_prefix ? new LookupMap(token_metadata_prefix.into_storage_key()) : null; - this.tokens_per_owner = new LookupMap( - enumeration_prefix.into_storage_key() - ); + this.tokens_per_owner = enumeration_prefix + ? new LookupMap(enumeration_prefix.into_storage_key()) + : null; this.approvals_by_id = approvals_by_id; this.next_approval_id_by_id = next_approval_id_by_id; this.measure_min_token_storage_cost(); @@ -479,9 +479,9 @@ export class NonFungibleToken sender_id: AccountId, receiver_id: AccountId, token_id: TokenId, - approval_id: Option, - memo: Option - ): [AccountId, { [approvals: AccountId]: bigint } | null] { + approval_id?: bigint, + memo?: string + ): [AccountId, Option<{ [approvals: AccountId]: bigint }>] { const owner_id = this.owner_by_id.get(token_id); if (owner_id == null) { throw new Error("Token not found"); @@ -489,7 +489,7 @@ export class NonFungibleToken const approved_account_ids = this.approvals_by_id?.remove(token_id); - let sender_id_authorized: Option; + let sender_id_authorized: string | undefined; if (sender_id != owner_id) { if (!approved_account_ids) { throw new Error("Unauthorized"); @@ -501,12 +501,12 @@ export class NonFungibleToken } assert( - approval_id == null || approval_id == actual_approval_id, + approval_id === undefined || approval_id == actual_approval_id, `The actual approval_id ${actual_approval_id} is different from the given ${approval_id}` ); sender_id_authorized = sender_id; } else { - sender_id_authorized = null; + sender_id_authorized = undefined; } assert(owner_id != receiver_id, "Current and next owner must differ"); this.internal_transfer_unguarded(token_id, owner_id, receiver_id); @@ -524,14 +524,14 @@ export class NonFungibleToken owner_id: AccountId, receiver_id: AccountId, token_id: TokenId, - sender_id: Option, - memo: Option + sender_id?: AccountId, + memo?: string ) { new NftTransfer( owner_id, receiver_id, [token_id], - sender_id && sender_id == owner_id ? sender_id : null, + sender_id && sender_id == owner_id ? sender_id : undefined, memo ).emit(); } @@ -539,7 +539,7 @@ export class NonFungibleToken internal_mint( token_id: TokenId, token_owner_id: AccountId, - token_metadata: Option + token_metadata?: TokenMetadata ): Token { const token = this.internal_mint_with_refund( token_id, @@ -547,21 +547,21 @@ export class NonFungibleToken token_metadata, near.predecessorAccountId() ); - new NftMint(token.owner_id, [token.token_id], null).emit(); + new NftMint(token.owner_id, [token.token_id]).emit(); return token; } internal_mint_with_refund( token_id: TokenId, token_owner_id: AccountId, - token_metadata: Option, - refund_id: Option + token_metadata?: TokenMetadata, + refund_id?: string ): Token { let initial_storage_usage: Option<[string, bigint]> = null; if (refund_id) { initial_storage_usage = [refund_id, near.storageUsage()]; } - if (this.token_metadata_by_id && token_metadata == null) { + if (this.token_metadata_by_id && token_metadata === undefined) { throw new Error("Must provide metadata"); } if (this.owner_by_id.get(token_id)) { @@ -584,7 +584,7 @@ export class NonFungibleToken this.tokens_per_owner.set(owner_id, token_ids); } - const approved_account_ids = this.approvals_by_id ? {} : null; + const approved_account_ids = this.approvals_by_id ? {} : undefined; if (initial_storage_usage) { const [id, storage_usage] = initial_storage_usage; refund_deposit_to_account(near.storageUsage() - storage_usage, id); @@ -600,8 +600,8 @@ export class NonFungibleToken }: { receiver_id: AccountId; token_id: TokenId; - approval_id: Option; - memo: Option; + approval_id?: bigint; + memo?: string; }) { assert_at_least_one_yocto(); const sender_id = near.predecessorAccountId(); @@ -617,8 +617,8 @@ export class NonFungibleToken }: { receiver_id: AccountId; token_id: TokenId; - approval_id: Option; - memo: Option; + approval_id?: bigint; + memo?: string; msg: string; }) { assert_at_least_one_yocto(); @@ -683,7 +683,7 @@ export class NonFungibleToken previous_owner_id: AccountId; receiver_id: AccountId; token_id: TokenId; - approved_account_ids: Option<{ [approvals: AccountId]: bigint }>; + approved_account_ids?: { [approvals: AccountId]: bigint }; }): boolean { let must_revert = false; let p: Bytes; diff --git a/near-contract-standards/src/non_fungible_token/token.ts b/near-contract-standards/src/non_fungible_token/token.ts index ec1b046c9..09f5d002f 100644 --- a/near-contract-standards/src/non_fungible_token/token.ts +++ b/near-contract-standards/src/non_fungible_token/token.ts @@ -1,6 +1,5 @@ import { TokenMetadata } from "./metadata"; import { AccountId } from "near-sdk-js"; -import { Option } from "./utils"; /** Note that token IDs for NFTs are strings on NEAR. It's still fine to use autoincrementing numbers as unique IDs if desired, but they should be stringified. This is to make IDs more future-proof as chain-agnostic conventions and standards arise, and allows for more flexibility with considerations like bridging NFTs across chains, etc. */ export type TokenId = string; @@ -10,9 +9,9 @@ export class Token { constructor( public token_id: TokenId, public owner_id: AccountId, - public metadata: Option, - public approved_account_ids: Option<{ + public metadata?: TokenMetadata, + public approved_account_ids?: { [approved_account_id: AccountId]: bigint; - }> + } ) {} } diff --git a/src/api.ts b/src/api.ts index 131b43a7e..1a20b4c83 100644 --- a/src/api.ts +++ b/src/api.ts @@ -661,26 +661,57 @@ export function promiseReturn(promiseIndex: PromiseIndex): void { env.promise_return(promiseIndex as unknown as bigint); } +/** + * Returns sha256 hash of given value + * @param value - value to be hashed, in Bytes + * @returns hash result in Bytes + */ export function sha256(value: Bytes): Bytes { env.sha256(value, 0); return env.read_register(0); } +/** + * Returns keccak256 hash of given value + * @param value - value to be hashed, in Bytes + * @returns hash result in Bytes + */ export function keccak256(value: Bytes): Bytes { env.keccak256(value, 0); return env.read_register(0); } +/** + * Returns keccak512 hash of given value + * @param value - value to be hashed, in Bytes + * @returns hash result in Bytes + */ export function keccak512(value: Bytes): Bytes { env.keccak512(value, 0); return env.read_register(0); } +/** + * Returns ripemd160 hash of given value + * @param value - value to be hashed, in Bytes + * @returns hash result in Bytes + */ export function ripemd160(value: Bytes): Bytes { env.ripemd160(value, 0); return env.read_register(0); } +/** + * Recovers an ECDSA signer address from a 32-byte message hash and a corresponding + * signature along with v recovery byte. Takes in an additional flag to check for + * malleability of the signature which is generally only ideal for transactions. + * + * @param hash - 32-byte message hash + * @param sig - signature + * @param v - number of recovery byte + * @param malleabilityFlag - whether to check malleability + * @returns 64 bytes representing the public key if the recovery was successful. + */ export function ecrecover( hash: Bytes, sig: Bytes, @@ -698,36 +729,96 @@ export function ecrecover( // NOTE: "env.panic(msg)" is not exported, use "throw Error(msg)" instead +/** + * Panic the transaction execution with given message + * @param msg - panic message in raw bytes, which should be a valid UTF-8 sequence + */ export function panicUtf8(msg: Bytes): never { env.panic_utf8(msg); } +/** + * Log the message in transaction logs + * @param msg - message in raw bytes, which should be a valid UTF-8 sequence + */ export function logUtf8(msg: Bytes) { env.log_utf8(msg); } +/** + * Log the message in transaction logs + * @param msg - message in raw bytes, which should be a valid UTF-16 sequence + */ export function logUtf16(msg: Bytes) { env.log_utf16(msg); } +/** + * Returns the number of staked NEAR of given validator, in yoctoNEAR + * @param accountId - validator's AccountID + * @returns - staked amount + */ export function validatorStake(accountId: Bytes): bigint { return env.validator_stake(accountId); } +/** + * Returns the number of staked NEAR of all validators, in yoctoNEAR + * @returns total staked amount + */ export function validatorTotalStake(): bigint { return env.validator_total_stake(); } +/** + * Computes multiexp on alt_bn128 curve using Pippenger's algorithm \sum_i + * mul_i g_{1 i} should be equal result. + * + * @param value - equence of (g1:G1, fr:Fr), where + * G1 is point (x:Fq, y:Fq) on alt_bn128, + * alt_bn128 is Y^2 = X^3 + 3 curve over Fq. + * `value` is encoded as packed, little-endian + * `[((u256, u256), u256)]` slice. + * + * @returns multi exp sum + */ export function altBn128G1Multiexp(value: Bytes): Bytes { env.alt_bn128_g1_multiexp(value, 0); return env.read_register(0); } +/** + * Computes sum for signed g1 group elements on alt_bn128 curve \sum_i + * (-1)^{sign_i} g_{1 i} should be equal result. + * + * @param value - sequence of (sign:bool, g1:G1), where + * G1 is point (x:Fq, y:Fq) on alt_bn128, + * alt_bn128 is Y^2 = X^3 + 3 curve over Fq. + * value` is encoded a as packed, little-endian + * `[((u256, u256), ((u256, u256), (u256, u256)))]` slice. + * + * @returns sum over Fq. + */ export function altBn128G1Sum(value: Bytes): Bytes { env.alt_bn128_g1_sum(value, 0); return env.read_register(0); } +/** + * Computes pairing check on alt_bn128 curve. + * \sum_i e(g_{1 i}, g_{2 i}) should be equal one (in additive notation), e(g1, g2) is Ate pairing + * + * @param value - sequence of (g1:G1, g2:G2), where + * G2 is Fr-ordered subgroup point (x:Fq2, y:Fq2) on alt_bn128 twist, + * alt_bn128 twist is Y^2 = X^3 + 3/(i+9) curve over Fq2 + * Fq2 is complex field element (re: Fq, im: Fq) + * G1 is point (x:Fq, y:Fq) on alt_bn128, + * alt_bn128 is Y^2 = X^3 + 3 curve over Fq + * `value` is encoded a as packed, little-endian + * `[((u256, u256), ((u256, u256), (u256, u256)))]` slice. + * + * @returns whether pairing check pass + */ export function altBn128PairingCheck(value: Bytes): boolean { return env.alt_bn128_pairing_check(value) === 1n; } diff --git a/src/cli/cli.ts b/src/cli/cli.ts index 846b4751a..53db365c0 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -58,7 +58,7 @@ program .argument("[source]", "Contract to build.", "src/index.js") .argument("[target]", "Target file path and name. The default corresponds to contract.js", "build/contract.wasm") .option("--verbose", "Whether to print more verbose output.", false) - .action(createJsFileWithRullupCom) + .action(createJsFileWithRollupCom) ) .addCommand( new Command("transpileJsAndBuildWasm") @@ -120,62 +120,73 @@ function ensureTargetDirExists(target: string): void { } export async function validateCom(source: string, { verbose = false }: { verbose: boolean }): Promise { - signal.await(`Validating ${source} contract...`); + const signale = new Signale({ scope: "validate", interactive: !verbose }); + + signale.await(`Validating ${source} contract...`); + if (!await validateContract(source, verbose)) { process.exit(1); } } export async function checkTypescriptCom(source: string, { verbose = false }: { verbose: boolean }): Promise { + const signale = new Signale({ scope: "checkTypescript", interactive: !verbose }); + const sourceExt = source.split(".").pop(); if (sourceExt !== "ts") { - signal.info(`Source file is not a typescript file ${source}`) + signale.info(`Source file is not a typescript file ${source}`) return; } - signal.await(`Typechecking ${source} with tsc...`); + signale.await(`Typechecking ${source} with tsc...`); await checkTsBuildWithTsc(source, verbose); } -export async function generateAbi(source: string, target: string, packageJson: string, tsConfig: string): Promise { +export async function generateAbi(source: string, target: string, packageJson: string, tsConfig: string, { verbose = false }: { verbose: boolean }): Promise { + const signale = new Signale({ scope: "generateAbi", interactive: !verbose }); + const sourceExt = source.split(".").pop(); if (sourceExt !== "ts") { - signal.info(`Skipping ABI generation as source file is not a typescript file ${source}`) + signale.info(`Skipping ABI generation as source file is not a typescript file ${source}`) return; } - signal.await("Generating ABI..."); + signale.await("Generating ABI..."); const abi = runAbiCompilerPlugin(source, packageJson, tsConfig); fs.writeFileSync(getContractAbi(target), JSON.stringify(abi, null, 2)); - signal.success(`Generated ${getContractAbi(target)} ABI successfully!`); + signale.success(`Generated ${getContractAbi(target)} ABI successfully!`); } -export async function createJsFileWithRullupCom(source: string, target: string, { verbose = false }: { verbose: boolean }): Promise { +export async function createJsFileWithRollupCom(source: string, target: string, { verbose = false }: { verbose: boolean }): Promise { + const signale = new Signale({ scope: "createJsFileWithRollup", interactive: !verbose }); + requireTargetExt(target); ensureTargetDirExists(target); - signal.await(`Creating ${source} file with Rollup...`); + signale.await(`Creating ${source} file with Rollup...`); await createJsFileWithRullup(source, getRollupTarget(target), verbose); } export async function transpileJsAndBuildWasmCom(target: string, { verbose = false }: { verbose: boolean }): Promise { + const signale = new Signale({ scope: "transpileJsAndBuildWasm", interactive: !verbose }); + requireTargetExt(target); ensureTargetDirExists(target); - signal.await(`Creating ${getQjscTarget(target)} file with QJSC...`); + signale.await(`Creating ${getQjscTarget(target)} file with QJSC...`); await createHeaderFileWithQjsc(getRollupTarget(target), getQjscTarget(target), verbose); - signal.await("Generating methods.h file..."); + signale.await("Generating methods.h file..."); await createMethodsHeaderFile(getRollupTarget(target), verbose); - signal.await(`Creating ${getContractTarget(target)} contract...`); + signale.await(`Creating ${getContractTarget(target)} contract...`); await createWasmContract(getQjscTarget(target), getContractTarget(target), verbose); - signal.await("Executing wasi-stub..."); + signale.await("Executing wasi-stub..."); await wasiStubContract(getContractTarget(target), verbose); - signal.success(`Generated ${getContractTarget(target)} contract successfully!`); + signale.success(`Generated ${getContractTarget(target)} contract successfully!`); } export async function buildCom( @@ -185,19 +196,21 @@ export async function buildCom( tsConfig: string, { verbose = false }: { verbose: boolean } ): Promise { + const signale = new Signale({ scope: "build", interactive: !verbose }); + requireTargetExt(target); - signal.await(`Building ${source} contract...`); + signale.await(`Building ${source} contract...`); await checkTypescriptCom(source, { verbose }); - await generateAbi(source, target, packageJson, tsConfig); + await generateAbi(source, target, packageJson, tsConfig, { verbose }); ensureTargetDirExists(target); await validateCom(source, { verbose }); - await createJsFileWithRullupCom(source, target, { verbose }); + await createJsFileWithRollupCom(source, target, { verbose }); await transpileJsAndBuildWasmCom(target, { verbose }); } diff --git a/tsconfig.json b/tsconfig.json index 5f7397490..b9ba6dcdd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,6 +27,5 @@ "src/build-tools/include-bytes.ts", "src/cli/cli.ts", "src/cli/post-install.ts" - ], - "exclude": ["src/cli/**/*"] + ] }