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/index account #111

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
124 changes: 103 additions & 21 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,64 @@ type Coin @jsonField {
amount: String!
}

"""
A Cosmos SDK public key.
"""
type PublicKey @jsonField {
"""
The type url of public key.
"""
typeUrl: String!
"""
The public key.
"""
key: String!
}

"""
A Cosmos SDK account. Can be any account found in the Cosmos module auth or OKP4 vesting module.
"""
type Account @entity {
"""
The account address.
"""
id: ID!
"""
The account public key.
"""
pubKey: PublicKey
"""
Account Balances in different denominators.
"""
balances: [Coin]!
"""
Objectarium objects stored by the account.
"""
storedObjectariumObjects: [ObjectariumObject] @derivedFrom(field: "sender")
"""
Objectarium objects pinned by the account.
"""
pinnedObjectariumObjects: [ObjectariumObjectPin]
@derivedFrom(field: "account")
"""
Objectariums created by the account.
"""
createdObjectariums: [Objectarium] @derivedFrom(field: "creator")
"""
Objectariums owned by the account.
"""
ownedObjectariums: [Objectarium] @derivedFrom(field: "owner")
"""
Contracts created by the account.
"""
createdContracts: [Contract] @derivedFrom(field: "creator")
"""
Objectarium objects pinned by the account.
"""
contractPermissions: [ContractPermissionAccount]
@derivedFrom(field: "account")
}

"""
A Cosmos SDK transaction in the blockchain.
"""
Expand Down Expand Up @@ -146,6 +204,24 @@ type Message @entity {
contract: Contract
}

"""
The pin of an objectarium object.
"""
type ObjectariumObjectPin @entity {
"""
Unique identifier made up by the account address and the objectarium object hash identifier.
"""
id: ID!
"""
The objectarium object being pinned.
"""
objectariumObject: ObjectariumObject!
"""
The account pinning the object.
"""
account: Account!
}

