[FEATURE] Whitelisting contracts for internal writes #403
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
} from '../../..';
|
||||
import { DeployPlugin } from 'warp-contracts-plugin-deploy';
|
||||
import path from 'path';
|
||||
import { WritesAware } from '../../../src';
|
||||
|
||||
jest.setTimeout(30000);
|
||||
|
||||
@@ -42,7 +43,7 @@ describe('Testing the Rust WASM Profit Sharing Token', () => {
|
||||
let arweave: Arweave;
|
||||
let arlocal: ArLocal;
|
||||
let warp: Warp;
|
||||
let toyContract: Contract<State>;
|
||||
let toyContract: Contract<State & WritesAware>;
|
||||
|
||||
let contractTxId: string;
|
||||
|
||||
|
||||
@@ -112,11 +112,13 @@
|
||||
"json-schema-to-typescript": "^11.0.1",
|
||||
"node-stdlib-browser": "^1.2.0",
|
||||
"prettier": "^2.3.2",
|
||||
"ramda": "^0.29.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"smartweave": "0.4.48",
|
||||
"ts-jest": "^28.0.7",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript": "^4.9.5",
|
||||
"warp-contracts": "^1.4.5",
|
||||
"warp-contracts-plugin-deploy": "1.0.8-beta.0",
|
||||
"warp-contracts-plugin-vm2": "1.0.0",
|
||||
"warp-contracts-plugin-vrf": "^1.0.3",
|
||||
|
||||
@@ -51,4 +51,12 @@ export async function handle(state, action) {
|
||||
if (action.input.function === 'justThrow') {
|
||||
throw new ContractError('Error from justThrow function');
|
||||
}
|
||||
|
||||
if (action.input.function === 'setAllowedSrc') {
|
||||
const allowedSrc = action.input.allowedSrc;
|
||||
|
||||
state.allowedSrcTxIds = allowedSrc;
|
||||
|
||||
return { state };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
/* eslint-disable */
|
||||
import fs from "fs";
|
||||
|
||||
import ArLocal from "arlocal";
|
||||
import { JWKInterface } from "arweave/node/lib/wallet";
|
||||
import path from "path";
|
||||
import { mineBlock } from "../../_helpers";
|
||||
import { Contract, WritesAware } from "../../../../contract/Contract";
|
||||
import { Warp } from "../../../../core/Warp";
|
||||
import { WarpFactory } from "../../../../core/WarpFactory";
|
||||
import { LoggerFactory } from "../../../../logging/LoggerFactory";
|
||||
import { DeployPlugin } from "warp-contracts-plugin-deploy";
|
||||
import Transaction from "arweave/node/lib/transaction";
|
||||
import { WARP_TAGS } from "../../../../core/KnownTags";
|
||||
import { createInteractionTx } from "../../../../legacy/create-interaction-tx";
|
||||
import { Signature } from "../../../../contract/Signature";
|
||||
|
||||
interface ExampleContractState {
|
||||
counter: number;
|
||||
errorCounter: number;
|
||||
}
|
||||
|
||||
type CalleeState = ExampleContractState & WritesAware;
|
||||
|
||||
describe("Testing internal writes with whitelist", () => {
|
||||
let callingContractSrc: string;
|
||||
let callingContractInitialState: string;
|
||||
let calleeContractSrc: string;
|
||||
let calleeInitialState: string;
|
||||
|
||||
let wallet: JWKInterface;
|
||||
|
||||
let arlocal: ArLocal;
|
||||
let warp: Warp;
|
||||
let calleeContract: Contract<CalleeState>;
|
||||
let callingContract: Contract<ExampleContractState>;
|
||||
let calleeTxId;
|
||||
let callingTxId;
|
||||
let callingSrcTxId;
|
||||
|
||||
const port = 1289;
|
||||
|
||||
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(port, false);
|
||||
await arlocal.start();
|
||||
LoggerFactory.INST.logLevel("error");
|
||||
|
||||
warp = WarpFactory.forLocal(port).use(new DeployPlugin());
|
||||
({ jwk: wallet } = await warp.generateWallet());
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await arlocal.stop();
|
||||
});
|
||||
|
||||
async function deployContracts() {
|
||||
callingContractSrc = fs.readFileSync(path.join(__dirname, "../../data/writing-contract.js"), "utf8");
|
||||
callingContractInitialState = fs.readFileSync(path.join(__dirname, "../../data/writing-contract-state.json"), "utf8");
|
||||
calleeContractSrc = fs.readFileSync(path.join(__dirname, "../../data/example-contract.js"), "utf8");
|
||||
calleeInitialState = fs.readFileSync(path.join(__dirname, "../../data/example-contract-state.json"), "utf8");
|
||||
|
||||
({ contractTxId: callingTxId, srcTxId: callingSrcTxId } = await warp.deploy({
|
||||
wallet,
|
||||
initState: callingContractInitialState,
|
||||
src: callingContractSrc
|
||||
}));
|
||||
|
||||
callingContract = warp
|
||||
.contract<ExampleContractState>(callingTxId)
|
||||
.setEvaluationOptions({
|
||||
internalWrites: true,
|
||||
mineArLocalBlocks: false
|
||||
})
|
||||
.connect(wallet);
|
||||
await mineBlock(warp);
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
await deployContracts();
|
||||
|
||||
({ contractTxId: calleeTxId } = await warp.deploy({
|
||||
wallet,
|
||||
initState: JSON.stringify({
|
||||
allowedSrcTxIds: [],
|
||||
...JSON.parse(calleeInitialState)
|
||||
}),
|
||||
src: calleeContractSrc
|
||||
}));
|
||||
|
||||
calleeContract = warp
|
||||
.contract<CalleeState>(calleeTxId)
|
||||
.setEvaluationOptions({
|
||||
internalWrites: true,
|
||||
mineArLocalBlocks: false
|
||||
})
|
||||
.connect(wallet);
|
||||
|
||||
await mineBlock(warp);
|
||||
});
|
||||
|
||||
it("should block internal write on creation in strict mode", async () => {
|
||||
await expect(callingContract.writeInteraction({
|
||||
function: "writeContract",
|
||||
contractId: calleeTxId,
|
||||
amount: 10
|
||||
}, { strict: true }))
|
||||
.rejects.toThrowError("[WriteNotAllowed]");
|
||||
});
|
||||
|
||||
it("should skip evaluation of the inner write tx", async () => {
|
||||
await calleeContract.writeInteraction({ function: "add" });
|
||||
await mineBlock(warp);
|
||||
|
||||
const invalidTx2 = await callingContract.writeInteraction({
|
||||
function: "writeContract",
|
||||
contractId: calleeTxId,
|
||||
amount: 10
|
||||
});
|
||||
await mineBlock(warp);
|
||||
|
||||
await calleeContract.writeInteraction({ function: "add" });
|
||||
await mineBlock(warp);
|
||||
|
||||
const result = await calleeContract.readState();
|
||||
expect(result.cachedValue.validity[invalidTx2.originalTxId]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should allow evaluation after adding to allowed array", async () => {
|
||||
await calleeContract.writeInteraction(
|
||||
{
|
||||
function: "setAllowedSrc",
|
||||
allowedSrc: [callingSrcTxId]
|
||||
});
|
||||
await mineBlock(warp);
|
||||
|
||||
const writeTx = await callingContract.writeInteraction({
|
||||
function: "writeContract",
|
||||
contractId: calleeTxId,
|
||||
amount: 10
|
||||
});
|
||||
await mineBlock(warp);
|
||||
|
||||
const result = await calleeContract.readState();
|
||||
expect(result.cachedValue.validity[writeTx.originalTxId]).toBeTruthy();
|
||||
expect(result.cachedValue.state.counter).toEqual(567);
|
||||
});
|
||||
|
||||
it("should block writes made outside of the SDK if whitelist empty", async () => {
|
||||
// clear the white list
|
||||
await calleeContract.writeInteraction(
|
||||
{
|
||||
function: "setAllowedSrc",
|
||||
allowedSrc: []
|
||||
});
|
||||
await mineBlock(warp);
|
||||
|
||||
const hackedTx = await createInteractionTx(
|
||||
warp.arweave,
|
||||
(new Signature(warp, wallet)).signer,
|
||||
callingTxId,
|
||||
{
|
||||
function: "writeContract",
|
||||
contractId: calleeTxId,
|
||||
amount: 10
|
||||
},
|
||||
[{name: WARP_TAGS.INTERACT_WRITE, value: calleeTxId}],
|
||||
'',
|
||||
'0',
|
||||
false,
|
||||
false
|
||||
);
|
||||
const response = await warp.arweave.transactions.post(hackedTx);
|
||||
expect(response.status).toEqual(200);
|
||||
await mineBlock(warp);
|
||||
|
||||
const result = await calleeContract.readState();
|
||||
expect(result.cachedValue.validity[hackedTx.id]).toBeFalsy();
|
||||
expect(result.cachedValue.errorMessages[hackedTx.id]).toContain('[WriteNotAllowed]');
|
||||
});
|
||||
|
||||
it("should allow writes made outside of the SDK if whitelist non-empty", async () => {
|
||||
// add the white list
|
||||
await calleeContract.writeInteraction(
|
||||
{
|
||||
function: "setAllowedSrc",
|
||||
allowedSrc: [callingSrcTxId]
|
||||
});
|
||||
await mineBlock(warp);
|
||||
|
||||
const hackedTx = await createInteractionTx(
|
||||
warp.arweave,
|
||||
(new Signature(warp, wallet)).signer,
|
||||
callingTxId,
|
||||
{
|
||||
function: "writeContract",
|
||||
contractId: calleeTxId,
|
||||
amount: 10
|
||||
},
|
||||
[{name: WARP_TAGS.INTERACT_WRITE, value: calleeTxId}],
|
||||
'',
|
||||
'0',
|
||||
false,
|
||||
false
|
||||
);
|
||||
const response = await warp.arweave.transactions.post(hackedTx);
|
||||
expect(response.status).toEqual(200);
|
||||
await mineBlock(warp);
|
||||
|
||||
const result = await calleeContract.readState();
|
||||
expect(result.cachedValue.validity[hackedTx.id]).toBeTruthy();
|
||||
expect(result.cachedValue.state.counter).toEqual(577);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ArTransfer, Tags, ArWallet } from './deploy/CreateContract';
|
||||
import { CustomSignature } from './Signature';
|
||||
import { EvaluationOptionsEvaluator } from './EvaluationOptionsEvaluator';
|
||||
import { InteractionState } from './states/InteractionState';
|
||||
import { ContractDefinition } from '../core/ContractDefinition';
|
||||
|
||||
export type BenchmarkStats = { gatewayCommunication: number; stateEvaluation: number; total: number };
|
||||
|
||||
@@ -71,6 +72,10 @@ export type InnerCallType = 'read' | 'view' | 'write';
|
||||
|
||||
export type InnerCallData = { callingInteraction: GQLNodeInterface; callType: InnerCallType };
|
||||
|
||||
export type WritesAware = {
|
||||
allowedSrcTxIds?: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* A base interface to be implemented by SmartWeave Contracts clients
|
||||
* - contains "low-level" methods that allow to interact with any contract
|
||||
@@ -150,7 +155,8 @@ export interface Contract<State = unknown> {
|
||||
*/
|
||||
viewStateForTx<Input = unknown, View = unknown>(
|
||||
input: Input,
|
||||
transaction: GQLNodeInterface
|
||||
transaction: GQLNodeInterface,
|
||||
contractDefinition: ContractDefinition<State>
|
||||
): Promise<InteractionResult<State, View>>;
|
||||
|
||||
/**
|
||||
@@ -172,7 +178,17 @@ export interface Contract<State = unknown> {
|
||||
vrf?: boolean
|
||||
): Promise<InteractionResult<State, unknown>>;
|
||||
|
||||
applyInput<Input>(input: Input, transaction: GQLNodeInterface): Promise<InteractionResult<State, unknown>>;
|
||||
/**
|
||||
* Applies input on the current state of the contract.
|
||||
* Verifies whether the callee contract has the calling contract whitelisted (https://github.com/warp-contracts/warp/issues/403).
|
||||
* @param input
|
||||
* @param transaction
|
||||
*/
|
||||
applyInputSafe<Input>(
|
||||
input: Input,
|
||||
transaction: GQLNodeInterface,
|
||||
contractDef: ContractDefinition<State>
|
||||
): Promise<InteractionResult<State, unknown>>;
|
||||
|
||||
/**
|
||||
* Writes a new "interaction" transaction - i.e. such transaction that stores input for the contract.
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Benchmark } from '../logging/Benchmark';
|
||||
import { LoggerFactory } from '../logging/LoggerFactory';
|
||||
import { Evolve } from '../plugins/Evolve';
|
||||
import { ArweaveWrapper } from '../utils/ArweaveWrapper';
|
||||
import { getJsonResponse, sleep, stripTrailingSlash } from '../utils/utils';
|
||||
import { getJsonResponse, isWritesWhitelistAware, sleep, stripTrailingSlash } from '../utils/utils';
|
||||
import {
|
||||
BenchmarkStats,
|
||||
Contract,
|
||||
@@ -40,11 +40,11 @@ import { InteractionState } from './states/InteractionState';
|
||||
import { ContractInteractionState } from './states/ContractInteractionState';
|
||||
import { Crypto } from 'warp-isomorphic';
|
||||
import { VrfPluginFunctions } from '../core/WarpPlugin';
|
||||
import Arweave from 'arweave';
|
||||
import { ContractDefinition } from '../core/ContractDefinition';
|
||||
|
||||
/**
|
||||
* An implementation of {@link Contract} that is backwards compatible with current style
|
||||
* of writing SW contracts (ie. using the "handle" function).
|
||||
* of writing SW contracts (i.e. using the "handle" function).
|
||||
*
|
||||
* It requires {@link ExecutorFactory} that is using {@link HandlerApi} generic type.
|
||||
*/
|
||||
@@ -215,10 +215,11 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
|
||||
async viewStateForTx<Input, View>(
|
||||
input: Input,
|
||||
interactionTx: GQLNodeInterface
|
||||
transaction: GQLNodeInterface,
|
||||
contractDefinition: ContractDefinition<State>
|
||||
): Promise<InteractionResult<State, View>> {
|
||||
this.logger.info(`View state for ${this._contractTxId}`);
|
||||
return await this.doApplyInputOnTx<Input, View>(input, interactionTx, 'view');
|
||||
return await this.doApplyInputOnTx<Input, View>(input, transaction, 'view', contractDefinition);
|
||||
}
|
||||
|
||||
async dryWrite<Input>(
|
||||
@@ -232,9 +233,13 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
return await this.callContract<Input>(input, 'write', caller, undefined, tags, transfer, undefined, vrf);
|
||||
}
|
||||
|
||||
async applyInput<Input>(input: Input, transaction: GQLNodeInterface): Promise<InteractionResult<State, unknown>> {
|
||||
async applyInputSafe<Input>(
|
||||
input: Input,
|
||||
transaction: GQLNodeInterface,
|
||||
contractDef: ContractDefinition<State>
|
||||
): Promise<InteractionResult<State, unknown>> {
|
||||
this.logger.info(`Apply-input from transaction ${transaction.id} for ${this._contractTxId}`);
|
||||
return await this.doApplyInputOnTx<Input>(input, transaction, 'write');
|
||||
return await this.doApplyInputOnTx<Input>(input, transaction, 'write', contractDef);
|
||||
}
|
||||
|
||||
async writeInteraction<Input>(
|
||||
@@ -290,6 +295,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
effectiveVrf && environment !== 'mainnet',
|
||||
effectiveReward
|
||||
);
|
||||
|
||||
const response = await arweave.transactions.post(interactionTx);
|
||||
|
||||
if (response.status !== 200) {
|
||||
@@ -689,7 +695,6 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
dummyTx.sortKey = await this._sorter.createSortKey(dummyTx.block.id, dummyTx.id, dummyTx.block.height, true);
|
||||
dummyTx.strict = strict;
|
||||
if (vrf) {
|
||||
Arweave.utils;
|
||||
const vrfPlugin = this.warp.maybeLoadPlugin<void, VrfPluginFunctions>('vrf');
|
||||
if (vrfPlugin) {
|
||||
dummyTx.vrf = vrfPlugin.process().generateMockVrf(dummyTx.sortKey);
|
||||
@@ -720,7 +725,8 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
private async doApplyInputOnTx<Input, View = unknown>(
|
||||
input: Input,
|
||||
interactionTx: GQLNodeInterface,
|
||||
interactionType: InteractionType
|
||||
interactionType: InteractionType,
|
||||
callingContractDef: ContractDefinition<State>
|
||||
): Promise<InteractionResult<State, View>> {
|
||||
this.maybeResetRootContract();
|
||||
|
||||
@@ -738,6 +744,30 @@ export class HandlerBasedContract<State> implements Contract<State> {
|
||||
this.interactionState().update(this.txId(), evalStateResult.cachedValue);
|
||||
}
|
||||
|
||||
const calleeState = evalStateResult.cachedValue.state;
|
||||
if (interactionType === 'write' && isWritesWhitelistAware(calleeState)) {
|
||||
let errorMessage = null;
|
||||
if (calleeState.allowedSrcTxIds.length === 0) {
|
||||
errorMessage = '[WriteNotAllowed] Internal writes not allowed.';
|
||||
} else {
|
||||
const callingSrcTxId = callingContractDef.srcTxId;
|
||||
if (!calleeState.allowedSrcTxIds.includes(callingSrcTxId)) {
|
||||
errorMessage = `[WriteNotAllowed] Calling contract source ${callingSrcTxId} not allowed.`;
|
||||
}
|
||||
}
|
||||
|
||||
if (errorMessage) {
|
||||
return {
|
||||
type: 'error',
|
||||
errorMessage,
|
||||
originalValidity: evalStateResult.cachedValue.validity,
|
||||
originalErrorMessages: evalStateResult.cachedValue.errorMessages,
|
||||
state: calleeState,
|
||||
result: null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.debug('callContractForTx - evalStateResult', {
|
||||
result: evalStateResult.cachedValue.state,
|
||||
txId: this._contractTxId
|
||||
|
||||
@@ -63,7 +63,11 @@ export abstract class AbstractContractHandler<State> implements HandlerApi<State
|
||||
callingInteraction: this.swGlobal._activeTx,
|
||||
callType: 'write'
|
||||
});
|
||||
const result = await calleeContract.applyInput<Input>(input, this.swGlobal._activeTx);
|
||||
const result = await calleeContract.applyInputSafe<Input>(
|
||||
input,
|
||||
this.swGlobal._activeTx,
|
||||
this.contractDefinition
|
||||
);
|
||||
|
||||
this.logger.debug('Cache result?:', !this.swGlobal._activeTx.dry);
|
||||
const shouldAutoThrow =
|
||||
@@ -110,7 +114,7 @@ export abstract class AbstractContractHandler<State> implements HandlerApi<State
|
||||
callType: 'view'
|
||||
});
|
||||
|
||||
return await childContract.viewStateForTx<Input, View>(input, this.swGlobal._activeTx);
|
||||
return await childContract.viewStateForTx<Input, View>(input, this.swGlobal._activeTx, this.contractDefinition);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* eslint-disable */
|
||||
import copy from 'fast-copy';
|
||||
import { Buffer } from 'warp-isomorphic';
|
||||
import { WritesAware } from "../contract/Contract";
|
||||
|
||||
export const sleep = (ms: number): Promise<void> => {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
@@ -102,3 +103,11 @@ export async function getJsonResponse<T>(response: Promise<Response>): Promise<T
|
||||
const result = await r.json();
|
||||
return result as T;
|
||||
}
|
||||
|
||||
export function isWritesWhitelistAware(state: unknown): state is WritesAware {
|
||||
if (!state) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Array.isArray((state as WritesAware).allowedSrcTxIds);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { JWKInterface } from 'arweave/node/lib/wallet';
|
||||
import {ArweaveSigner, DeployPlugin} from "warp-contracts-plugin-deploy";
|
||||
|
||||
async function main() {
|
||||
let wallet: JWKInterface = readJSON('./.secrets/33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA.json');
|
||||
let wallet: JWKInterface = readJSON('./.secrets/warp.json');
|
||||
LoggerFactory.INST.logLevel('error');
|
||||
//LoggerFactory.INST.logLevel('debug', 'ExecutionContext');
|
||||
const logger = LoggerFactory.INST.create('deploy');
|
||||
@@ -20,26 +20,25 @@ async function main() {
|
||||
|
||||
try {
|
||||
const warp = WarpFactory.forMainnet({...defaultCacheOptions, inMemory: true})
|
||||
.use(new DeployPlugin())
|
||||
.useGwUrl("http://localhost:5666/");
|
||||
.use(new DeployPlugin());
|
||||
|
||||
const jsContractSrc = fs.readFileSync(path.join(__dirname, 'data/js/token-pst.js'), 'utf8');
|
||||
const initialState = fs.readFileSync(path.join(__dirname, 'data/js/token-pst.json'), 'utf8');
|
||||
|
||||
// case 1 - full deploy, js contract
|
||||
/* const { contractTxId, srcTxId } = await warp.deploy({
|
||||
wallet: new ArweaveSigner(wallet),
|
||||
const { contractTxId, srcTxId } = await warp.deploy({
|
||||
wallet: wallet,
|
||||
initState: initialState,
|
||||
src: jsContractSrc
|
||||
/!*evaluationManifest: {
|
||||
/*evaluationManifest: {
|
||||
evaluationOptions: {
|
||||
useKVStorage: true
|
||||
}
|
||||
}*!/
|
||||
});
|
||||
}*/
|
||||
}, true);
|
||||
|
||||
console.log('contractTxId:', contractTxId);
|
||||
console.log('srcTxId:', srcTxId);*/
|
||||
console.log('srcTxId:', srcTxId);
|
||||
// case 2 - deploy from source, js contract
|
||||
/*const {contractTxId} = await warp.createContract.deployFromSourceTx({
|
||||
wallet,
|
||||
@@ -63,9 +62,9 @@ async function main() {
|
||||
srcTxId: "5wXT-A0iugP9pWEyw-iTbB0plZ_AbmvlNKyBfGS3AUY",
|
||||
});*/
|
||||
|
||||
const contract = warp.contract<any>('SG9sKOZvKFQ7EcpJU3bS0pQWp2idQf3VY2Ki_5-hDjo').setEvaluationOptions({
|
||||
/*const contract = warp.contract<any>('SG9sKOZvKFQ7EcpJU3bS0pQWp2idQf3VY2Ki_5-hDjo').setEvaluationOptions({
|
||||
sequencerUrl: 'http://localhost:5666/'
|
||||
}).connect(wallet);
|
||||
}).connect(wallet);*/
|
||||
|
||||
await Promise.all([
|
||||
/* contract.writeInteraction<any>({
|
||||
@@ -86,7 +85,7 @@ async function main() {
|
||||
disableBundling: true
|
||||
})*/
|
||||
]);
|
||||
const {cachedValue} = await contract.readState();
|
||||
//const {cachedValue} = await contract.readState();
|
||||
|
||||
//logger.info("Result", await contract.getStorageValue('33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA'));
|
||||
//console.dir(cachedValue.state);
|
||||
|
||||
1665
tools/test.txt
1665
tools/test.txt
File diff suppressed because it is too large
Load Diff
24
yarn.lock
24
yarn.lock
@@ -6442,6 +6442,11 @@ queue-microtask@^1.2.2, queue-microtask@^1.2.3:
|
||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||
|
||||
ramda@^0.29.0:
|
||||
version "0.29.0"
|
||||
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.29.0.tgz#fbbb67a740a754c8a4cbb41e2a6e0eb8507f55fb"
|
||||
integrity sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==
|
||||
|
||||
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
|
||||
@@ -7497,6 +7502,25 @@ warp-contracts-plugin-vrf@^1.0.3:
|
||||
"@idena/vrf-js" "^1.0.1"
|
||||
elliptic "^6.5.4"
|
||||
|
||||
warp-contracts@^1.4.5:
|
||||
version "1.4.5"
|
||||
resolved "https://registry.yarnpkg.com/warp-contracts/-/warp-contracts-1.4.5.tgz#a9113c1f4fe963ca6a65410ae0917e55db5cc7fe"
|
||||
integrity sha512-hUlJt1DXQapPf7cQRUehv9bEMNl6gX9srJ3K3LFAjKvtv2evD22tdcAH0MYHb+QchGIBLqJwva9d2pC7pSA+PQ==
|
||||
dependencies:
|
||||
archiver "^5.3.0"
|
||||
arweave "1.13.7"
|
||||
async-mutex "^0.4.0"
|
||||
bignumber.js "9.1.1"
|
||||
events "3.3.0"
|
||||
fast-copy "^3.0.0"
|
||||
level "^8.0.0"
|
||||
memory-level "^1.0.0"
|
||||
safe-stable-stringify "2.4.1"
|
||||
stream-buffers "^3.0.2"
|
||||
unzipit "^1.4.0"
|
||||
warp-isomorphic "1.0.4"
|
||||
warp-wasm-metering "1.0.1"
|
||||
|
||||
warp-isomorphic@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/warp-isomorphic/-/warp-isomorphic-1.0.0.tgz#dccccfc046bc6ac77919f8be1024ced1385c70ea"
|
||||
|
||||
Reference in New Issue
Block a user