Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/objectarium instance #82

Merged
merged 7 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ module.exports = {
},
plugins: ["@typescript-eslint"],
rules: {
indent: ["error", 4],
indent: ["error", 4, { SwitchCase: 1 }],
"linebreak-style": ["error", "unix"],
quotes: ["error", "double"],
semi: ["error", "always"],
Expand Down
8 changes: 7 additions & 1 deletion project.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ network:
file: ./proto/cosmwasm/wasm/v1/tx.proto
messages:
- MsgExecuteContract
- MsgInstantiateContract
dataSources:
- kind: cosmos/Runtime
startBlock: 3507305
startBlock: 2720090
mapping:
file: ./dist/index.js
handlers:
Expand Down Expand Up @@ -56,3 +57,8 @@ dataSources:
filter:
type: /cosmwasm.wasm.v1.MsgExecuteContract
contractCall: unpin_object
- handler: handleInitObjectarium
kind: cosmos/MessageHandler
filter:
type: /cosmwasm.wasm.v1.MsgInstantiateContract
contractCall: instantiate
84 changes: 82 additions & 2 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ type Message @entity {
"""
block: Block!
"""
Objectarium that message relates to (for a message which concerns an objectarium smart contract).
"""
objectarium: Objectarium
"""
Object that message relates to (for a message which concerns an object).
"""
objectariumObject: ObjectariumObject
Expand All @@ -152,9 +156,9 @@ type ObjectariumObject @entity {
"""
sender: String! @index
"""
Address of the smart contract instance (aka bucket) where the object is stored.
The smart contract instance (aka bucket) where the object is stored.
"""
contract: String! @index
objectarium: Objectarium!
"""
Addresses that have pinned the object.
"""
Expand All @@ -164,3 +168,79 @@ type ObjectariumObject @entity {
"""
messages: [Message]! @derivedFrom(field: "objectariumObject")
}

"""
BucketConfig is the type of the configuration of a bucket.
"""
type BucketConfig @jsonField {
"""
The algorithm used to hash the content of the objects to generate the id of the objects.
"""
hashAlgorithm: String
"""
The acceptable compression algorithms for the objects in the bucket.
"""
acceptedCompressionAlgorithms: [String!]
}

"""
BucketLimits is the type of the limits of a Objectarium bucket.
The limits are optional and if not set, there is no limit.
"""
type BucketLimits @jsonField {
"""
The maximum total size of the objects in the bucket.
"""
maxTotalSize: BigInt
"""
The maximum number of objects in the bucket.
"""
maxObjects: BigInt
"""
The maximum size of the objects in the bucket.
"""
maxObjectSize: BigInt
"""
The maximum number of pins in the bucket for an object.
"""
maxObjectPins: BigInt
}

"""
Objectarium smart contract instance (aka bucket).
"""
type Objectarium @entity {
ccamel marked this conversation as resolved.
Show resolved Hide resolved
"""
Identifier of the smart contract instance.
It is the address of the smart contract instance.
"""
id: ID!
"""
The owner of the bucket.
"""
owner: String!
"""
Label of the bucket.
"""
label: String!
"""
The name of the bucket.
"""
name: String!
ccamel marked this conversation as resolved.
Show resolved Hide resolved
"""
The configuration of the bucket.
"""
config: BucketConfig
"""
The limits of the bucket.
"""
limits: BucketLimits
"""
List of objects contained within the Objectarium instance.
"""
objectariumObjects: [ObjectariumObject] @derivedFrom(field: "objectarium")
"""
List of messages that concern the entity.
"""
messages: [Message]! @derivedFrom(field: "objectarium")
}
ccamel marked this conversation as resolved.
Show resolved Hide resolved
55 changes: 54 additions & 1 deletion src/mappings/helper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,57 @@
import { CosmosEvent, CosmosMessage } from "@subql/types-cosmos";
import type { CosmosEvent, CosmosMessage } from "@subql/types-cosmos";
import { Message } from "../types";
import type {
Event,
Attribute,
} from "@cosmjs/tendermint-rpc/build/tendermint37";

export const messageId = (msg: CosmosMessage | CosmosEvent): string =>
`${msg.tx.hash}-${msg.idx}`;

export const findEvent = (
events: Readonly<Event[]>,
event: string,
): Event | undefined => events.find(({ type }) => type === event);

export const findEventAttribute = (
events: Readonly<Event[]>,
event: string,
attribute: string,
): Attribute | undefined =>
findEvent(events, event)?.attributes.find(({ key }) => key === attribute);

export const referenceEntityInMessage = async (
msg: CosmosMessage,
entity: {
id: string;
messageField: keyof Pick<
Message,
| "objectariumObjectId"
| "objectariumId"
| "blockId"
| "transactionId"
>;
},
): Promise<void> => {
const message = await Message.get(messageId(msg));
if (!message) {
return;
}

switch (entity.messageField) {
case "objectariumObjectId":
message.objectariumObjectId = entity.id;
break;
case "objectariumId":
message.objectariumId = entity.id;
break;
case "blockId":
message.blockId = entity.id;
break;
case "transactionId":
message.transactionId = entity.id;
break;
}

await message.save();
};
fredericvilcot marked this conversation as resolved.
Show resolved Hide resolved
129 changes: 105 additions & 24 deletions src/mappings/objectariumHandlers.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,52 @@
import type { CosmosMessage } from "@subql/types-cosmos";
import type { MsgExecuteContract } from "cosmjs-types/cosmwasm/wasm/v1/tx";
import { Message, ObjectariumObject } from "../types";
import { messageId } from "./helper";
import type {
MsgExecuteContract,
MsgInstantiateContract,
} from "cosmjs-types/cosmwasm/wasm/v1/tx";
import {
ObjectariumObject,
Objectarium,
BucketConfig,
BucketLimits,
} from "../types";
import { findEventAttribute, referenceEntityInMessage } from "./helper";
import type { Event } from "@cosmjs/tendermint-rpc/build/tendermint37";

type StoreObject = {
pin: boolean;
};

type Msg = {
store_object: StoreObject;
type Execute = Omit<MsgExecuteContract, "msg"> & {
msg: {
store_object: StoreObject;
};
};

type ObjectariumMsgExecuteContract = Omit<MsgExecuteContract, "msg"> & {
msg: Msg;
type ObjectariumBucketConfig = {
hash_algorithm?: string;
accepted_compression_algorithms?: string[];
};

type ObjectariumBucketLimits = {
max_total_size?: bigint;
max_objects?: bigint;
max_object_size?: bigint;
max_object_pins?: bigint;
};

type Instantiate = Omit<MsgInstantiateContract, "msg"> & {
msg: {
bucket: string;
config?: ObjectariumBucketConfig;
limits?: ObjectariumBucketLimits;
};
};

type ContractCalls = Execute | Instantiate;
type ObjectariumMsg<T extends ContractCalls> = T;

export const handleStoreObject = async (
msg: CosmosMessage<ObjectariumMsgExecuteContract>,
msg: CosmosMessage<ObjectariumMsg<Execute>>,
): Promise<void> => {
const objectId = objectariumObjectId(msg.tx.tx.events);
if (!objectId) {
Expand All @@ -30,11 +59,14 @@ export const handleStoreObject = async (
await ObjectariumObject.create({
id: objectId,
sender,
contract,
objectariumId: contract,
pins: isPinned ? [sender] : [],
}).save();

await referenceObjectInMessage(msg, objectId);
await referenceEntityInMessage(msg, {
messageField: "objectariumObjectId",
id: objectId,
});
};

export const handleForgetObject = async (
Expand All @@ -46,7 +78,10 @@ export const handleForgetObject = async (
}

await ObjectariumObject.remove(objectId);
await referenceObjectInMessage(msg, objectId);
await referenceEntityInMessage(msg, {
messageField: "objectariumObjectId",
id: objectId,
});
};

export const handlePinObject = async (
Expand All @@ -64,7 +99,10 @@ export const handlePinObject = async (
await object.save();
}

await referenceObjectInMessage(msg, object.id);
await referenceEntityInMessage(msg, {
messageField: "objectariumObjectId",
id: object.id,
});
};

export const handleUnpinObject = async (
Expand All @@ -83,20 +121,66 @@ export const handleUnpinObject = async (
await object.save();
}

await referenceObjectInMessage(msg, object.id);
await referenceEntityInMessage(msg, {
messageField: "objectariumObjectId",
id: object.id,
});
};

export const referenceObjectInMessage = async (
msg: CosmosMessage,
objectId: string,
export const handleInitObjectarium = async (
msg: CosmosMessage<ObjectariumMsg<Instantiate>>,
): Promise<void> => {
const message = await Message.get(messageId(msg));
if (!message) {
const contractAddress = findEventAttribute(
msg.tx.tx.events,
"instantiate",
"_contract_address",
)?.value;

// TODO: filter the calling of this handler through the manifest.
// Justification: There seems to be a bug with the filtering of events
// that make it impossible to filter on the contract code id. The
// following codeId variable allows to filter the handling of the
// message before treating the msg as a objectarium instantiate msg.
const codeId = findEventAttribute(
msg.tx.tx.events,
"instantiate",
"code_id",
)?.value;

if (!contractAddress || codeId !== "4") {
ccamel marked this conversation as resolved.
Show resolved Hide resolved
return;
}

message.objectariumObjectId = objectId;
await message.save();
const {
sender,
label,
msg: { bucket, limits: bucketLimits, config: bucketConfig },
} = msg.msg.decodedMsg;
const limits: BucketLimits = {
maxTotalSize: bucketLimits?.max_total_size,
maxObjectSize: bucketLimits?.max_object_size,
maxObjects: bucketLimits?.max_objects,
maxObjectPins: bucketLimits?.max_object_pins,
};
const config: BucketConfig = {
hashAlgorithm: bucketConfig?.hash_algorithm,
acceptedCompressionAlgorithms:
bucketConfig?.accepted_compression_algorithms,
};

await Objectarium.create({
id: contractAddress,
owner: sender,
label,
name: bucket,
config,
limits,
}).save();

await referenceEntityInMessage(msg, {
messageField: "objectariumId",
id: contractAddress,
});
};

export const retrieveObjectariumObject = async (
Expand All @@ -112,7 +196,4 @@ export const retrieveObjectariumObject = async (

export const objectariumObjectId = (
events: Readonly<Event[]>,
): string | undefined =>
events
.find((event) => event.type === "wasm")
?.attributes.find((attribute) => attribute.key === "id")?.value;
): string | undefined => findEventAttribute(events, "wasm", "id")?.value;
Loading