"""
Type of an object stored in the Objectarium smart contract instance.
"""
Expand All @@ -156,17 +232,17 @@ type ObjectariumObject @entity {
"""
id: ID!
"""
Address of the user who stored the object.
Account that stored the object.
"""
sender: String! @index
sender: Account!
"""
The smart contract instance (aka bucket) where the object is stored.
"""
objectarium: Objectarium!
"""
Addresses that have pinned the object.
A list of objectarium object pins containing the accounts that have pinned the object.
"""
pins: [String!]!
pins: [ObjectariumObjectPin!]! @derivedFrom(field: "objectariumObject")
"""
List of messages that concern the object.
"""
Expand Down Expand Up @@ -220,9 +296,13 @@ type Objectarium @entity {
"""
id: ID!
"""
The owner of the bucket.
The account that created the bucket.
"""
owner: String!
creator: Account!
"""
The account that owns the bucket.
"""
owner: Account!
"""
Label of the bucket.
"""
Expand Down Expand Up @@ -254,24 +334,21 @@ type Objectarium @entity {
}

"""
An object describing the permissions of a smart contract.
An account of a contract permission.
"""
type AccessConfig @jsonField {
# TODO: Change permission type to Enum for better value representaion.
# Explanation: As of date (12/09/2023) SubQuery does not support Enum fields in jsonFields
# See function "processFields" in https://github.com/subquery/subql/blob/main/packages/cli/src/controller/codegen-controller.ts.
type ContractPermissionAccount @entity {
"""
AccessType permission type.
Unique identifier made up by the account address and the contract code id.
"""
permission: String!
id: ID!
"""
Address concerned by permission.
The contract with the permission.
"""
address: String!
contract: Contract!
"""
List of addresses included in the permission.
The account concerned by the contract permission.
"""
addresses: [String]!
account: Account!
}

"""
Expand All @@ -288,13 +365,18 @@ type Contract @entity {
"""
dataHash: String!
"""
The creator of the smart contract.
The account that created the smart contract.
"""
creator: Account!
"""
Contract access permission.
"""
creator: String!
permission: String
"""
Contract creation permissions.
List of account permission associations included in the access permission.
"""
instantiatePermission: AccessConfig
permissionAccounts: [ContractPermissionAccount!]
@derivedFrom(field: "contract")
"""
List of messages that concern the smart contract.
"""
Expand Down
69 changes: 46 additions & 23 deletions src/mappings/contractHandlers.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,68 @@
import type { CosmosMessage } from "@subql/types-cosmos";
import type { MsgStoreCode } from "cosmjs-types/cosmwasm/wasm/v1/tx";
import { Contract } from "../types";
import { findEventAttribute } from "./helper";

enum AccessType {
ACCESS_TYPE_UNSPECIFIED = 0,
ACCESS_TYPE_NOBODY = 1,
ACCESS_TYPE_ONLY_ADDRESS = 2,
ACCESS_TYPE_EVERYBODY = 3,
ACCESS_TYPE_ANY_OF_ADDRESSES = 4,
UNRECOGNIZED = -1,
}
import { AccessType } from "cosmjs-types/cosmwasm/wasm/v1/types";
import { Account, Contract, ContractPermissionAccount } from "../types";
import { contractPermissionAccountId, findEventAttribute } from "./helper";

export const handleStoreContract = async (
msg: CosmosMessage<MsgStoreCode>,
): Promise<void> => {
const id = findEventAttribute(msg.tx.tx.events, "store_code", "code_id")
?.value;
const contractId = findEventAttribute(
msg.tx.tx.events,
"store_code",
"code_id",
)?.value;

const dataHash = findEventAttribute(
msg.tx.tx.events,
"store_code",
"code_checksum",
)?.value;

if (!id || !dataHash) {
if (!contractId || !dataHash) {
return;
}

const { instantiatePermission: instantiatePermissionWithEnums, sender } =
msg.msg.decodedMsg;
const { instantiatePermission, sender } = msg.msg.decodedMsg;
const senderAccount = await Account.get(sender);

const instantiatePermission = instantiatePermissionWithEnums && {
...instantiatePermissionWithEnums,
permission: AccessType[instantiatePermissionWithEnums.permission],
};
if (!senderAccount) {
const publicKey = msg.tx.decodedTx.authInfo.signerInfos.find(
(signerInfo) => signerInfo.publicKey,
)?.publicKey;

await Account.create({
id: sender,
pubKey: publicKey && {
typeUrl: publicKey.typeUrl,
key: Buffer.from(publicKey.value).toString("base64"),
Copy link
Member

@ccamel ccamel Oct 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not correct. What you have is a protobuf Any. This Any type has a typeUrl that tells you what kind of data it contains, and a value which is the data itself in a specific format. You can't just convert the data into base64 directly, because you'll only get the binary form of that data.

Instead, you should use the typeUrl to understand what type is, knowing that, decode the binary data into its proper form. After that, you can get a string representation of it.

For instance, for the secp256k1 public key type, you can get the compressed base64 representation like this:

const pk = PubKey.deserializeBinary(new Uint8Array(publicKey.value));
const key = pk.getKey_asB64();

which gives:

"pubKey": {
            "typeUrl": "/cosmos.crypto.secp256k1.PubKey"
            "key": "A8Ny+hVbRIB8h5iiF7d4rSS+9ts06W2t1p5riyOIl1Pm",
          },

Verification:

➜ okp4d debug pubkey-raw "A8Ny+hVbRIB8h5iiF7d4rSS+9ts06W2t1p5riyOIl1Pm"
Parsed key as secp256k1
Address: 1BC29537C771DCA2C365EE81B39B44042F916DF6
...

➜ okp4d debug addr 1BC29537C771DCA2C365EE81B39B44042F916DF6
Address: [27 194 149 55 199 113 220 162 195 101 238 129 179 155 68 4 47 145 109 246]
Address (hex): 1BC29537C771DCA2C365EE81B39B44042F916DF6
Bech32 Acc: okp41r0pf2d78w8w29sm9a6qm8x6yqshezm0k6vwcrg
Bech32 Val: okp4valoper1r0pf2d78w8w29sm9a6qm8x6yqshezm0k0t73af

},
balances: [],
}).save();
}

const permission =
instantiatePermission && AccessType[instantiatePermission.permission];

await Contract.create({
id,
id: contractId,
dataHash,
creator: sender,
instantiatePermission,
creatorId: sender,
permission,
}).save();

for (const permissionAddress in instantiatePermission?.addresses) {
if (!(await Account.get(permissionAddress))) {
await Account.create({
id: permissionAddress,
balances: [],
}).save();
}

await ContractPermissionAccount.create({
id: contractPermissionAccountId(contractId, sender),
contractId: permissionAddress,
accountId: sender,
}).save();
}
Comment on lines +54 to +67
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code iterates over instantiatePermission?.addresses and for each permissionAddress, it checks if an account exists, and if not, it creates a new account. Then, it creates a ContractPermissionAccount entry. This is a good practice to ensure that all permission addresses have corresponding accounts. However, the contractId and accountId seem to be swapped in the ContractPermissionAccount.create call. The contractId should be contractId and the accountId should be permissionAddress.

-            await ContractPermissionAccount.create({
-                id: contractPermissionAccountId(contractId, sender),
-                contractId: permissionAddress,
-                accountId: sender,
-            }).save();
+            await ContractPermissionAccount.create({
+                id: contractPermissionAccountId(contractId, permissionAddress),
+                contractId: contractId,
+                accountId: permissionAddress,
+            }).save();
Committable suggestion (Beta)
Suggested change
for (const permissionAddress in instantiatePermission?.addresses) {
if (!(await Account.get(permissionAddress))) {
await Account.create({
id: permissionAddress,
balances: [],
}).save();
}
await ContractPermissionAccount.create({
id: contractPermissionAccountId(contractId, sender),
contractId: permissionAddress,
accountId: sender,
}).save();
}
await ContractPermissionAccount.create({
id: contractPermissionAccountId(contractId, permissionAddress),
contractId: contractId,
accountId: permissionAddress,
}).save();

};
10 changes: 10 additions & 0 deletions src/mappings/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ import type {
export const messageId = (msg: CosmosMessage | CosmosEvent): string =>
`${msg.tx.hash}-${msg.idx}`;

export const objectariumObjectPinId = (
objectId: string,
sender: string,
): string => `${objectId}-${sender}`;

export const contractPermissionAccountId = (
contractId: string,
account: string,
): string => `${contractId}-${account}`;

export const findEvent = (
events: Readonly<Event[]>,
event: string,
Expand Down
Loading