Skip to content

Commit

Permalink
Add set, get, and id
Browse files Browse the repository at this point in the history
  • Loading branch information
pouya-eghbali committed May 28, 2023
1 parent 31db2a6 commit 2e382f8
Show file tree
Hide file tree
Showing 19 changed files with 441 additions and 91 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,15 @@ solidq schema.yaml MyContract.sol
Book:
Name: string
Author: string indexed
Published: uint256 indexed
Published: uint256 indexed get set

Person:
Name: string
Birth: uint256 indexed

User:
Address: address id
Balance: uint256
```
is equivalent to
Expand All @@ -64,6 +68,8 @@ Book:
Author:
type: string
indexed: true
get: true
set: true
Published:
type: uint256
indexed: true
Expand All @@ -73,6 +79,11 @@ Person:
Birth:
type: uint256
indexed: true

User:
Address:
type: address
id: true
```
![SolidQuery Demo](./assets/solidq.demo.png)
Expand All @@ -81,6 +92,7 @@ Person:
We have planned the following features:
- Schema validation
- Fine control over contract generation
- Array fields
- Relations
Expand Down
23 changes: 16 additions & 7 deletions lib/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,31 @@ export const toCamel = (...arr) => camelCase(arr.join("-"));
* @return {Object} An object containing the type as a string and a boolean
* indicating whether it's indexed.
*/
export const parseFieldType = (type) => {
if (typeof type === "string") {
export const parseFieldType = (fieldType) => {
if (typeof fieldType === "string") {
const [type, ...properties] = fieldType.split(" ");
return {
type: type.split(" ").shift(),
indexed: type.endsWith("indexed"),
type,
...Object.fromEntries(properties.map((property) => [property, true])),
};
}
return type;
return fieldType;
};

const callDataTypes = ["string"];
const arrayDataTypes = ["string"];

/**
* Appends `calldata` to a data-type whenever required
* @param {string} type - Data type.
* @return {string} Data type with `calldata` prepended.
*/
export const toCallData = (type) =>
callDataTypes.includes(type) ? `${type} calldata` : type;
arrayDataTypes.includes(type) ? `${type} calldata` : type;

/**
* Appends `memory` to a data-type whenever required
* @param {string} type - Data type.
* @return {string} Data type with `memory` prepended.
*/
export const toMemory = (type) =>
arrayDataTypes.includes(type) ? `${type} memory` : type;
10 changes: 8 additions & 2 deletions lib/crud/all.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
export { getAddFunctions } from "./create.js";
export { getFindFunctions, getGetFunctions } from "./read.js";
export { getUpdateFunctions } from "./update.js";
export { getDeleteFunctions } from "./delete.js";

export {
getFindFunctions,
getGetFunctions,
getFieldGetFunctions,
} from "./read.js";

export { getUpdateFunctions, getFieldSetFunctions } from "./update.js";
76 changes: 58 additions & 18 deletions lib/crud/create.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { toCamel, parseFieldType } from "../common.js";
import { getIdField } from "../helpers/id.js";

/**
* Generates Solidity functions for adding records to the schema's structures.
Expand All @@ -9,31 +10,70 @@ import { toCamel, parseFieldType } from "../common.js";
export const getAddFunctions = (schema) => {
const functions = [];
for (const [structName, details] of Object.entries(schema)) {
const id = getIdField(structName, details);
const indexes = [];
for (const [fieldName, fieldType] of Object.entries(details)) {
const { indexed } = parseFieldType(fieldType);
if (indexed) {
const indexFunctionName = toCamel("add", structName, fieldName);
indexes.push(`${indexFunctionName}IndexForId(id, value);`);
const indexFunctionName = toCamel(
"add",
structName,
fieldName,
"index",
"for",
id.name
);
indexes.push(`${indexFunctionName}(${id.name}, value);`);
}
}
const functionName = toCamel("add", structName);
const fields = Object.keys(details).map((field) => `value.${field}`);
functions.push(`
/**
* @dev Adds a new ${structName} record and updates relevant indexes.
* @notice Emits a ${structName}Added event on success.
* @param value The new record to add.
* @return The ID of the newly added record.
*/
function ${functionName}(${structName} calldata value) external onlyOwner returns (uint256) {
uint256 id = ${structName}s.length;
${structName}s.push(value);
${indexes.join("\n")}
emit ${structName}Created(id, ${fields.join(", ")});
return id;
}
`);
const fields = Object.keys(details)
.filter((field) => field !== id.name)
.map((field) => `value.${field}`);
const counterName = toCamel(structName, "counter");
if (id.auto) {
const body = [
`uint256 ${id.name} = ${counterName}++;`,
`${structName}s[${id.name}] = value;`,
`${indexes.join("\n")}`,
`emit ${structName}Created(${id.name}, ${fields.join(", ")});`,
`return ${id.name};`,
]
.filter(Boolean)
.join("\n");
functions.push(`
/**
* @dev Adds a new ${structName} record and updates relevant indexes.
* @notice Emits a ${structName}Added event on success.
* @param value The new record to add.
* @return The ID of the newly added record.
*/
function ${functionName}(${structName} calldata value) external onlyOwner returns (uint256) {
${body}
}
`);
} else {
const idType = id.type;
const idName = id.name;
const body = [
`${structName}s[${idName}] = value;`,
`${indexes.join("\n")}`,
`emit ${structName}Created(${idName}, ${fields.join(", ")});`,
]
.filter(Boolean)
.join("\n");
functions.push(`
/**
* @dev Adds a new ${structName} record and updates relevant indexes.
* @notice Emits a ${structName}Added event on success.
* @param ${idName} The ${idName} of the record to add.
* @param value The new record to add.
*/
function ${functionName}(${idType} ${idName}, ${structName} calldata value) external onlyOwner {
${body}
}
`);
}
}
return functions.join("\n");
};
28 changes: 21 additions & 7 deletions lib/crud/delete.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { toCamel, parseFieldType } from "../common.js";
import { getIdField } from "../helpers/id.js";

/**
* Generates Solidity functions for deleting records from the schema's
Expand All @@ -10,25 +11,38 @@ import { toCamel, parseFieldType } from "../common.js";
export const getDeleteFunctions = (schema) => {
const functions = [];
for (const [structName, details] of Object.entries(schema)) {
const id = getIdField(structName, details);
const indexes = [];
for (const [fieldName, fieldType] of Object.entries(details)) {
const { indexed } = parseFieldType(fieldType);
if (indexed) {
const indexFunctionName = toCamel("delete", structName, fieldName);
indexes.push(`${indexFunctionName}IndexForId(id);`);
const indexFunctionName = toCamel(
"delete",
structName,
fieldName,
"index",
"for",
id.name
);
indexes.push(`${indexFunctionName}(${id.name});`);
}
}
const functionName = toCamel("delete", structName);
const body = [
`${indexes.join("\n")}`,
`delete ${structName}s[${id.name}];`,
`emit ${structName}Deleted(${id.name});`,
]
.filter(Boolean)
.join("\n");
functions.push(`
/**
* @dev Deletes a ${structName} record by its ID and updates relevant indexes.
* @notice Emits a ${structName}Deleted event on success.
* @param id The ID of the record to delete.
* @param ${id.name} The ID of the record to delete.
*/
function ${functionName}(uint256 id) external onlyOwner {
${indexes.join("\n")}
delete ${structName}s[id];
emit ${structName}Deleted(id);
function ${functionName}(${id.type} ${id.name}) external onlyOwner {
${body}
}
`);
}
Expand Down
57 changes: 49 additions & 8 deletions lib/crud/read.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { toCamel, toCallData, parseFieldType } from "../common.js";
import { toCamel, toCallData, toMemory, parseFieldType } from "../common.js";
import { getIdField } from "../helpers/id.js";

/**
* Generates Solidity functions for finding records in the schema's
Expand All @@ -10,6 +11,7 @@ import { toCamel, toCallData, parseFieldType } from "../common.js";
export const getFindFunctions = (schema) => {
const functions = [];
for (const [structName, details] of Object.entries(schema)) {
const id = getIdField(structName, details);
for (const [fieldName, fieldType] of Object.entries(details)) {
const { type, indexed } = parseFieldType(fieldType);
if (indexed) {
Expand All @@ -22,7 +24,7 @@ export const getFindFunctions = (schema) => {
* @param value The ${fieldName} value to search by.
* @return An array of matching record IDs.
*/
function ${functionName}(${calldataType} value) external view returns (uint256[] memory) {
function ${functionName}(${calldataType} value) external view returns (${id.type}[] memory) {
return ${indexName}Index[value];
}
`);
Expand All @@ -41,23 +43,62 @@ export const getFindFunctions = (schema) => {
*/
export const getGetFunctions = (schema) => {
const functions = [];
for (const [structName] of Object.entries(schema)) {
const functionName = toCamel("get", `${structName}s`, "by", "Id");
for (const [structName, details] of Object.entries(schema)) {
const id = getIdField(structName, details);
const functionName = toCamel("get", `${structName}s`, "by", id.name);
functions.push(`
/**
* @dev Retrieves an array of ${structName} records by their IDs.
* @param ids An array of record IDs to retrieve.
* @param ${id.name}List An array of record IDs to retrieve.
* @return An array of the retrieved records.
*/
function ${functionName}(uint256[] calldata ids) external view returns (${structName}[] memory) {
uint256 length = ids.length;
function ${functionName}(${id.type}[] calldata ${id.name}List) external view returns (${structName}[] memory) {
uint256 length = ${id.name}List.length;
${structName}[] memory result = new ${structName}[](length);
for (uint256 index = 0; index < length; index++) {
result[index] = ${structName}s[ids[index]];
result[index] = ${structName}s[${id.name}List[index]];
}
return result;
}
`);
}
return functions.join("\n");
};

/**
* Generates Solidity functions for retrieving a specific field of a specific
* record by its ID.
* @param {Object} schema - The schema object to parse.
* @return {string} Solidity functions for getting records, each function
* on a new line.
*/
export const getFieldGetFunctions = (schema) => {
const functions = [];
for (const [structName, details] of Object.entries(schema)) {
const id = getIdField(structName, details);
for (const [fieldName, fieldType] of Object.entries(details)) {
const { type, get } = parseFieldType(fieldType);
if (get) {
const functionName = toCamel(
"get",
structName,
fieldName,
"by",
id.name
);
const returnType = toMemory(type);
functions.push(`
/**
* @dev Retrieves the ${fieldName} of a ${structName} record by its ID.
* @param ${id.name} ${id.name} of the record to retrieve.
* @return The ${fieldName} of the ${structName}
*/
function ${functionName}(${id.type} ${id.name}) external view returns (${returnType}) {
return ${structName}s[${id.name}].${fieldName};
}
`);
}
}
}
return functions.join("\n");
};
Loading

0 comments on commit 2e382f8

Please sign in to comment.