diff --git a/src/__tests__/integration/basic/vrf.test.ts b/src/__tests__/integration/basic/vrf.test.ts index 304fd64..3326ad2 100644 --- a/src/__tests__/integration/basic/vrf.test.ts +++ b/src/__tests__/integration/basic/vrf.test.ts @@ -170,6 +170,75 @@ describe('Testing the Profit Sharing Token', () => { expect(vrf['random_99_1'] == vrf['random_99_2']).toBe(true); expect(vrf['random_99_2'] == vrf['random_99_3']).toBe(true); }); + + it('should allow to test VRF on a standard forLocal Warp instance in strict mode', async () => { + const localWarp = WarpFactory.forLocal(1823); + + const { contractTxId: vrfContractTxId } = await localWarp.createContract.deploy({ + wallet, + initState: JSON.stringify(initialState), + src: contractSrc + }); + + await mineBlock(localWarp); + const vrfContract = localWarp.contract(vrfContractTxId).connect(wallet); + await vrfContract.writeInteraction( + { + function: 'vrf' + }, + { vrf: true, strict: true } + ); + const result = await vrfContract.readState(); + const lastTxId = Object.keys(result.cachedValue.validity).pop(); + const vrf = (result.cachedValue.state as any).vrf[lastTxId]; + + 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 allow to test VRF on a standard forLocal Warp instance with internal writes', async () => { + const localWarp = WarpFactory.forLocal(1823); + + const { contractTxId: vrfContractTxId } = await localWarp.createContract.deploy({ + wallet, + initState: JSON.stringify(initialState), + src: contractSrc + }); + + await mineBlock(localWarp); + const vrfContract = localWarp + .contract(vrfContractTxId) + .setEvaluationOptions({ + internalWrites: true + }) + .connect(wallet); + const result2 = await vrfContract.writeInteraction( + { + function: 'vrf' + }, + { vrf: true, strict: true } + ); + const result = await vrfContract.readState(); + const lastTxId = Object.keys(result.cachedValue.validity).pop(); + const vrf = (result.cachedValue.state as any).vrf[lastTxId]; + + 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); + }); }); class VrfDecorator extends ArweaveGatewayInteractionsLoader { diff --git a/src/__tests__/regression/test-cases/read-state-vm.json b/src/__tests__/regression/test-cases/read-state-vm.json index de5eef9..0012595 100644 --- a/src/__tests__/regression/test-cases/read-state-vm.json +++ b/src/__tests__/regression/test-cases/read-state-vm.json @@ -20,7 +20,6 @@ "lbHyzjQH4oZ4JxYuYzY20iLYEe4mX1p7ApU-S2Ew218", "UvjBvJUy8pOMR_lf85tBDaJD0jF85G5Ayj1p2h7yols", "f1wibc4fPQbcOtHR9ZlcfiJZicJSHA2mgETC-3WHMME", - "FdY68iYqTvA40U34aXQBBYseGmIUmLV-u57bT7LZWm0", "usjm4PCxUd5mtaon7zc97-dt-3qf67yPyqgzLnLqk5A", "UjZ6sg7KvoF1XoW7ReB2X3P5uHAbCWYaUIzB7XrjTtM", "nxZu3_PWyO_4w2Q7mX2NxpBmEFp5a_Q2bv8CWfDfOjo", diff --git a/src/__tests__/regression/test-cases/read-state.json b/src/__tests__/regression/test-cases/read-state.json index f9c83ee..aa7d944 100644 --- a/src/__tests__/regression/test-cases/read-state.json +++ b/src/__tests__/regression/test-cases/read-state.json @@ -20,7 +20,6 @@ "lbHyzjQH4oZ4JxYuYzY20iLYEe4mX1p7ApU-S2Ew218", "UvjBvJUy8pOMR_lf85tBDaJD0jF85G5Ayj1p2h7yols", "f1wibc4fPQbcOtHR9ZlcfiJZicJSHA2mgETC-3WHMME", - "FdY68iYqTvA40U34aXQBBYseGmIUmLV-u57bT7LZWm0", "usjm4PCxUd5mtaon7zc97-dt-3qf67yPyqgzLnLqk5A", "UjZ6sg7KvoF1XoW7ReB2X3P5uHAbCWYaUIzB7XrjTtM", "nxZu3_PWyO_4w2Q7mX2NxpBmEFp5a_Q2bv8CWfDfOjo", diff --git a/src/contract/HandlerBasedContract.ts b/src/contract/HandlerBasedContract.ts index fcb3709..3598ffa 100644 --- a/src/contract/HandlerBasedContract.ts +++ b/src/contract/HandlerBasedContract.ts @@ -35,6 +35,7 @@ import { import { Tags, ArTransfer, emptyTransfer, ArWallet } from './deploy/CreateContract'; import { SourceData, SourceImpl } from './deploy/impl/SourceImpl'; import { InnerWritesEvaluator } from './InnerWritesEvaluator'; +import { generateMockVrf } from '../utils/vrf'; /** * An implementation of {@link Contract} that is backwards compatible with current style @@ -332,7 +333,7 @@ export class HandlerBasedContract implements Contract { // 3. Verify the callStack and search for any "internalWrites" transactions // 4. For each found "internalWrite" transaction - generate additional tag: // {name: 'InternalWrite', value: callingContractTxId} - const handlerResult = await this.callContract(input, undefined, undefined, tags, transfer, strict); + const handlerResult = await this.callContract(input, undefined, undefined, tags, transfer, strict, vrf); if (strict && handlerResult.type !== 'ok') { throw Error(`Cannot create interaction: ${handlerResult.errorMessage}`); @@ -352,7 +353,7 @@ export class HandlerBasedContract implements Contract { this.logger.debug('Tags with inner calls', tags); } else { if (strict) { - const handlerResult = await this.callContract(input, undefined, undefined, tags, transfer, strict); + const handlerResult = await this.callContract(input, undefined, undefined, tags, transfer, strict, vrf); if (handlerResult.type !== 'ok') { throw Error(`Cannot create interaction: ${handlerResult.errorMessage}`); } @@ -533,7 +534,8 @@ export class HandlerBasedContract implements Contract { sortKey?: string, tags: Tags = [], transfer: ArTransfer = emptyTransfer, - strict = false + strict = false, + vrf = false ): Promise> { this.logger.info('Call contract input', input); this.maybeResetRootContract(); @@ -552,6 +554,8 @@ export class HandlerBasedContract implements Contract { if (caller) { effectiveCaller = caller; } else if (this.signer) { + // we're creating this transaction just to call the signing function on it + // - and retrieve the caller/owner const dummyTx = await arweave.createTransaction({ data: Math.random().toString().slice(-4), reward: '72600854', @@ -600,6 +604,10 @@ export class HandlerBasedContract implements Contract { dummyTx.sortKey = await this._sorter.createSortKey(dummyTx.block.id, dummyTx.id, dummyTx.block.height, true); dummyTx.strict = strict; + if (vrf) { + dummyTx.vrf = generateMockVrf(dummyTx.sortKey, arweave); + } + const handleResult = await this.evalInteraction( { interaction, diff --git a/src/core/modules/impl/ArweaveGatewayInteractionsLoader.ts b/src/core/modules/impl/ArweaveGatewayInteractionsLoader.ts index d248404..b9ef14f 100644 --- a/src/core/modules/impl/ArweaveGatewayInteractionsLoader.ts +++ b/src/core/modules/impl/ArweaveGatewayInteractionsLoader.ts @@ -9,14 +9,13 @@ import { import { Benchmark } from '../../../logging/Benchmark'; import { LoggerFactory } from '../../../logging/LoggerFactory'; import { ArweaveWrapper } from '../../../utils/ArweaveWrapper'; -import { bufToBn, sleep } from '../../../utils/utils'; +import { sleep } from '../../../utils/utils'; import { GW_TYPE, InteractionsLoader } from '../InteractionsLoader'; import { InteractionsSorter } from '../InteractionsSorter'; import { EvaluationOptions } from '../StateEvaluator'; import { LexicographicalInteractionsSorter } from './LexicographicalInteractionsSorter'; import { WarpEnvironment } from '../../Warp'; -import elliptic from 'elliptic'; -import { Evaluate } from '@idena/vrf-js'; +import { generateMockVrf } from '../../../utils/vrf'; const MAX_REQUEST = 100; @@ -41,10 +40,6 @@ export function bundledTxsFilter(tx: GQLEdgeInterface) { return !tx.node.parent?.id && !tx.node.bundledIn?.id; } -const EC = new elliptic.ec('secp256k1'); -const key = EC.genKeyPair(); -const pubKeyS = key.getPublic(true, 'hex'); - export class ArweaveGatewayInteractionsLoader implements InteractionsLoader { private readonly logger = LoggerFactory.INST.create('ArweaveGatewayInteractionsLoader'); @@ -179,14 +174,7 @@ export class ArweaveGatewayInteractionsLoader implements InteractionsLoader { return t.name == SmartWeaveTags.REQUEST_VRF && t.value === 'true'; }) ) { - const data = this.arweave.utils.stringToBuffer(interaction.sortKey); - const [index, proof] = Evaluate(key.getPrivate().toArray(), data); - interaction.vrf = { - index: this.arweave.utils.bufferTob64Url(index), - proof: this.arweave.utils.bufferTob64Url(proof), - bigint: bufToBn(index).toString(), - pubkey: pubKeyS - }; + interaction.vrf = generateMockVrf(interaction.sortKey, this.arweave); } } diff --git a/src/legacy/create-interaction-tx.ts b/src/legacy/create-interaction-tx.ts index 871d606..8bf3916 100644 --- a/src/legacy/create-interaction-tx.ts +++ b/src/legacy/create-interaction-tx.ts @@ -29,8 +29,8 @@ export async function createInteractionTx( } // both reward and last_tx are irrelevant in case of interactions - // that are bundled. So to speed up the procees (and prevent the arweave-js - // from calling /tx_anchor and /price endpoints) - we're presetting theses + // that are bundled. So to speed up the process (and prevent the arweave-js + // from calling /tx_anchor and /price endpoints) - we're presetting these // values here if (dummy) { options.reward = '72600854'; diff --git a/src/utils/vrf.ts b/src/utils/vrf.ts new file mode 100644 index 0000000..9c3b9c5 --- /dev/null +++ b/src/utils/vrf.ts @@ -0,0 +1,20 @@ +import elliptic from 'elliptic'; +import Arweave from 'arweave'; +import { Evaluate } from '@idena/vrf-js'; +import { bufToBn } from './utils'; +import { VrfData } from '../legacy/gqlResult'; + +const EC = new elliptic.ec('secp256k1'); +const key = EC.genKeyPair(); +const pubKeyS = key.getPublic(true, 'hex'); + +export function generateMockVrf(sortKey: string, arweave: Arweave): VrfData { + const data = arweave.utils.stringToBuffer(sortKey); + const [index, proof] = Evaluate(key.getPrivate().toArray(), data); + return { + index: arweave.utils.bufferTob64Url(index), + proof: arweave.utils.bufferTob64Url(proof), + bigint: bufToBn(index).toString(), + pubkey: pubKeyS + }; +} diff --git a/tools/contract.ts b/tools/contract.ts index caec38a..5e55247 100644 --- a/tools/contract.ts +++ b/tools/contract.ts @@ -31,12 +31,11 @@ async function main() { logging: false // Enable network request logging }); - const warp = WarpFactory.forMainnet({...defaultCacheOptions, inMemory: false}); + const warp = WarpFactory.forMainnet({...defaultCacheOptions, inMemory: true}); try { - const contract = warp.contract("5Yt1IujBmOm1LSux9KDUTjCE7rJqepzP7gZKf_DyzWI"); + const contract = warp.contract("Ws9hhYckc-zSnVmbBep6q_kZD5zmzYzDmgMC50nMiuE"); const cacheResult = await contract .setEvaluationOptions({ - allowBigInt: true }) .readState();