eslint-plugin-o1js
Version:
o1js rules for ESLint
179 lines • 8.13 kB
JavaScript
"use strict";
const types_1 = require("../types");
const ast_utils_1 = require("../utils/ast-utils");
const selectors_1 = require("../utils/selectors");
// A map containing all Smart Contracts that we can immediately derive state count from
const knownContractState = new Map();
// A map containing all Smart Contracts that need their state to be derived from other Smart Contracts
const unknownContractState = new Map();
const rule = {
meta: {
messages: {
noGreaterStorageLimitInCircuit: `A Smart Contract can only have ${types_1.MAX_CONTRACT_STATES} allowed storage fields.`,
},
schema: [],
type: 'problem',
docs: {
description: `A Smart Contract can only have ${types_1.MAX_CONTRACT_STATES} allowed storage fields.`,
recommended: 'error',
url: '',
},
},
create(context) {
return {
'Program:exit': function () {
let derivedUnknownState = true;
// Continue to derive unknown states if we can succesfully derive a previous unknown state.
while (derivedUnknownState) {
derivedUnknownState = false; // Assume we can't derive an unknown state to break out of the while
unknownContractState.forEach((stateInfo, className) => {
stateInfo = stateInfo.map((state) => {
var _a;
if (state.kind === 'UnknownStateInfo') {
const { dependsOn, type, node } = state;
const contractState = (_a = knownContractState.get(dependsOn)) !== null && _a !== void 0 ? _a : [];
const contractStateCount = calculateContractState(contractState);
// Succesfully derived an unknown state, continue looping in the parent while loop
if (contractStateCount > 0) {
derivedUnknownState = true;
return {
kind: 'KnownStateInfo',
size: contractStateCount,
type,
node,
};
}
}
return state;
});
const unknownRemaining = stateInfo.filter((state) => {
return state.kind === 'UnknownStateInfo';
}).length;
if (unknownRemaining === 0) {
unknownContractState.delete(className);
knownContractState.set(className, stateInfo);
}
});
}
for (const [, contractState] of knownContractState) {
const stateDecoratorInfo = getStateDecoratorInfo(contractState);
const contractStateCount = calculateContractState(contractState);
if (stateDecoratorInfo && !stateDecoratorInfo.reported) {
if (contractStateCount > types_1.MAX_CONTRACT_STATES) {
context.report({
messageId: `noGreaterStorageLimitInCircuit`,
loc: stateDecoratorInfo.node.loc,
});
stateDecoratorInfo.reported = true; // Set reported to true to avoid reporting same error in multiple files
}
}
}
},
[selectors_1.SMART_CONTRACT_DEFINITION]: function (smartContractNode) {
findKnownAndUnknownStates(smartContractNode);
},
[selectors_1.CIRCUIT_VALUE_DEFINITION]: function (circuitValueNode) {
findKnownAndUnknownStates(circuitValueNode);
},
};
},
};
function calculateContractState(contractState) {
return contractState.reduce((acc, state) => {
if (state.type.kind === 'arrayProp') {
return acc + state.size * state.type.arrayPropLength;
}
else {
return acc + state.size;
}
}, 0);
}
function getStateDecoratorInfo(stateInfo) {
return stateInfo.find((state) => {
return state.type.kind === 'state';
});
}
function findKnownAndUnknownStates(smartContractNode) {
var _a, _b, _c, _d;
const stateInfo = [];
const classBody = (_a = (0, ast_utils_1.getClassBodyStatements)(smartContractNode)) !== null && _a !== void 0 ? _a : [];
let isAllPrimitiveState = true;
for (const classStatement of classBody) {
// Get the kind of decorator (`prop`, `state` or `arrayProp`) as well as the TS decorator node
const decorator = (0, ast_utils_1.getValidDecorator)(classStatement);
if (!decorator) {
continue;
}
// Get the user specified type from the decorator node (e.g `Field`, `Group` or a CircuitValue)
const o1DecoratorType = (_b = (0, ast_utils_1.getFirstDecoratorValue)(decorator.decorator)) !== null && _b !== void 0 ? _b : (0, ast_utils_1.getPropertyType)(classStatement);
if (!o1DecoratorType) {
continue;
}
// If the decorator type is a o1js primitive, get it's value
const primitive = (0, types_1.geto1jsPrimitive)(o1DecoratorType);
if (!primitive) {
isAllPrimitiveState = false;
}
if (decorator.kind === 'prop' || decorator.kind === 'state') {
if (primitive) {
const { size } = types_1.o1jsPrimitiveSizeInfo[primitive];
stateInfo.push({
kind: 'KnownStateInfo',
type: { kind: decorator.kind },
node: smartContractNode,
reported: false,
size,
});
}
else {
stateInfo.push({
kind: 'UnknownStateInfo',
type: {
kind: decorator.kind,
},
node: smartContractNode,
dependsOn: o1DecoratorType,
});
}
}
else if (decorator.kind === 'arrayProp') {
const arrayPropLength = (_c = (0, ast_utils_1.getSecondDecoratorValue)(decorator.decorator)) !== null && _c !== void 0 ? _c : 0;
if (primitive) {
const { size } = types_1.o1jsPrimitiveSizeInfo[primitive];
stateInfo.push({
kind: 'KnownStateInfo',
type: {
kind: decorator.kind,
arrayPropLength,
},
node: smartContractNode,
reported: false,
size,
});
}
else {
stateInfo.push({
kind: 'UnknownStateInfo',
type: {
kind: decorator.kind,
arrayPropLength,
},
node: smartContractNode,
dependsOn: o1DecoratorType,
});
}
}
}
// If all decorators are a o1js primitive, we can calculate the Smart Contract state count and
// insert it into the `knownContractStateMap`. If the user specifies a CircuitValue, we store the state info
// in `unknownContractState` to derive it's state count later.
const className = (_d = (0, ast_utils_1.getClassName)(smartContractNode)) !== null && _d !== void 0 ? _d : '';
if (isAllPrimitiveState) {
knownContractState.set(className, stateInfo);
}
else {
unknownContractState.set(className, stateInfo);
}
}
module.exports = rule;
//# sourceMappingURL=no-greater-storage-limit-in-circuit.js.map