feat: add support for vrf
This commit is contained in:
@@ -56,9 +56,11 @@
|
|||||||
"homepage": "https://github.com/redstone-finance/redstone-smartweave#readme",
|
"homepage": "https://github.com/redstone-finance/redstone-smartweave#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@assemblyscript/loader": "^0.19.23",
|
"@assemblyscript/loader": "^0.19.23",
|
||||||
|
"@idena/vrf-js": "^1.0.1",
|
||||||
"archiver": "^5.3.0",
|
"archiver": "^5.3.0",
|
||||||
"arweave": "1.10.23",
|
"arweave": "1.10.23",
|
||||||
"bignumber.js": "^9.0.1",
|
"bignumber.js": "^9.0.1",
|
||||||
|
"elliptic": "^6.5.4",
|
||||||
"fast-copy": "^2.1.1",
|
"fast-copy": "^2.1.1",
|
||||||
"knex": "^0.95.14",
|
"knex": "^0.95.14",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
|||||||
191
src/__tests__/integration/basic/vrf.test.ts
Normal file
191
src/__tests__/integration/basic/vrf.test.ts
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
import ArLocal from 'arlocal';
|
||||||
|
import Arweave from 'arweave';
|
||||||
|
import { JWKInterface } from 'arweave/node/lib/wallet';
|
||||||
|
import {
|
||||||
|
ArweaveGatewayInteractionsLoader,
|
||||||
|
EvaluationOptions,
|
||||||
|
GQLEdgeInterface,
|
||||||
|
InteractionsLoader,
|
||||||
|
LexicographicalInteractionsSorter,
|
||||||
|
LoggerFactory,
|
||||||
|
PstContract,
|
||||||
|
PstState,
|
||||||
|
SmartWeave,
|
||||||
|
SmartWeaveNodeFactory
|
||||||
|
} from '@smartweave';
|
||||||
|
import path from 'path';
|
||||||
|
import { addFunds, mineBlock } from '../_helpers';
|
||||||
|
import { Evaluate } from '@idena/vrf-js';
|
||||||
|
import elliptic from 'elliptic';
|
||||||
|
|
||||||
|
const EC = new elliptic.ec('secp256k1');
|
||||||
|
const key = EC.genKeyPair();
|
||||||
|
const pubKeyS = key.getPublic(true, 'hex');
|
||||||
|
|
||||||
|
const useWrongIndex = [];
|
||||||
|
const useWrongProof = [];
|
||||||
|
|
||||||
|
describe('Testing the Profit Sharing Token', () => {
|
||||||
|
let contractSrc: string;
|
||||||
|
|
||||||
|
let wallet: JWKInterface;
|
||||||
|
let walletAddress: string;
|
||||||
|
|
||||||
|
let initialState: PstState;
|
||||||
|
|
||||||
|
let arweave: Arweave;
|
||||||
|
let arlocal: ArLocal;
|
||||||
|
let smartweave: SmartWeave;
|
||||||
|
let pst: PstContract;
|
||||||
|
let loader: InteractionsLoader;
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
// note: each tests suit (i.e. file with tests that Jest is running concurrently
|
||||||
|
// with another files has to have ArLocal set to a different port!)
|
||||||
|
arlocal = new ArLocal(1823, false);
|
||||||
|
await arlocal.start();
|
||||||
|
|
||||||
|
arweave = Arweave.init({
|
||||||
|
host: 'localhost',
|
||||||
|
port: 1823,
|
||||||
|
protocol: 'http'
|
||||||
|
});
|
||||||
|
|
||||||
|
loader = new VrfDecorator(arweave);
|
||||||
|
LoggerFactory.INST.logLevel('error');
|
||||||
|
|
||||||
|
smartweave = SmartWeaveNodeFactory.memCachedBased(arweave)
|
||||||
|
.useArweaveGateway()
|
||||||
|
.setInteractionsLoader(loader)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
wallet = await arweave.wallets.generate();
|
||||||
|
await addFunds(arweave, wallet);
|
||||||
|
walletAddress = await arweave.wallets.jwkToAddress(wallet);
|
||||||
|
|
||||||
|
contractSrc = fs.readFileSync(path.join(__dirname, '../data/token-pst.js'), 'utf8');
|
||||||
|
const stateFromFile: PstState = JSON.parse(fs.readFileSync(path.join(__dirname, '../data/token-pst.json'), 'utf8'));
|
||||||
|
|
||||||
|
initialState = {
|
||||||
|
...stateFromFile,
|
||||||
|
...{
|
||||||
|
owner: walletAddress,
|
||||||
|
balances: {
|
||||||
|
...stateFromFile.balances,
|
||||||
|
[walletAddress]: 555669
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const contractTxId = await smartweave.createContract.deploy({
|
||||||
|
wallet,
|
||||||
|
initState: JSON.stringify(initialState),
|
||||||
|
src: contractSrc
|
||||||
|
});
|
||||||
|
|
||||||
|
// connecting to the PST contract
|
||||||
|
pst = smartweave.pst(contractTxId);
|
||||||
|
|
||||||
|
// connecting wallet to the PST contract
|
||||||
|
pst.connect(wallet);
|
||||||
|
|
||||||
|
await mineBlock(arweave);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await arlocal.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should properly return random numbers', async () => {
|
||||||
|
await pst.writeInteraction({
|
||||||
|
function: 'vrf'
|
||||||
|
});
|
||||||
|
await mineBlock(arweave);
|
||||||
|
const result = await pst.readState();
|
||||||
|
const lastTxId = Object.keys(result.validity).pop();
|
||||||
|
const vrf = (result.state as any).vrf[lastTxId];
|
||||||
|
|
||||||
|
console.log(vrf);
|
||||||
|
|
||||||
|
expect(vrf).not.toBeUndefined();
|
||||||
|
expect(vrf['random_6_1'] == vrf['random_6_2']).toBe(true);
|
||||||
|
expect(vrf['random_6_2'] == vrf['random_6_3']).toBe(true);
|
||||||
|
expect(vrf['random_12_1'] == vrf['random_12_2']).toBe(true);
|
||||||
|
expect(vrf['random_12_2'] == vrf['random_12_3']).toBe(true);
|
||||||
|
expect(vrf['random_46_1'] == vrf['random_46_2']).toBe(true);
|
||||||
|
expect(vrf['random_46_2'] == vrf['random_46_3']).toBe(true);
|
||||||
|
expect(vrf['random_99_1'] == vrf['random_99_2']).toBe(true);
|
||||||
|
expect(vrf['random_99_2'] == vrf['random_99_3']).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if random cannot be verified', async () => {
|
||||||
|
const txId = await pst.writeInteraction({
|
||||||
|
function: 'vrf'
|
||||||
|
});
|
||||||
|
await mineBlock(arweave);
|
||||||
|
useWrongIndex.push(txId);
|
||||||
|
await expect(pst.readState()).rejects.toThrow('Vrf verification failed.');
|
||||||
|
useWrongIndex.pop();
|
||||||
|
|
||||||
|
const txId2 = await pst.writeInteraction({
|
||||||
|
function: 'vrf'
|
||||||
|
});
|
||||||
|
await mineBlock(arweave);
|
||||||
|
useWrongProof.push(txId2);
|
||||||
|
await expect(pst.readState()).rejects.toThrow('Vrf verification failed.');
|
||||||
|
useWrongProof.pop();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
class VrfDecorator extends ArweaveGatewayInteractionsLoader {
|
||||||
|
constructor(protected readonly arweave: Arweave) {
|
||||||
|
super(arweave);
|
||||||
|
}
|
||||||
|
|
||||||
|
async load(
|
||||||
|
contractId: string,
|
||||||
|
fromBlockHeight: number,
|
||||||
|
toBlockHeight: number,
|
||||||
|
evaluationOptions: EvaluationOptions
|
||||||
|
): Promise<GQLEdgeInterface[]> {
|
||||||
|
const result = await super.load(contractId, fromBlockHeight, toBlockHeight, evaluationOptions);
|
||||||
|
const arUtils = this.arweave.utils;
|
||||||
|
|
||||||
|
const sorter = new LexicographicalInteractionsSorter(this.arweave);
|
||||||
|
|
||||||
|
for (const r of result) {
|
||||||
|
r.node.sortKey = await sorter.createSortKey(r.node.block.id, r.node.id, r.node.block.height);
|
||||||
|
const data = arUtils.stringToBuffer(r.node.sortKey);
|
||||||
|
const [index, proof] = Evaluate(key.getPrivate().toArray(), data);
|
||||||
|
r.node.vrf = {
|
||||||
|
index: useWrongIndex.includes(r.node.id)
|
||||||
|
? arUtils.bufferTob64Url(Uint8Array.of(1, 2, 3))
|
||||||
|
: arUtils.bufferTob64Url(index),
|
||||||
|
proof: useWrongProof.includes(r.node.id)
|
||||||
|
? 'pK5HGnXo_rJkZPJorIX7TBCAEikcemL2DgJaPB3Pfm2D6tZUdK9mDuBSRUkcHUDNnrO02O0-ogq1e32JVEuVvgR4i5YFa-UV9MEoHgHg4yv0e318WNfzNWPc9rlte7P7RoO57idHu5SSkm7Qj0f4pBjUR7lWODVKBYp9fEJ-PObZ'
|
||||||
|
: arUtils.bufferTob64Url(proof),
|
||||||
|
bigint: bufToBn(index).toString(),
|
||||||
|
pubkey: pubKeyS
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function bufToBn(buf) {
|
||||||
|
const hex = [];
|
||||||
|
const u8 = Uint8Array.from(buf);
|
||||||
|
|
||||||
|
u8.forEach(function (i) {
|
||||||
|
let h = i.toString(16);
|
||||||
|
if (h.length % 2) {
|
||||||
|
h = '0' + h;
|
||||||
|
}
|
||||||
|
hex.push(h);
|
||||||
|
});
|
||||||
|
|
||||||
|
return BigInt('0x' + hex.join(''));
|
||||||
|
}
|
||||||
@@ -34,7 +34,7 @@ export function handle(state, action) {
|
|||||||
balances[target] = qty;
|
balances[target] = qty;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { state };
|
return {state};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.function === 'balance') {
|
if (input.function === 'balance') {
|
||||||
@@ -49,7 +49,36 @@ export function handle(state, action) {
|
|||||||
throw new ContractError('Cannot get balance, target does not exist');
|
throw new ContractError('Cannot get balance, target does not exist');
|
||||||
}
|
}
|
||||||
|
|
||||||
return { result: { target, ticker, balance: balances[target] } };
|
return {result: {target, ticker, balance: balances[target]}};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.function === 'vrf') {
|
||||||
|
if (!state.vrf) {
|
||||||
|
state.vrf = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
state.vrf[SmartWeave.transaction.id] = {
|
||||||
|
vrf: SmartWeave.vrf.data,
|
||||||
|
value: SmartWeave.vrf.value,
|
||||||
|
|
||||||
|
random_6_1: SmartWeave.vrf.randomInt(6),
|
||||||
|
random_6_2: SmartWeave.vrf.randomInt(6),
|
||||||
|
random_6_3: SmartWeave.vrf.randomInt(6),
|
||||||
|
|
||||||
|
random_12_1: SmartWeave.vrf.randomInt(12),
|
||||||
|
random_12_2: SmartWeave.vrf.randomInt(12),
|
||||||
|
random_12_3: SmartWeave.vrf.randomInt(12),
|
||||||
|
|
||||||
|
random_46_1: SmartWeave.vrf.randomInt(46),
|
||||||
|
random_46_2: SmartWeave.vrf.randomInt(46),
|
||||||
|
random_46_3: SmartWeave.vrf.randomInt(46),
|
||||||
|
|
||||||
|
random_99_1: SmartWeave.vrf.randomInt(99),
|
||||||
|
random_99_2: SmartWeave.vrf.randomInt(99),
|
||||||
|
random_99_3: SmartWeave.vrf.randomInt(99),
|
||||||
|
}
|
||||||
|
|
||||||
|
return {state};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.function === 'evolve' && canEvolve) {
|
if (input.function === 'evolve' && canEvolve) {
|
||||||
@@ -59,7 +88,7 @@ export function handle(state, action) {
|
|||||||
|
|
||||||
state.evolve = input.value;
|
state.evolve = input.value;
|
||||||
|
|
||||||
return { state };
|
return {state};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ContractError(`No function supplied or function not recognised: "${input.function}"`);
|
throw new ContractError(`No function supplied or function not recognised: "${input.function}"`);
|
||||||
|
|||||||
@@ -144,13 +144,17 @@ export interface Contract<State = unknown> {
|
|||||||
/**
|
/**
|
||||||
* Creates a new "interaction" transaction using RedStone Sequencer - this, with combination with
|
* Creates a new "interaction" transaction using RedStone Sequencer - this, with combination with
|
||||||
* RedStone Gateway, gives instant transaction availability and finality guaranteed by Bundlr.
|
* RedStone Gateway, gives instant transaction availability and finality guaranteed by Bundlr.
|
||||||
*
|
|
||||||
* @param input - new input to the contract that will be assigned with this interactions transaction
|
* @param input - new input to the contract that will be assigned with this interactions transaction
|
||||||
* @param tags - additional tags that can be attached to the newly created interaction transaction
|
* @param options
|
||||||
* @param transfer - additional {@link ArTransfer} than can be attached to the interaction transaction
|
|
||||||
* @param strict - transaction will be posted on Arweave only if the dry-run of the input result is "ok"
|
|
||||||
*/
|
*/
|
||||||
bundleInteraction<Input = unknown>(input: Input, tags?: Tags, strict?: boolean): Promise<any | null>;
|
bundleInteraction<Input = unknown>(
|
||||||
|
input: Input,
|
||||||
|
options: {
|
||||||
|
tags?: Tags;
|
||||||
|
strict?: boolean;
|
||||||
|
vrf?: boolean;
|
||||||
|
}
|
||||||
|
): Promise<any | null>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the full call tree report the last
|
* Returns the full call tree report the last
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import { NetworkInfoInterface } from 'arweave/node/network';
|
|||||||
import stringify from 'safe-stable-stringify';
|
import stringify from 'safe-stable-stringify';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import Transaction from 'arweave/node/lib/transaction';
|
import Transaction from 'arweave/node/lib/transaction';
|
||||||
|
import { options } from 'tsconfig-paths/lib/options';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of {@link Contract} that is backwards compatible with current style
|
* An implementation of {@link Contract} that is backwards compatible with current style
|
||||||
@@ -226,12 +227,19 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
return interactionTx.id;
|
return interactionTx.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
async bundleInteraction<Input>(input: Input, tags: Tags = [], strict = false): Promise<any | null> {
|
async bundleInteraction<Input>(
|
||||||
|
input: Input,
|
||||||
|
options: {
|
||||||
|
tags: [];
|
||||||
|
strict: false;
|
||||||
|
vrf: false;
|
||||||
|
}
|
||||||
|
): Promise<any | null> {
|
||||||
this.logger.info('Bundle interaction input', input);
|
this.logger.info('Bundle interaction input', input);
|
||||||
if (!this.signer) {
|
if (!this.signer) {
|
||||||
throw new Error("Wallet not connected. Use 'connect' method first.");
|
throw new Error("Wallet not connected. Use 'connect' method first.");
|
||||||
}
|
}
|
||||||
const interactionTx = await this.createInteraction(input, tags, emptyTransfer, strict);
|
const interactionTx = await this.createInteraction(input, options.tags, emptyTransfer, options.strict, options.vrf);
|
||||||
|
|
||||||
const response = await fetch(`${this._evaluationOptions.bundlerUrl}gateway/sequencer/register`, {
|
const response = await fetch(`${this._evaluationOptions.bundlerUrl}gateway/sequencer/register`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -264,7 +272,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
input: Input,
|
input: Input,
|
||||||
tags: { name: string; value: string }[],
|
tags: { name: string; value: string }[],
|
||||||
transfer: ArTransfer,
|
transfer: ArTransfer,
|
||||||
strict: boolean
|
strict: boolean,
|
||||||
|
vrf = false
|
||||||
) {
|
) {
|
||||||
if (this._evaluationOptions.internalWrites) {
|
if (this._evaluationOptions.internalWrites) {
|
||||||
// Call contract and verify if there are any internal writes:
|
// Call contract and verify if there are any internal writes:
|
||||||
@@ -299,6 +308,13 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (vrf) {
|
||||||
|
tags.push({
|
||||||
|
name: SmartWeaveTags.REQUEST_VRF,
|
||||||
|
value: 'true'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const interactionTx = await createTx(
|
const interactionTx = await createTx(
|
||||||
this.smartweave.arweave,
|
this.smartweave.arweave,
|
||||||
this.signer,
|
this.signer,
|
||||||
|
|||||||
@@ -15,5 +15,6 @@ export enum SmartWeaveTags {
|
|||||||
INTERACT_WRITE = 'Interact-Write',
|
INTERACT_WRITE = 'Interact-Write',
|
||||||
WASM_LANG = 'Wasm-Lang',
|
WASM_LANG = 'Wasm-Lang',
|
||||||
WASM_LANG_VERSION = 'Wasm-Lang-Version',
|
WASM_LANG_VERSION = 'Wasm-Lang-Version',
|
||||||
WASM_META = 'Wasm-Meta'
|
WASM_META = 'Wasm-Meta',
|
||||||
|
REQUEST_VRF = 'Request-Vrf'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export class ArweaveGatewayInteractionsLoader implements InteractionsLoader {
|
|||||||
|
|
||||||
private readonly arweaveWrapper: ArweaveWrapper;
|
private readonly arweaveWrapper: ArweaveWrapper;
|
||||||
|
|
||||||
constructor(private readonly arweave: Arweave) {
|
constructor(protected readonly arweave: Arweave) {
|
||||||
this.arweaveWrapper = new ArweaveWrapper(arweave);
|
this.arweaveWrapper = new ArweaveWrapper(arweave);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,10 +15,16 @@ import {
|
|||||||
InteractionResult,
|
InteractionResult,
|
||||||
LoggerFactory,
|
LoggerFactory,
|
||||||
StateEvaluator,
|
StateEvaluator,
|
||||||
TagsParser
|
TagsParser,
|
||||||
|
VrfData
|
||||||
} from '@smartweave';
|
} from '@smartweave';
|
||||||
import Arweave from 'arweave';
|
import Arweave from 'arweave';
|
||||||
|
|
||||||
|
import { ProofHoHash } from '@idena/vrf-js';
|
||||||
|
import elliptic from 'elliptic';
|
||||||
|
|
||||||
|
const EC = new elliptic.ec('secp256k1');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class contains the base functionality of evaluating the contracts state - according
|
* This class contains the base functionality of evaluating the contracts state - according
|
||||||
* to the SmartWeave protocol.
|
* to the SmartWeave protocol.
|
||||||
@@ -78,6 +84,12 @@ export abstract class DefaultStateEvaluator implements StateEvaluator {
|
|||||||
|
|
||||||
const interactionTx: GQLNodeInterface = missingInteraction.node;
|
const interactionTx: GQLNodeInterface = missingInteraction.node;
|
||||||
|
|
||||||
|
if (interactionTx.vrf) {
|
||||||
|
if (!this.verifyVrf(interactionTx.vrf, interactionTx.sortKey, this.arweave)) {
|
||||||
|
throw new Error('Vrf verification failed.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`[${contractDefinition.txId}][${missingInteraction.node.id}][${missingInteraction.node.block.height}]: ${
|
`[${contractDefinition.txId}][${missingInteraction.node.id}][${missingInteraction.node.block.height}]: ${
|
||||||
missingInteractions.indexOf(missingInteraction) + 1
|
missingInteractions.indexOf(missingInteraction) + 1
|
||||||
@@ -242,6 +254,24 @@ export abstract class DefaultStateEvaluator implements StateEvaluator {
|
|||||||
return evalStateResult;
|
return evalStateResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private verifyVrf(vrf: VrfData, sortKey: string, arweave: Arweave): boolean {
|
||||||
|
const keys = EC.keyFromPublic(vrf.pubkey, 'hex');
|
||||||
|
|
||||||
|
let hash;
|
||||||
|
try {
|
||||||
|
// ProofHoHash throws its own 'invalid vrf' exception
|
||||||
|
hash = ProofHoHash(
|
||||||
|
keys.getPublic(),
|
||||||
|
arweave.utils.stringToBuffer(sortKey),
|
||||||
|
arweave.utils.b64UrlToBuffer(vrf.proof)
|
||||||
|
);
|
||||||
|
} catch (e: any) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return arweave.utils.bufferTob64Url(hash) == vrf.index;
|
||||||
|
}
|
||||||
|
|
||||||
private logResult<State>(
|
private logResult<State>(
|
||||||
result: InteractionResult<State, unknown>,
|
result: InteractionResult<State, unknown>,
|
||||||
currentTx: GQLNodeInterface,
|
currentTx: GQLNodeInterface,
|
||||||
|
|||||||
@@ -51,6 +51,14 @@ export interface GQLNodeInterface {
|
|||||||
confirmationStatus?: string;
|
confirmationStatus?: string;
|
||||||
source?: string;
|
source?: string;
|
||||||
bundlerTxId?: string;
|
bundlerTxId?: string;
|
||||||
|
vrf?: VrfData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VrfData {
|
||||||
|
index: string;
|
||||||
|
proof: string;
|
||||||
|
bigint: string;
|
||||||
|
pubkey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GQLEdgeInterface {
|
export interface GQLEdgeInterface {
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
import Arweave from 'arweave';
|
import Arweave from 'arweave';
|
||||||
import { GQLNodeInterface, GQLTagInterface } from './gqlResult';
|
import {GQLNodeInterface, GQLTagInterface, VrfData} from './gqlResult';
|
||||||
import { EvaluationOptions } from '@smartweave/core';
|
import { EvaluationOptions } from '@smartweave/core';
|
||||||
|
import {kMaxLength} from "buffer";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -34,6 +35,7 @@ export class SmartWeaveGlobal {
|
|||||||
gasLimit: number;
|
gasLimit: number;
|
||||||
transaction: Transaction;
|
transaction: Transaction;
|
||||||
block: Block;
|
block: Block;
|
||||||
|
vrf: Vrf;
|
||||||
arweave: Pick<Arweave, 'ar' | 'wallets' | 'utils' | 'crypto'>;
|
arweave: Pick<Arweave, 'ar' | 'wallets' | 'utils' | 'crypto'>;
|
||||||
contract: {
|
contract: {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -102,6 +104,7 @@ export class SmartWeaveGlobal {
|
|||||||
throw new Error('Not implemented - should be set by HandlerApi implementor');
|
throw new Error('Not implemented - should be set by HandlerApi implementor');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
this.vrf = new Vrf(this);
|
||||||
|
|
||||||
this.useGas = this.useGas.bind(this);
|
this.useGas = this.useGas.bind(this);
|
||||||
}
|
}
|
||||||
@@ -189,3 +192,30 @@ class Block {
|
|||||||
return this.global._activeTx.block.timestamp;
|
return this.global._activeTx.block.timestamp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Vrf {
|
||||||
|
constructor(private readonly global: SmartWeaveGlobal) {}
|
||||||
|
|
||||||
|
get data(): VrfData {
|
||||||
|
return this.global._activeTx.vrf;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the original generated random number as a BigInt string;
|
||||||
|
get value(): string {
|
||||||
|
return this.global._activeTx.vrf.bigint;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns a random value in a range from 1 to maxValue
|
||||||
|
randomInt(maxValue: number): number {
|
||||||
|
if (!Number.isInteger(maxValue)) {
|
||||||
|
throw new Error('Integer max value required for random integer generation');
|
||||||
|
}
|
||||||
|
const result = BigInt(this.global._activeTx.vrf.bigint) % BigInt(maxValue) + BigInt(1);
|
||||||
|
|
||||||
|
if (result > Number.MAX_SAFE_INTEGER || result < Number.MIN_SAFE_INTEGER) {
|
||||||
|
throw new Error('Random int cannot be cast to number');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user