diff --git a/.husky/pre-commit b/.husky/pre-commit index b15fa986d..091a73a76 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -3,3 +3,4 @@ . "$(dirname "$0")/_/dissalowed_words.sh" yarn run lint-staged + diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b45ebcbb..3b1d21d43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,8 @@ from `algosdk` and sends it to the network. - Added support for `vrf_verify` opcode to `Runtime`. IMPORTANT: the opcode assumes the proof is always valid thus it will always return 1. - Added program length check on app deploy on the basis of extra pages in `runtime`. - Added logic signature and arguments size check in `runtime`. +- Added support for `block` opcode to `Runtime`. +- Added blocks to `Runtime`. It simulates the block generation by using radnom bytes generator as the first seed. The following seeds are MD5 hash of the seed from the previous block. #### @algo-builder/web - Added support for logic signature to `executeTx` method of `Webmode` for AlgoSigner, MyAlgo Wallet and Wallet Connect. diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 8a98146e5..568b56418 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -34,9 +34,11 @@ "dependencies": { "@algo-builder/web": "workspace:*", "@nodelib/fs.walk": "^1.2.8", + "@types/crypto-js": "^4.1.1", "@types/json-bigint": "^1.0.1", "algosdk": "^1.22.0", "chalk": "^4.1.2", + "crypto-js": "^4.1.1", "debug": "^4.3.4", "elliptic": "^6.5.4", "hi-base32": "^0.5.1", diff --git a/packages/runtime/src/errors/errors-list.ts b/packages/runtime/src/errors/errors-list.ts index 897f80e5b..81d37555f 100644 --- a/packages/runtime/src/errors/errors-list.ts +++ b/packages/runtime/src/errors/errors-list.ts @@ -476,7 +476,7 @@ maximun uint128`, }, UNKNOWN_VRF_TYPE: { number: 1070, - message: "Unknown field", + message: "Unknown vrf_verify field", title: "Unknown field", description: "Unknow field for vrf_verify opcode", }, @@ -487,6 +487,12 @@ maximun uint128`, description: "Allowed extra pages range is from 0 to %maxExtraAppProgramPages% but got %extraPages% extra pages", }, + UNKNOWN_BLOCK_FIELD: { + number: 1072, + message: "Unknown block opcode field", + title: "Unknown field", + description: "Block opcode support only BlkTimestamp and BlkSeed field, got: %field", + } }; const runtimeGeneralErrors = { @@ -627,6 +633,18 @@ const runtimeGeneralErrors = { description: "Provided multisignature is invalid and was not able to authenticate the transaction", }, + PRODUCE_BLOCK: { + number: 1322, + message: "Produce block", + title: "Produce block", + description: "Runtime failed while trying to produce new block", + }, + INVALID_BLOCK: { + number: 1323, + message: "Invalid block", + title: "Invalid block", + description: "Provided round does not correspond to any existing block", + } }; const transactionErrors = { diff --git a/packages/runtime/src/interpreter/interpreter.ts b/packages/runtime/src/interpreter/interpreter.ts index 1ac06666f..7ddca8334 100644 --- a/packages/runtime/src/interpreter/interpreter.ts +++ b/packages/runtime/src/interpreter/interpreter.ts @@ -17,6 +17,7 @@ import { DEFAULT_STACK_ELEM, LOGIC_SIG_MAX_COST, MaxTEALVersion, + MaxTxnLife, MinVersionSupportC2CCall, TransactionTypeEnum, } from "../lib/constants"; @@ -170,7 +171,7 @@ export class Interpreter { if ( txAccounts?.find((buff) => compareArray(Uint8Array.from(buff), accountPk)) !== - undefined || + undefined || compareArray(accountPk, Uint8Array.from(this.runtime.ctx.tx.snd)) || // since tealv5, currentApplicationAddress is also allowed (directly) compareArray(accountPk, decodeAddress(getApplicationAddress(appID)).publicKey) || @@ -595,4 +596,28 @@ export class Interpreter { } return result; } + + /** + * This functions checks if the requested round is avaiable to access. If it is + * not throws an Error + * @param round: round number + * @returns void + */ + assertRoundIsAvailable(round: number): void { + let firstAvail = this.runtime.ctx.tx.lv - MaxTxnLife - 1; + if (firstAvail > this.runtime.ctx.tx.lv || firstAvail === 0) { + // early in chain's life + firstAvail = 1; + } + let lastAvail = this.runtime.ctx.tx.fv === undefined ? 0 : this.runtime.ctx.tx.fv - 1; + if (this.runtime.ctx.tx.fv === undefined || lastAvail > this.runtime.ctx.tx.fv) { + // txn had a 0 in FirstValid + lastAvail = 0; // So nothing will be available + } + if (firstAvail > round || round > lastAvail) { + throw new Error("round is not available"); + //todo: add better error + // throw error ("round %d is not available. It's outside [%d-%d]", r, firstAvail, lastAvail) + } + } } diff --git a/packages/runtime/src/interpreter/opcode-list.ts b/packages/runtime/src/interpreter/opcode-list.ts index e157c48f9..2ee29aaa8 100644 --- a/packages/runtime/src/interpreter/opcode-list.ts +++ b/packages/runtime/src/interpreter/opcode-list.ts @@ -29,6 +29,7 @@ import { ALGORAND_MAX_LOGS_LENGTH, AppParamDefined, AssetParamMap, + blockFieldTypes, GlobalFields, ITxArrFields, json_refTypes, @@ -42,8 +43,11 @@ import { MAX_UINT128, MaxTEALVersion, OpGasCost, + proofLength, + publicKeyLength, TransactionTypeEnum, TxArrFields, + vrfVerifyFieldTypes, ZERO_ADDRESS, } from "../lib/constants"; import { addInnerTransaction, calculateInnerTxCredit, setInnerTxField } from "../lib/itxn"; @@ -5209,6 +5213,7 @@ export class Json_ref extends Op { return this.computeCost(); } } +//TODO:add description export class Bn254Add extends Op { readonly line: number; /** @@ -5232,6 +5237,7 @@ export class Bn254Add extends Op { return this.computeCost(); } } +//TODO:add description export class Bn254ScalarMul extends Op { readonly line: number; /** @@ -5255,6 +5261,7 @@ export class Bn254ScalarMul extends Op { return this.computeCost(); } } +//TODO:add description export class Bn254Pairing extends Op { readonly line: number; /** @@ -5279,6 +5286,46 @@ export class Bn254Pairing extends Op { return this.computeCost(); } } + +/** + * Opcode: 0xd1 {uint8 block field} + * Stack: ..., A: uint64 → ..., any + * field F of block A. Fail unless A falls between txn.LastValid-1002 and txn.FirstValid (exclusive) +*/ +export class Block extends Op { + readonly line: number; + readonly field: string; + readonly interpreter: Interpreter; + /** + * @param args Expected arguments: [BlkSeed || BlkTimestamp] + * @param line line number in TEAL file + * @param interpreter interpreter instance + */ + constructor(args: string[], line: number, interpreter: Interpreter) { + super(); + assertLen(args.length, 1, line); + const argument = args[0]; + if (argument === blockFieldTypes.BlkSeed|| argument === blockFieldTypes.BlkTimestamp) { + this.field = argument; + } else { + throw new RuntimeError(RUNTIME_ERRORS.TEAL.UNKNOWN_BLOCK_FIELD, {field: argument}); + } + this.line = line; + this.interpreter = interpreter; + } + + execute(stack: TEALStack): number { + this.assertMinStackLen(stack, 1, this.line); + const round = Number(this.assertBigInt(stack.pop(), this.line)); + this.interpreter.assertRoundIsAvailable(round); + const block = this.interpreter.runtime.getBlock(round); + //"BlkTimestamp" = seconds since epoch, assuming one round(block) = 4.5s truncated to 4s (BigInt) + const result = (this.field === blockFieldTypes.BlkSeed) ? block.seed : block.timestamp + stack.push(result); + return this.computeCost(); + } +}; + /** * Opcode: 0xd0 {uint8 parameters index} * Stack: ..., A: []byte, B: []byte, C: []byte → ..., X: []byte, Y: uint64 @@ -5300,11 +5347,11 @@ export class VrfVerify extends Op { super(); this.line = line; switch (argument) { - case "VrfAlgorand": { + case vrfVerifyFieldTypes.VrfAlgorand: { this.vrfType = argument; break; } - case "VrfStandard": { + case vrfVerifyFieldTypes.VrfStandard: { this.vrfType = argument; break; } @@ -5315,7 +5362,7 @@ export class VrfVerify extends Op { } computeCost(): number { - return 5700; + return OpGasCost[7]["vrf_verify"]; } execute(stack: TEALStack): number { @@ -5324,13 +5371,13 @@ export class VrfVerify extends Op { const proof = this.assertBytes(stack.pop(), this.line); const publicKey = this.assertBytes(stack.pop(), this.line); - if (proof.length !== 80) { + if (proof.length !== proofLength) { throw new RuntimeError(RUNTIME_ERRORS.TEAL.INVALID_PROOF_LENGTH, {length: proof.length}); } - if (publicKey.length !== 32) { + if (publicKey.length !== publicKeyLength) { throw new RuntimeError(RUNTIME_ERRORS.TEAL.INVALID_PUB_KEY_LENGTH, {length: publicKey.length}); } - if (this.vrfType === "VrfAlgorand") { + if (this.vrfType === vrfVerifyFieldTypes.VrfAlgorand) { const hash = sha512(proof); stack.push(convertToBuffer(hash, EncodingType.HEX)); stack.push(1n); diff --git a/packages/runtime/src/interpreter/opcode.ts b/packages/runtime/src/interpreter/opcode.ts index 104424d6d..421f3108b 100644 --- a/packages/runtime/src/interpreter/opcode.ts +++ b/packages/runtime/src/interpreter/opcode.ts @@ -193,13 +193,14 @@ export abstract class Op { * @param index Index * @param line line number in TEAL file */ - assert64BitIndex(index: bigint, line: number): void { + assert64BitIndex(index: bigint, line: number): bigint { if (index > MAX_UINT6) { throw new RuntimeError(RUNTIME_ERRORS.TEAL.SET_BIT_INDEX_ERROR, { index: index, line: line, }); } + return index; } /** diff --git a/packages/runtime/src/lib/constants.ts b/packages/runtime/src/lib/constants.ts index c52247210..0d2a70afe 100644 --- a/packages/runtime/src/lib/constants.ts +++ b/packages/runtime/src/lib/constants.ts @@ -40,6 +40,10 @@ export const MAX_INNER_TRANSACTIONS = 16; export const ALGORAND_MAX_LOGS_COUNT = 32; export const ALGORAND_MAX_LOGS_LENGTH = 1024; +export const publicKeyLength = 32; +export const proofLength = 80; +export const seedLength = 32; + export const MAX_ALGORAND_ACCOUNT_ASSETS = 1000; export const MAX_ALGORAND_ACCOUNT_CREATED_APPS = 10; @@ -56,6 +60,9 @@ export const MAX_LOCAL_SCHEMA_ENTRIES = 16; export const MAX_INPUT_BYTE_LEN = 64; export const MAX_OUTPUT_BYTE_LEN = 128; +export const MaxTxnLife = 1000; +export const BlockFinalisationTime = 4n; // block finalisation time in seconds truncated down + export const ZERO_ADDRESS = new Uint8Array(32); export const ZERO_ADDRESS_STR = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ"; const zeroUint64 = 0n; @@ -475,6 +482,7 @@ OpGasCost[7] = { ...OpGasCost[6], sha3_256: 130, ed25519verify_bare: 1900, + vrf_verify : 5700, }; export const enum MathOp { @@ -517,6 +525,16 @@ export const json_refTypes = { JSONObject: "JSONObject", }; +export enum blockFieldTypes { + BlkTimestamp = "BlkTimestamp", + BlkSeed = "BlkSeed", +}; + +export enum vrfVerifyFieldTypes { + VrfAlgorand = "VrfAlgorand", + VrfStandard = "VrfStandard", +}; + export enum TxFieldEnum { FirstValidTime = "FirstValidTime", TypeEnum = "TypeEnum", diff --git a/packages/runtime/src/parser/parser.ts b/packages/runtime/src/parser/parser.ts index e6719a153..0c43d48e9 100644 --- a/packages/runtime/src/parser/parser.ts +++ b/packages/runtime/src/parser/parser.ts @@ -156,6 +156,7 @@ import { Txna, Txnas, Uncover, + Block, VrfVerify, } from "../interpreter/opcode-list"; import { @@ -410,6 +411,7 @@ opCodeMap[7] = { sha3_256: Sha3_256, ed25519verify_bare: Ed25519verify_bare, json_ref: Json_ref, + block: Block, vrf_verify: VrfVerify, }; /** @@ -488,6 +490,7 @@ const interpreterReqList = new Set([ "keccak256", "sha3_256", "ed25519verify", + "block", ]); const signatureModeOps = new Set(["arg", "args", "arg_0", "arg_1", "arg_2", "arg_3"]); diff --git a/packages/runtime/src/runtime.ts b/packages/runtime/src/runtime.ts index 4c64aee05..e561a36e1 100644 --- a/packages/runtime/src/runtime.ts +++ b/packages/runtime/src/runtime.ts @@ -7,6 +7,7 @@ import algosdk, { SignedTransaction, Transaction, } from "algosdk"; +import MD5 from "crypto-js/md5"; import cloneDeep from "lodash.clonedeep"; import nacl from "tweetnacl"; @@ -19,11 +20,12 @@ import { compareArray } from "./lib/compare"; import { ALGORAND_ACCOUNT_MIN_BALANCE, ALGORAND_MAX_TX_ARRAY_LEN, + BlockFinalisationTime, MAX_APP_PROGRAM_COST, MaxExtraAppProgramPages, + seedLength, TransactionTypeEnum, - ZERO_ADDRESS_STR -} from "./lib/constants"; + ZERO_ADDRESS_STR} from "./lib/constants"; import { convertToString } from "./lib/parsing"; import { LogicSigAccount } from "./logicsig"; import { mockSuggestedParams } from "./mock/tx"; @@ -35,6 +37,7 @@ import { ASADeploymentFlags, ASAInfo, AssetHoldingM, + Block, Context, EncTx, ExecutionMode, @@ -76,6 +79,7 @@ export class Runtime { appCounter: ALGORAND_MAX_TX_ARRAY_LEN, // initialize app counter with 8 assetCounter: ALGORAND_MAX_TX_ARRAY_LEN, // initialize asset counter with 8 txReceipts: new Map(), // receipt of each transaction, i.e map of {txID: txReceipt} + blocks: new Map(), }; this._defaultAccounts = this._setupDefaultAccounts(); @@ -88,7 +92,8 @@ export class Runtime { // context for interpreter this.ctx = new Ctx(cloneDeep(this.store), {}, [], [], this); - this.round = 2; + this.round = 2000; + this.populateChain(this.round); this.timestamp = 1; } @@ -1180,4 +1185,50 @@ export class Runtime { sendTxAndWait(transactions: SignedTransaction[]): TxnReceipt[] { return this.executeTx(transactions); } + + /** + * Produces new block and adds it to a Map where the keys are block numbers + */ + produceBlock() { + let timestamp: bigint | undefined; + let seed: Uint8Array | undefined; + if (this.store.blocks.size === 0) { //create genesis block + seed = nacl.randomBytes(seedLength); + timestamp = BigInt(Math.round(new Date().getTime() / 1000)); + } else { //add another block + const lastBlock = this.store.blocks.get(this.round); + if (lastBlock) { + seed = new TextEncoder().encode(MD5(lastBlock.seed.toString()).toString()); + timestamp = lastBlock.timestamp + BlockFinalisationTime; + this.round += 1;// we move to new a new round + } + } + if (timestamp && seed) { + this.store.blocks.set(this.round, { timestamp, seed }); + } else { + throw new RuntimeError(RUNTIME_ERRORS.GENERAL.PRODUCE_BLOCK); + } + } + /** + * Returns a requested Block object. If it does not exist on chain throws an error + * @param round block number + * @returns Block + */ + getBlock(round: number): Block { + const block = cloneDeep(this.store.blocks.get(round)); + if (block) { + return block; + } + throw new RuntimeError(RUNTIME_ERRORS.GENERAL.INVALID_BLOCK); + } + /** + * Populates chain from first block to round number block (Produces N rounds) + * @param round current round number + */ + private populateChain(round:number) { + this.round = 1 + for(let blockN = 1; blockN<=round; blockN++){ + this.produceBlock(); + } + } } diff --git a/packages/runtime/src/types.ts b/packages/runtime/src/types.ts index 539821a55..4c95e4033 100644 --- a/packages/runtime/src/types.ts +++ b/packages/runtime/src/types.ts @@ -88,6 +88,7 @@ export interface State { appCounter: number; assetCounter: number; txReceipts: Map; // map of {txID: txReceipt} + blocks: Map, // map of{round, block} } export interface DeployedAssetInfo { @@ -423,3 +424,8 @@ export interface AppInfoReceipt extends DeployedAssetInfoReceipt { logs?: Uint8Array[]; gas?: number; } + +export interface Block { + timestamp: bigint; //uint64 + seed: Uint8Array; //[]byte +} diff --git a/packages/runtime/test/mocks/txn.ts b/packages/runtime/test/mocks/txn.ts index 923c5160d..ff0325791 100644 --- a/packages/runtime/test/mocks/txn.ts +++ b/packages/runtime/test/mocks/txn.ts @@ -22,8 +22,8 @@ const txn_obj = { fee: 1000, amt: 20200, aamt: 100, - fv: 258820, - lv: 259820, + fv: 2000, + lv: 3000, note: Buffer.from("Note"), gen: "default-v1", gh: Buffer.from("default-v1"), diff --git a/packages/runtime/test/src/interpreter/opcode-list.ts b/packages/runtime/test/src/interpreter/opcode-list.ts index e7d48c388..888cb4e56 100644 --- a/packages/runtime/test/src/interpreter/opcode-list.ts +++ b/packages/runtime/test/src/interpreter/opcode-list.ts @@ -11,6 +11,7 @@ import algosdk, { import { assert } from "chai"; import { ec as EC } from "elliptic"; import { sha512_256 } from "js-sha512"; +import cloneDeep from "lodash.clonedeep"; import { describe } from "mocha"; import nacl from "tweetnacl"; @@ -45,6 +46,7 @@ import { BitwiseNot, BitwiseOr, BitwiseXor, + Block, Bn254Add, Bn254Pairing, Bn254ScalarMul, @@ -167,12 +169,15 @@ import { import { ALGORAND_ACCOUNT_MIN_BALANCE, ASSET_CREATION_FEE, + blockFieldTypes, DEFAULT_STACK_ELEM, MAX_UINT8, MAX_UINT64, MaxTEALVersion, MIN_UINT8, + vrfVerifyFieldTypes, ZERO_ADDRESS, + seedLength, } from "../../../src/lib/constants"; import { bigEndianBytesToBigInt, @@ -7564,6 +7569,67 @@ describe("Teal Opcodes", function () { assert.equal(expectedResult, stack.pop()); }); }); + describe("Block opcode", function () { + const stack = new Stack(); + let interpreter: Interpreter; + const FIRST_VALID = 2000n; + this.beforeEach(function () { + interpreter = new Interpreter(); + interpreter.runtime = new Runtime([]); + interpreter.runtime.ctx.tx = cloneDeep(TXN_OBJ); + interpreter.tealVersion = MaxTEALVersion; // set tealversion to latest (to support all tx fields) + }); + + it("Should fail when accesing two behind FirstVaild because LastValid is 1000 after", function () { + stack.push(BigInt(FIRST_VALID - 2n)); + const op = new Block([blockFieldTypes.BlkTimestamp], 1, interpreter); + assert.throws(() => op.execute(stack)); + }); + + it("Should allow to acces two behind FirstValid if LastValid is 100 after", function () { + interpreter.runtime.ctx.tx.lv = Number(FIRST_VALID) + 100; + stack.push(BigInt(FIRST_VALID - 2n)); + const op = new Block([blockFieldTypes.BlkTimestamp], 1, interpreter); + assert.doesNotThrow(() => op.execute(stack)); + }); + + it("Should return two different seeds for to different rounds", function () { + interpreter.runtime.ctx.tx.lv = Number(FIRST_VALID) + 100; + const op = new Block([blockFieldTypes.BlkSeed], 1, interpreter); + //first round + stack.push(FIRST_VALID - 1n); + op.execute(stack); + const seedRound1 = stack.pop(); + //second round + stack.push(FIRST_VALID - 2n); + op.execute(stack); + const seedRound2 = stack.pop(); + assert.notEqual(seedRound1, seedRound2); + }); + + it("Should fail when accesing block 0 even if last valid is low", function(){ + interpreter.runtime.ctx.tx.lv = 100; + interpreter.runtime.ctx.tx.fv = 5; + stack.push(0n); + const op = new Block([blockFieldTypes.BlkTimestamp], 1, interpreter); + assert.throws(() => op.execute(stack)); + }); + + it("Should return seed for a block that is within boundries and exist", function(){ + stack.push(FIRST_VALID - 1n); + const op = new Block([blockFieldTypes.BlkSeed], 1, interpreter); + op.execute(stack); + const result = stack.pop(); + assert.equal((result as Buffer).length, seedLength); + }); + + it("Should return correct cost", function(){ + stack.push(FIRST_VALID - 1n); + const op = new Block([blockFieldTypes.BlkSeed], 1, interpreter); + const cost = op.execute(stack); + assert.equal(cost, 1); + }); + }); describe("vrf_verify", function () { let stack = new Stack(); @@ -7586,7 +7652,7 @@ describe("Teal Opcodes", function () { }) it("Should return 1 and hash of proof for valid proof and verification key", function () { - const op = new VrfVerify(["VrfAlgorand"], 1); + const op = new VrfVerify([vrfVerifyFieldTypes.VrfAlgorand], 1); stack.push(publicKey); stack.push(proof); stack.push(message); @@ -7595,7 +7661,7 @@ describe("Teal Opcodes", function () { assert.deepEqual(expectedResult, stack.pop()); }); it("Should throw error if pubKey size not equal 32", function(){ - const op = new VrfVerify(["VrfAlgorand"], 1); + const op = new VrfVerify([vrfVerifyFieldTypes.VrfAlgorand], 1); const wrongSizePubKey = Buffer.from("b6b4699f87d56126c9117"); stack.push(wrongSizePubKey); stack.push(proof); @@ -7603,7 +7669,7 @@ describe("Teal Opcodes", function () { expectRuntimeError(() => op.execute(stack), RUNTIME_ERRORS.TEAL.INVALID_PUB_KEY_LENGTH); }) it("Should throw error if proof size not equal 80", function(){ - const op = new VrfVerify(["VrfAlgorand"], 1); + const op = new VrfVerify([vrfVerifyFieldTypes.VrfAlgorand], 1); const wrongSizeProof = Buffer.from("b6b4699f87d56126c9117"); stack.push(publicKey); stack.push(wrongSizeProof); @@ -7611,7 +7677,7 @@ describe("Teal Opcodes", function () { expectRuntimeError(() => op.execute(stack), RUNTIME_ERRORS.TEAL.INVALID_PROOF_LENGTH); }) it("Should return correct cost", function(){ - const op = new VrfVerify(["VrfAlgorand"], 1); + const op = new VrfVerify([vrfVerifyFieldTypes.VrfAlgorand], 1); stack.push(publicKey); stack.push(proof); stack.push(message); @@ -7619,7 +7685,7 @@ describe("Teal Opcodes", function () { assert.equal(cost, 5700); }) it("Should throw error when field = VrfStandard", function(){ - const op = new VrfVerify(["VrfStandard"], 1); + const op = new VrfVerify([vrfVerifyFieldTypes.VrfStandard], 1); stack.push(publicKey); stack.push(proof); stack.push(message); diff --git a/packages/runtime/test/src/runtime.ts b/packages/runtime/test/src/runtime.ts index 9d53d8f7b..b22279a55 100644 --- a/packages/runtime/test/src/runtime.ts +++ b/packages/runtime/test/src/runtime.ts @@ -1,13 +1,12 @@ import { types } from "@algo-builder/web"; -import { ExecParams, SignType, TransactionType } from "@algo-builder/web/build/types"; import algosdk, { LogicSigAccount, Transaction } from "algosdk"; -import { assert } from "chai"; +import { assert, expect } from "chai"; import sinon from "sinon"; import { getProgram } from "../../src"; import { AccountStore } from "../../src/account"; import { RUNTIME_ERRORS } from "../../src/errors/errors-list"; -import { ASSET_CREATION_FEE } from "../../src/lib/constants"; +import { ASSET_CREATION_FEE, BlockFinalisationTime } from "../../src/lib/constants"; import { mockSuggestedParams } from "../../src/mock/tx"; import { Runtime } from "../../src/runtime"; import { AccountStoreI } from "../../src/types"; @@ -1442,3 +1441,28 @@ describe("Helper functions", function () { }); }); }); +describe("Funtions related to runtime chain", function () { + let runtime: Runtime; + + this.beforeEach(function () { + runtime = new Runtime([]); + }); + + it("Should return current block", function () { + const currentBlock = runtime.getBlock(runtime.getRound()); + assert.equal(currentBlock.seed.length, 32); + expect(currentBlock.timestamp).to.be.a("bigint"); + }); + + it("Should produce a new block and move to next round", function () { + const currentRound = runtime.getRound(); + const currentBlock = runtime.getBlock(currentRound); + //produce new block and move to next round + runtime.produceBlock(); + const newRound = runtime.getRound(); + const newBlock = runtime.getBlock(newRound); + assert.equal(currentRound + 1, newRound); + assert.notDeepEqual(currentBlock, newBlock); + assert.equal(currentBlock.timestamp + BlockFinalisationTime, newBlock.timestamp); + }); +}); diff --git a/yarn.lock b/yarn.lock index 7d0805452..a0adcf1f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -63,6 +63,7 @@ __metadata: "@algo-builder/web": "workspace:*" "@nodelib/fs.walk": ^1.2.8 "@types/chai": ^4.3.3 + "@types/crypto-js": ^4.1.1 "@types/debug": ^4.1.7 "@types/elliptic": ^6.4.14 "@types/json-bigint": ^1.0.1 @@ -73,6 +74,7 @@ __metadata: algosdk: ^1.22.0 chai: ^4.3.6 chalk: ^4.1.2 + crypto-js: ^4.1.1 debug: ^4.3.4 elliptic: ^6.5.4 eslint: ^8.26.0 @@ -708,6 +710,13 @@ __metadata: languageName: node linkType: hard +"@types/crypto-js@npm:^4.1.1": + version: 4.1.1 + resolution: "@types/crypto-js@npm:4.1.1" + checksum: ea3d6a67b69f88baeb6af96004395903d2367a41bd5cd86306da23a44dd96589749495da50974a9b01bb5163c500764c8a33706831eade036bddae016417e3ea + languageName: node + linkType: hard + "@types/debug@npm:^4.1.7": version: 4.1.7 resolution: "@types/debug@npm:4.1.7" @@ -2191,6 +2200,13 @@ __metadata: languageName: unknown linkType: soft +"crypto-js@npm:^4.1.1": + version: 4.1.1 + resolution: "crypto-js@npm:4.1.1" + checksum: b3747c12ee3a7632fab3b3e171ea50f78b182545f0714f6d3e7e2858385f0f4101a15f2517e033802ce9d12ba50a391575ff4638c9de3dd9b2c4bc47768d5425 + languageName: node + linkType: hard + "dao@workspace:examples/dao": version: 0.0.0-use.local resolution: "dao@workspace:examples/dao"