From ed123ea8f737b7a37288d204b5afedf4375bb2c0 Mon Sep 17 00:00:00 2001 From: abishekk92 Date: Fri, 8 Jul 2022 20:07:36 +0530 Subject: [PATCH 1/5] init: set up slugger --- Cargo.lock | 8 ++++ programs/slugger/Cargo.toml | 25 +++++++++++ programs/slugger/Xargo.toml | 2 + programs/slugger/src/lib.rs | 88 +++++++++++++++++++++++++++++++++++++ tests/slugger.spec.ts | 47 ++++++++++++++++++++ 5 files changed, 170 insertions(+) create mode 100644 programs/slugger/Cargo.toml create mode 100644 programs/slugger/Xargo.toml create mode 100644 programs/slugger/src/lib.rs create mode 100644 tests/slugger.spec.ts diff --git a/Cargo.lock b/Cargo.lock index aab438d..bc0c486 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -988,6 +988,14 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "slugger" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "wordcel", +] + [[package]] name = "smallvec" version = "1.8.0" diff --git a/programs/slugger/Cargo.toml b/programs/slugger/Cargo.toml new file mode 100644 index 0000000..5e79e30 --- /dev/null +++ b/programs/slugger/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "slugger" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "slugger" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = ["wordcel"] +devnet = ["wordcel", "wordcel/devnet"] +mainnet = ["wordcel", "wordcel/mainnet"] + +[profile.release] +overflow-checks = true + +[dependencies] +anchor-lang = "0.24.2" +wordcel = { path = "../wordcel", features = ["cpi"], optional = true } diff --git a/programs/slugger/Xargo.toml b/programs/slugger/Xargo.toml new file mode 100644 index 0000000..475fb71 --- /dev/null +++ b/programs/slugger/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/programs/slugger/src/lib.rs b/programs/slugger/src/lib.rs new file mode 100644 index 0000000..13638b6 --- /dev/null +++ b/programs/slugger/src/lib.rs @@ -0,0 +1,88 @@ +use anchor_lang::prelude::*; +use std::mem::size_of; + +use wordcel::program::Wordcel as WordcelProgram; +use wordcel::state::{Post, Profile}; + +declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); + +#[program] +pub mod slugger { + use super::*; + + pub fn initialize(ctx: Context, slug_hash: [u8; 32]) -> Result<()> { + let slug = &mut ctx.accounts.slug; + slug.slug_hash = slug_hash; + slug.bump = *ctx.bumps.get("slug").unwrap(); + slug.authority = *ctx.accounts.authority.to_account_info().key; + slug.post_account = *ctx.accounts.post.to_account_info().key; + slug.profile_account = *ctx.accounts.profile.to_account_info().key; + Ok(()) + } +} + +#[derive(Accounts)] +#[instruction( + slug_hash: [u8;32], +)] + +// Question: Is it safe to read bump from the account, instead of recalculating it? +// What possible attack could it open up? +// Will it allow one to post on another profile? +// +// TODO: Verify with test cases +pub struct Initialize<'info> { + #[account( + init, + seeds = [ + b"slug".as_ref(), + authority.key().as_ref(), + post.key().as_ref(), + &slug_hash + ], + bump, + payer = authority, + space = Slug::LEN + )] + pub slug: Account<'info, Slug>, + #[account( + owner = wordcel_program.key(), + seeds = [ + b"post".as_ref(), + post.random_hash.as_ref() + ], + seeds::program = wordcel_program.key(), + bump = post.bump, + has_one = profile + )] + pub post: Account<'info, Post>, + #[account( + owner = wordcel_program.key(), + seeds = [ + b"profile".as_ref(), + post.random_hash.as_ref() + ], + seeds::program = wordcel_program.key(), + bump = profile.bump, + has_one = authority + )] + pub profile: Account<'info, Profile>, + #[account(mut)] + pub authority: Signer<'info>, + pub system_program: Program<'info, System>, + pub wordcel_program: Program<'info, WordcelProgram>, +} + +#[account] +#[derive(Default)] +pub struct Slug { + slug_hash: [u8; 32], + post_account: Pubkey, + profile_account: Pubkey, + bump: u8, + authority: Pubkey, +} + +impl Slug { + pub const LEN: usize = 8 + size_of::(); +} diff --git a/tests/slugger.spec.ts b/tests/slugger.spec.ts new file mode 100644 index 0000000..97f6d68 --- /dev/null +++ b/tests/slugger.spec.ts @@ -0,0 +1,47 @@ +import * as anchor from '@project-serum/anchor'; +import {Program, AnchorError} from '@project-serum/anchor'; +import {Slugger} from '../target/types/slugger'; +import {expect} from 'chai'; +import {PublicKey} from '@solana/web3.js'; +import {getInviteAccount} from "./utils/invite"; +import {airdrop} from './utils'; + +const {SystemProgram} = anchor.web3; +const provider = anchor.getProvider(); + +const program = anchor.workspace.Invite as Program; +const user = provider.wallet.publicKey; + + +describe('Slugger', async () => { + + // Prepare test user. + const testUser = anchor.web3.Keypair.generate(); + let oneInviteAccount: PublicKey; + + before(async () => { + await airdrop(testUser.publicKey); + oneInviteAccount = await getInviteAccount(testUser.publicKey); + }); + + + it("should initialize", async () => { + console.log("Testing Slug") + let slug = "foobar"; + // let slugHash = sha256(slug); + // let slugAccount = getSlugAccount(slugHash, postAccount, testUser); + // await program.methods.initialize(slugHash) + // .accounts({ + // profile: profileAccount, + // post: profileAccount, + // authority: testUser.publicKey, + // payer: user, + // systemProgram: SystemProgram.programId + // }) + // .rpc(); + // const data = await program.account.slug.fetch(slugAccount); + // expect(data.authority.toString()).to.equal(testUser.publicKey.toString()); + // expect(data.post.toString()).to.equal(postAccount.toString()); + }); + +}); From 1d70bd2abc2f2f3a4064e332b4055889be3b2d4a Mon Sep 17 00:00:00 2001 From: abishekk92 Date: Fri, 8 Jul 2022 20:07:52 +0530 Subject: [PATCH 2/5] add: scratch to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 870608d..e1625ac 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ target node_modules test_ledger/ .goki/ +scratch/ From 7de366ef49efc9716f8c18b7a4584cec8fdd3256 Mon Sep 17 00:00:00 2001 From: abishekk92 Date: Fri, 15 Jul 2022 18:41:01 +0530 Subject: [PATCH 3/5] add: Test cases for slugger --- Anchor.toml | 1 + programs/slugger/src/lib.rs | 15 +++--- tests/slugger.spec.ts | 93 ++++++++++++++++++++++++++++--------- yarn.lock | 5 ++ 4 files changed, 83 insertions(+), 31 deletions(-) diff --git a/Anchor.toml b/Anchor.toml index 3d85c18..0fe1b89 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -1,6 +1,7 @@ [programs.localnet] wordcel = "v4enuof3drNvU2Y3b5m7K62hMq3QUP6qQSV2jjxAhkp" invite = "6G5x4Es2YZYB5e4QkFJN88TrfLABkYEQpkUH5Gob9Cut" +slugger = "SAbD2TPKyTd54oahjz6UEBzweXvojsRWbGB2t21gDnB" [programs.devnet] wordcel = "D9JJgeRf2rKq5LNMHLBMb92g4ZpeMgCyvZkd7QKwSCzg" diff --git a/programs/slugger/src/lib.rs b/programs/slugger/src/lib.rs index 13638b6..955ad17 100644 --- a/programs/slugger/src/lib.rs +++ b/programs/slugger/src/lib.rs @@ -4,7 +4,7 @@ use std::mem::size_of; use wordcel::program::Wordcel as WordcelProgram; use wordcel::state::{Post, Profile}; -declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); +declare_id!("SAbD2TPKyTd54oahjz6UEBzweXvojsRWbGB2t21gDnB"); #[program] pub mod slugger { @@ -15,8 +15,8 @@ pub mod slugger { slug.slug_hash = slug_hash; slug.bump = *ctx.bumps.get("slug").unwrap(); slug.authority = *ctx.accounts.authority.to_account_info().key; - slug.post_account = *ctx.accounts.post.to_account_info().key; - slug.profile_account = *ctx.accounts.profile.to_account_info().key; + slug.post = *ctx.accounts.post.to_account_info().key; + slug.profile = *ctx.accounts.profile.to_account_info().key; Ok(()) } } @@ -36,8 +36,7 @@ pub struct Initialize<'info> { init, seeds = [ b"slug".as_ref(), - authority.key().as_ref(), - post.key().as_ref(), + profile.key().as_ref(), &slug_hash ], bump, @@ -60,7 +59,7 @@ pub struct Initialize<'info> { owner = wordcel_program.key(), seeds = [ b"profile".as_ref(), - post.random_hash.as_ref() + profile.random_hash.as_ref() ], seeds::program = wordcel_program.key(), bump = profile.bump, @@ -77,8 +76,8 @@ pub struct Initialize<'info> { #[derive(Default)] pub struct Slug { slug_hash: [u8; 32], - post_account: Pubkey, - profile_account: Pubkey, + post: Pubkey, + profile: Pubkey, bump: u8, authority: Pubkey, } diff --git a/tests/slugger.spec.ts b/tests/slugger.spec.ts index 97f6d68..58f01ea 100644 --- a/tests/slugger.spec.ts +++ b/tests/slugger.spec.ts @@ -1,47 +1,94 @@ import * as anchor from '@project-serum/anchor'; import {Program, AnchorError} from '@project-serum/anchor'; +import {Wordcel} from '../target/types/wordcel'; import {Slugger} from '../target/types/slugger'; import {expect} from 'chai'; import {PublicKey} from '@solana/web3.js'; -import {getInviteAccount} from "./utils/invite"; +import {getInviteAccount, invitationProgram} from "./utils/invite"; import {airdrop} from './utils'; +import randombytes from 'randombytes'; +import {createHash} from 'crypto'; const {SystemProgram} = anchor.web3; const provider = anchor.getProvider(); -const program = anchor.workspace.Invite as Program; +const wordcelProgram = anchor.workspace.Wordcel as Program; +const sluggerProgram = anchor.workspace.Slugger as Program; + + const user = provider.wallet.publicKey; +async function getSlugAccount(slugHash, profileAccount: PublicKey) { + const seeds = [Buffer.from("slug"), profileAccount.toBuffer(), slugHash]; + const [account, _] = await anchor.web3.PublicKey.findProgramAddress(seeds, sluggerProgram.programId); + return account +} + describe('Slugger', async () => { - // Prepare test user. - const testUser = anchor.web3.Keypair.generate(); - let oneInviteAccount: PublicKey; + let inviteAccount: PublicKey; + let profileAccount: PublicKey; + let postAccount: PublicKey; + // Prepare test user. before(async () => { - await airdrop(testUser.publicKey); - oneInviteAccount = await getInviteAccount(testUser.publicKey); + await airdrop(user); + inviteAccount = await getInviteAccount(user); + // Initialize invite + await invitationProgram.methods.initialize() + .accounts({ + inviteAccount: inviteAccount, + authority: user, + payer: user, + systemProgram: SystemProgram.programId + }).rpc(); + + // Set up a profile + const profileHash = randombytes(32); + const profileSeed = [Buffer.from("profile"), profileHash]; + const [_profileAccount, _profileBump] = await anchor.web3.PublicKey.findProgramAddress(profileSeed, wordcelProgram.programId); + profileAccount = _profileAccount; + await wordcelProgram.methods.initialize(profileHash) + .accounts({ + profile: profileAccount, + user: user, + invitation: inviteAccount, + invitationProgram: invitationProgram.programId, + systemProgram: SystemProgram.programId + }).rpc(); + + // Set up a post + const postHash = randombytes(32); + const postSeeds = [Buffer.from("post"), postHash]; + const [_postAccount, _postBump] = await anchor.web3.PublicKey.findProgramAddress(postSeeds, wordcelProgram.programId); + postAccount = _postAccount; + const metadataUri = "https://gist.githubusercontent.com/abishekk92/10593977/raw/589238c3d48e654347d6cbc1e29c1e10dadc7cea/monoid.md"; + await wordcelProgram.methods.createPost(metadataUri, postHash).accounts({ + post: postAccount, + profile: profileAccount, + authority: user, + systemProgram: SystemProgram.programId, + }).rpc(); }); it("should initialize", async () => { - console.log("Testing Slug") let slug = "foobar"; - // let slugHash = sha256(slug); - // let slugAccount = getSlugAccount(slugHash, postAccount, testUser); - // await program.methods.initialize(slugHash) - // .accounts({ - // profile: profileAccount, - // post: profileAccount, - // authority: testUser.publicKey, - // payer: user, - // systemProgram: SystemProgram.programId - // }) - // .rpc(); - // const data = await program.account.slug.fetch(slugAccount); - // expect(data.authority.toString()).to.equal(testUser.publicKey.toString()); - // expect(data.post.toString()).to.equal(postAccount.toString()); + let slugHash = createHash('sha256').update(slug, 'utf8').digest() + let slugAccount = await getSlugAccount(slugHash, profileAccount); + await sluggerProgram.methods.initialize(slugHash) + .accounts({ + slug: slugAccount, + profile: profileAccount, + post: postAccount, + authority: user, + wordcelProgram: wordcelProgram.programId, + systemProgram: SystemProgram.programId + }) + .rpc(); + const data = await sluggerProgram.account.slug.fetch(slugAccount); + expect(data.authority.toString()).to.equal(user.toString()); + expect(data.post.toString()).to.equal(postAccount.toString()); }); - }); diff --git a/yarn.lock b/yarn.lock index 2a52bb3..6837e1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1040,6 +1040,11 @@ crypto-hash@^1.3.0: resolved "https://registry.yarnpkg.com/crypto-hash/-/crypto-hash-1.3.0.tgz" integrity sha512-lyAZ0EMyjDkVvz8WOeVnuCPvKVBXcMv1l5SVqO1yC7PzTwrD/pPje/BIRbWhMoPe436U+Y2nD7f5bFx0kt+Sbg== +crypto-js@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf" + integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw== + debug@4.3.2: version "4.3.2" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz" From e718c16c9ac47c9770ad0ef5e18243b839670c77 Mon Sep 17 00:00:00 2001 From: abishekk92 Date: Fri, 15 Jul 2022 18:45:05 +0530 Subject: [PATCH 4/5] refactor: extract getSlugHash --- tests/slugger.spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/slugger.spec.ts b/tests/slugger.spec.ts index 58f01ea..b9ca01d 100644 --- a/tests/slugger.spec.ts +++ b/tests/slugger.spec.ts @@ -19,6 +19,9 @@ const sluggerProgram = anchor.workspace.Slugger as Program; const user = provider.wallet.publicKey; +function getSlugHash(slug) { + return createHash('sha256').update(slug, 'utf8').digest(); +} async function getSlugAccount(slugHash, profileAccount: PublicKey) { const seeds = [Buffer.from("slug"), profileAccount.toBuffer(), slugHash]; const [account, _] = await anchor.web3.PublicKey.findProgramAddress(seeds, sluggerProgram.programId); @@ -74,8 +77,7 @@ describe('Slugger', async () => { it("should initialize", async () => { - let slug = "foobar"; - let slugHash = createHash('sha256').update(slug, 'utf8').digest() + let slugHash = getSlugHash("gm-wagmi"); let slugAccount = await getSlugAccount(slugHash, profileAccount); await sluggerProgram.methods.initialize(slugHash) .accounts({ From 41b4ccbb76b5d400f50bf2ea58345ceb58a59438 Mon Sep 17 00:00:00 2001 From: abishekk92 Date: Mon, 18 Jul 2022 20:23:57 +0530 Subject: [PATCH 5/5] fix: use a invite singleton --- Anchor.toml | 2 ++ tests/slugger.spec.ts | 12 ++---------- tests/utils/invite.ts | 19 +++++++++++++++++++ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Anchor.toml b/Anchor.toml index 0fe1b89..d8020b4 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -6,10 +6,12 @@ slugger = "SAbD2TPKyTd54oahjz6UEBzweXvojsRWbGB2t21gDnB" [programs.devnet] wordcel = "D9JJgeRf2rKq5LNMHLBMb92g4ZpeMgCyvZkd7QKwSCzg" invite = "6G5x4Es2YZYB5e4QkFJN88TrfLABkYEQpkUH5Gob9Cut" +slugger = "SAbD2TPKyTd54oahjz6UEBzweXvojsRWbGB2t21gDnB" [programs.mainnet] wordcel = "EXzAYHZ8xS6QJ6xGRsdKZXixoQBLsuMbmwJozm85jHp" invite = "Fc4q6ttyDHr11HjMHRvanG9SskeR24Q62egdwsUUMHLf" +slugger = "SAbD2TPKyTd54oahjz6UEBzweXvojsRWbGB2t21gDnB" [registry] url = "https://anchor.projectserum.com" diff --git a/tests/slugger.spec.ts b/tests/slugger.spec.ts index b9ca01d..2aedf47 100644 --- a/tests/slugger.spec.ts +++ b/tests/slugger.spec.ts @@ -4,7 +4,7 @@ import {Wordcel} from '../target/types/wordcel'; import {Slugger} from '../target/types/slugger'; import {expect} from 'chai'; import {PublicKey} from '@solana/web3.js'; -import {getInviteAccount, invitationProgram} from "./utils/invite"; +import {getInviteSingleton, invitationProgram} from "./utils/invite"; import {airdrop} from './utils'; import randombytes from 'randombytes'; import {createHash} from 'crypto'; @@ -37,15 +37,7 @@ describe('Slugger', async () => { // Prepare test user. before(async () => { await airdrop(user); - inviteAccount = await getInviteAccount(user); - // Initialize invite - await invitationProgram.methods.initialize() - .accounts({ - inviteAccount: inviteAccount, - authority: user, - payer: user, - systemProgram: SystemProgram.programId - }).rpc(); + inviteAccount = await getInviteSingleton(user); // Set up a profile const profileHash = randombytes(32); diff --git a/tests/utils/invite.ts b/tests/utils/invite.ts index 1a12f83..b7e8468 100644 --- a/tests/utils/invite.ts +++ b/tests/utils/invite.ts @@ -7,6 +7,8 @@ const {SystemProgram} = anchor.web3; const provider = anchor.getProvider(); const invitationPrefix = Buffer.from("invite"); +let inviteMap = new Map(); + export const invitationProgram = anchor.workspace.Invite as Program; export async function getInviteAccount(key: PublicKey) { @@ -33,3 +35,20 @@ export async function sendInvite(from_user: Keypair, to: PublicKey, feePayer: Pu await provider.sendAndConfirm(tx); return [inviteAccount, toInviteAccount]; } + +export async function getInviteSingleton(user: PublicKey) { + const key = user.toString() + if(inviteMap.has(key)) { + return inviteMap.get(key) + } + const inviteAccount = await getInviteAccount(user); + await invitationProgram.methods.initialize() + .accounts({ + inviteAccount: inviteAccount, + authority: user, + payer: user, + systemProgram: SystemProgram.programId + }).rpc(); + inviteMap.set(key, inviteAccount); + return inviteAccount; +}