From 3f1d3b9a97bb4f2bccff820f1e2d9e822b87655d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Libert?= Date: Fri, 24 May 2024 07:59:17 +0200 Subject: [PATCH] 339 constant key manager (#340) Co-authored-by: BenRey --- .../__tests__/constantKeyManager.spec.ts | 54 +++++++++++++++++++ .../helpers/__tests__/keyIncrementer.spec.ts | 20 +++++++ assembly/helpers/constantKeyManager.ts | 54 +++++++++++++++++++ assembly/helpers/index.ts | 2 + assembly/helpers/keyIncrementer.ts | 31 +++++++++++ assembly/index.ts | 2 + package-lock.json | 34 ++++++++---- package.json | 2 +- 8 files changed, 187 insertions(+), 12 deletions(-) create mode 100644 assembly/helpers/__tests__/constantKeyManager.spec.ts create mode 100644 assembly/helpers/__tests__/keyIncrementer.spec.ts create mode 100644 assembly/helpers/constantKeyManager.ts create mode 100644 assembly/helpers/index.ts create mode 100644 assembly/helpers/keyIncrementer.ts diff --git a/assembly/helpers/__tests__/constantKeyManager.spec.ts b/assembly/helpers/__tests__/constantKeyManager.spec.ts new file mode 100644 index 00000000..2eed5540 --- /dev/null +++ b/assembly/helpers/__tests__/constantKeyManager.spec.ts @@ -0,0 +1,54 @@ +import { Address } from '../../std'; +import { resetStorage } from '../../vm-mock'; +import { u256 } from 'as-bignum/assembly'; +import { ConstantManager } from '../constantKeyManager'; +import { KeyIncrementer } from '../keyIncrementer'; + +beforeEach(() => { + resetStorage(); +}); + +describe('ConstantManager - use cases', () => { + test('executes a basic scenario - one key', () => { + const bins = new ConstantManager>(); + + bins.set([1, 2, 3, 4, 5]); + + expect(bins.mustValue()).toStrictEqual([1, 2, 3, 4, 5]); + }); + + test('executes a basic scenario - multiple keys', () => { + // a key manager instance is needed to generate unique keys + const keyManager = new KeyIncrementer(); + const owner = new ConstantManager
(keyManager); + const fee = new ConstantManager(keyManager); + const usdc = new ConstantManager(keyManager); + + owner.set( + new Address('AU12UBnqTHDQALpocVBnkPNy7y5CndUJQTLutaVDDFgMJcq5kQiKq'), + ); + fee.set(100); + usdc.set(u256.fromU64(1000000)); + + expect(owner.mustValue().toString()).toBe( + 'AU12UBnqTHDQALpocVBnkPNy7y5CndUJQTLutaVDDFgMJcq5kQiKq', + ); + expect(fee.mustValue()).toBe(100); + expect(usdc.mustValue().toString()).toBe('1000000'); + }); +}); + +describe('ConstantManager - unit tests', () => { + test('mustValue - key not found', () => { + expect(() => { + const cst = new ConstantManager(); + cst.mustValue(); + }).toThrow('Key not found'); + }); + + test('tryValue - key not found', () => { + const cst = new ConstantManager>(); + expect(cst.tryValue().isErr()).toBe(true); + expect(cst.tryValue().error).toBe('Key not found'); + }); +}); diff --git a/assembly/helpers/__tests__/keyIncrementer.spec.ts b/assembly/helpers/__tests__/keyIncrementer.spec.ts new file mode 100644 index 00000000..8505e798 --- /dev/null +++ b/assembly/helpers/__tests__/keyIncrementer.spec.ts @@ -0,0 +1,20 @@ +import { KeyIncrementer } from '../keyIncrementer'; +import { resetStorage } from '../../vm-mock'; + +beforeEach(() => { + resetStorage(); +}); + +describe('KeyIncrementer - unit tests', () => { + test('nextKey - u8', () => { + const keyInc = new KeyIncrementer(); + expect(keyInc.nextKey()).toStrictEqual([0]); + expect(keyInc.nextKey()).toStrictEqual([1]); + }); + + test('nextKey - u64', () => { + const keyInc = new KeyIncrementer(); + expect(keyInc.nextKey()).toStrictEqual([0, 0, 0, 0, 0, 0, 0, 0]); + expect(keyInc.nextKey()).toStrictEqual([1, 0, 0, 0, 0, 0, 0, 0]); + }); +}); diff --git a/assembly/helpers/constantKeyManager.ts b/assembly/helpers/constantKeyManager.ts new file mode 100644 index 00000000..9e4989d4 --- /dev/null +++ b/assembly/helpers/constantKeyManager.ts @@ -0,0 +1,54 @@ +import { Args, Result } from '@massalabs/as-types'; +import { KeyIncrementer, KeySequenceManager } from './keyIncrementer'; +import { Storage } from '../std'; + +/** + * Manages a constant value in storage. + * + * @typeParam TValue - The type of the value stored. + * @typeParam TKey - The type of the key used to store the value. + * @typeParam TArray - When value is an array of serializable, the underlying serializable type. + */ +export class ConstantManager { + public key: StaticArray; + + constructor(manager: KeySequenceManager = new KeyIncrementer(0)) { + this.key = manager.nextKey(); + } + + /** + * Retrieves the value from storage and panics in case of failure. + * + * @returns the value stored. + * @throws if the key is not found in storage. + * @throws if the value is not found in storage. + */ + public mustValue(): TValue { + return new Args(Storage.get(this.key)).next().unwrap(); + } + + /** + * Retrieves the value from storage and returns a result. + * + * @returns the value stored wrapped in a result. + */ + public tryValue(): Result { + if (!Storage.has(this.key)) { + if (isManaged()) { + return new Result(instantiate(), 'Key not found'); + } + return new Result(0, 'Key not found'); + } + + return new Args(Storage.get(this.key)).next(); + } + + /** + * Sets the value in storage. + * + * @param value - The value to store. Must be an A + */ + public set(value: TValue): void { + Storage.set(this.key, new Args().add(value).serialize()); + } +} diff --git a/assembly/helpers/index.ts b/assembly/helpers/index.ts new file mode 100644 index 00000000..bfba27c6 --- /dev/null +++ b/assembly/helpers/index.ts @@ -0,0 +1,2 @@ +export * from './constantKeyManager'; +export * from './keyIncrementer'; diff --git a/assembly/helpers/keyIncrementer.ts b/assembly/helpers/keyIncrementer.ts new file mode 100644 index 00000000..1830f9dc --- /dev/null +++ b/assembly/helpers/keyIncrementer.ts @@ -0,0 +1,31 @@ +import { Args } from '@massalabs/as-types'; +/** + * Manages key sequences for storage. + */ +export interface KeySequenceManager { + nextKey(): StaticArray; +} + +/** + * A key sequence manager that simply increments a counter each time a key is requested. + * + * @typeParam T - The type of the counter, defaults to `u8`. + */ +export class KeyIncrementer implements KeySequenceManager { + constructor(public counter: T = 0) {} + + /** + * Generates the next key in the sequence. + * + * @remarks + * The `Args` class is used to serialize the counter into a key. Ideally, this serialization should + * be done outside of the `Args` object as we are allocating an object simply to get a proper serialization + * of the counter. + * + * @returns A unique storage key. + */ + @inline + public nextKey(): StaticArray { + return new Args().add(this.counter++).serialize(); + } +} diff --git a/assembly/index.ts b/assembly/index.ts index 681e2a3f..3e183629 100644 --- a/assembly/index.ts +++ b/assembly/index.ts @@ -11,6 +11,8 @@ export { env }; import * as security from './security/index'; export { security }; +export * from './helpers/index'; + // massa std functionalities export * from './std'; diff --git a/package-lock.json b/package-lock.json index 716d6f4f..3e94aa20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "devDependencies": { "@as-pect/cli": "^8.1.0", "@massalabs/as-transformer": "^0.3.2", - "@massalabs/as-types": "^2.0.0", + "@massalabs/as-types": "^2.0.1-dev.20240521132240", "@massalabs/eslint-config": "^0.0.8", "@massalabs/prettier-config-as": "^0.0.2", "as-bignum": "^0.2.40", @@ -763,15 +763,21 @@ } }, "node_modules/@massalabs/as-types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@massalabs/as-types/-/as-types-2.0.0.tgz", - "integrity": "sha512-e5vr6nxOmXLjgaFeRdML+DHf5/rIPWiASWJUPAZCdUU7gHhJm6InsZo4+jQVmfzrPzahBZoKAq8j7Hfv9K6Faw==", + "version": "2.0.1-dev.20240521132240", + "resolved": "https://registry.npmjs.org/@massalabs/as-types/-/as-types-2.0.1-dev.20240521132240.tgz", + "integrity": "sha512-K5UwZrGfIpX7ryCaEEMEtmoFsqwb/eyq0AAyc2Dv/pmoHTrO2I+V3oi6Mz10JVUF8Vh4wC8ZOP5noyy+xdl9vg==", "dev": true, "dependencies": { - "as-bignum": "^0.2.31", - "assemblyscript": "^0.27.9" + "as-bignum": "^0.3.0", + "assemblyscript": "^0.27.18" } }, + "node_modules/@massalabs/as-types/node_modules/as-bignum": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/as-bignum/-/as-bignum-0.3.1.tgz", + "integrity": "sha512-/RS4NbSPYUCRTsGNeCNA8aLjiyYlsEYaevY4Jwzj/QlzVk82t4gXdaWb8R3hnqo96g0MSIbhdaq18UcpeyW7gA==", + "dev": true + }, "node_modules/@massalabs/as-types/node_modules/assemblyscript": { "version": "0.27.22", "resolved": "https://registry.npmjs.org/assemblyscript/-/assemblyscript-0.27.22.tgz", @@ -10058,15 +10064,21 @@ } }, "@massalabs/as-types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@massalabs/as-types/-/as-types-2.0.0.tgz", - "integrity": "sha512-e5vr6nxOmXLjgaFeRdML+DHf5/rIPWiASWJUPAZCdUU7gHhJm6InsZo4+jQVmfzrPzahBZoKAq8j7Hfv9K6Faw==", + "version": "2.0.1-dev.20240521132240", + "resolved": "https://registry.npmjs.org/@massalabs/as-types/-/as-types-2.0.1-dev.20240521132240.tgz", + "integrity": "sha512-K5UwZrGfIpX7ryCaEEMEtmoFsqwb/eyq0AAyc2Dv/pmoHTrO2I+V3oi6Mz10JVUF8Vh4wC8ZOP5noyy+xdl9vg==", "dev": true, "requires": { - "as-bignum": "^0.2.31", - "assemblyscript": "^0.27.9" + "as-bignum": "^0.3.0", + "assemblyscript": "^0.27.18" }, "dependencies": { + "as-bignum": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/as-bignum/-/as-bignum-0.3.1.tgz", + "integrity": "sha512-/RS4NbSPYUCRTsGNeCNA8aLjiyYlsEYaevY4Jwzj/QlzVk82t4gXdaWb8R3hnqo96g0MSIbhdaq18UcpeyW7gA==", + "dev": true + }, "assemblyscript": { "version": "0.27.22", "resolved": "https://registry.npmjs.org/assemblyscript/-/assemblyscript-0.27.22.tgz", diff --git a/package.json b/package.json index ca644127..7d5567da 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "devDependencies": { "@as-pect/cli": "^8.1.0", "@massalabs/as-transformer": "^0.3.2", - "@massalabs/as-types": "^2.0.0", + "@massalabs/as-types": "^2.0.1-dev.20240521132240", "@massalabs/eslint-config": "^0.0.8", "@massalabs/prettier-config-as": "^0.0.2", "as-bignum": "^0.2.40",