Skip to content
Open
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
1 change: 1 addition & 0 deletions CLAUDE.md
3 changes: 3 additions & 0 deletions e2e/test1.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ test.describe("Upgrades & token transfer flow", () => {
.getByPlaceholder("Repeat your seed phrase...")
.fill(mkPwd("eve"));
await page.getByRole("button", { name: "CONTINUE" }).click();
await waitForUILoading(page);
const stalwartPrincipal =
"v5znh-suak4-idmlq-uaq6k-iiygt-7d7de-jq7pf-dpzmt-zhmle-akfo2-mqe";
await expect(page.getByText(stalwartPrincipal)).toBeVisible();
Expand Down Expand Up @@ -104,6 +105,7 @@ test.describe("Upgrades & token transfer flow", () => {
.getByPlaceholder("Repeat your seed phrase...")
.fill(mkPwd("pete"));
await page.getByRole("button", { name: "CONTINUE" }).click();
await waitForUILoading(page);
await page.getByPlaceholder("alphanumeric").fill("pete");
await page.getByRole("button", { name: "SAVE" }).click();
await waitForUILoading(page);
Expand Down Expand Up @@ -178,6 +180,7 @@ test.describe("Upgrades & token transfer flow", () => {
.getByPlaceholder("Enter your seed phrase...")
.fill(mkPwd("eve"));
await page.getByRole("button", { name: "CONTINUE" }).click();
await waitForUILoading(page);

await expect(
page.getByRole("heading", { name: "RECOVERY" }),
Expand Down
2 changes: 2 additions & 0 deletions e2e/test2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ test.describe("Regular users flow", () => {
.getByPlaceholder("Repeat your seed phrase...")
.fill(mkPwd("alice"));
await page.getByRole("button", { name: "CONTINUE" }).click();
await waitForUILoading(page);
const alicePrincipal =
"xkqsg-2iln4-5zio6-xn4ja-s34n3-g63uk-kc6ex-wklca-7kfzz-67won-yqe";
await expect(page.getByText(alicePrincipal)).toBeVisible();
Expand Down Expand Up @@ -259,6 +260,7 @@ test.describe("Regular users flow", () => {
.getByPlaceholder("Repeat your seed phrase...")
.fill(mkPwd("bob"));
await page.getByRole("button", { name: "CONTINUE" }).click();
await waitForUILoading(page);
await page.getByPlaceholder("alphanumeric").fill("bob");
await page
.getByPlaceholder("tell us what we should know about you")
Expand Down
2 changes: 2 additions & 0 deletions e2e/test3.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ test.describe("Regular users flow, part two", () => {
.getByPlaceholder("Repeat your seed phrase...")
.fill(mkPwd("john"));
await page.getByRole("button", { name: "CONTINUE" }).click();
await waitForUILoading(page);
await page
.getByRole("button", { name: "MINT CREDITS WITH ICP" })
.click();
Expand Down Expand Up @@ -134,6 +135,7 @@ test.describe("Regular users flow, part two", () => {
.getByPlaceholder("Repeat your seed phrase...")
.fill(mkPwd("eye"));
await page.getByRole("button", { name: "CONTINUE" }).click();
await waitForUILoading(page);
await page
.getByRole("button", { name: "MINT CREDITS WITH ICP" })
.click();
Expand Down
4 changes: 4 additions & 0 deletions e2e/test4.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ test.describe("Report and transfer to user", () => {
.getByPlaceholder("Repeat your seed phrase...")
.fill(mkPwd("joe"));
await page.getByRole("button", { name: "CONTINUE" }).click();
await waitForUILoading(page);
transferICP(
"e93e7f1cfa411dafa8debb4769c6cc1b7972434f1669083fd08d86d11c0c0722",
1,
Expand Down Expand Up @@ -77,6 +78,7 @@ test.describe("Report and transfer to user", () => {
.getByPlaceholder("Repeat your seed phrase...")
.fill(mkPwd("jane"));
await page.getByRole("button", { name: "CONTINUE" }).click();
await waitForUILoading(page);
await page.getByPlaceholder("alphanumeric").fill("jane");
await page.getByRole("button", { name: "SAVE" }).click();
await waitForUILoading(page);
Expand All @@ -101,6 +103,7 @@ test.describe("Report and transfer to user", () => {
.getByPlaceholder("Repeat your seed phrase...")
.fill(mkPwd("kyle"));
await page.getByRole("button", { name: "CONTINUE" }).click();
await waitForUILoading(page);
await page.getByPlaceholder("alphanumeric").fill("kyle");
await page.getByRole("button", { name: "SAVE" }).click();
await waitForUILoading(page);
Expand Down Expand Up @@ -219,6 +222,7 @@ test.describe("Report and transfer to user", () => {
.getByPlaceholder("Enter your seed phrase...")
.fill(mkPwd("jane"));
await page.getByRole("button", { name: "CONTINUE" }).click();
await waitForUILoading(page);
await page.goto("/#/user/kyle");
await page.reload();
await waitForUILoading(page);
Expand Down
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
"raw-loader": "4.0.2",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-markdown": "10.1.0",
"terser-webpack-plugin": "^5.3.10",
"ts-loader": "9.5.0",
"typescript": "5.8.3",
Expand All @@ -49,7 +48,6 @@
"@dfinity/identity": "2.4.1",
"@dfinity/ledger-icrc": "2.8.0",
"@dfinity/principal": "2.4.1",
"diff-match-patch": "1.0.5",
"remark-gfm": "4.0.1"
"diff-match-patch": "1.0.5"
}
}
17 changes: 11 additions & 6 deletions src/backend/env/auction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,18 +168,23 @@ pub async fn cancel_bid(principal: Principal) -> Result<u64, String> {
.checked_sub(DEFAULT_FEE.e8s())
.expect("nothing to refund");

invoices::transfer(
if let Err(err) = invoices::transfer(
user_account,
Tokens::from_e8s(funds),
Memo(727),
Some(Subaccount(AUCTION_ICP_SUBACCOUNT)),
)
.await
.map_err(|err| {
{
let msg = format!("couldn't withdraw funds from bid {:?}: {}", bid, err);
mutate(|state| state.logger.error(&msg));
msg
})
mutate(|state| {
state.logger.error(&msg);
state.auction.bids.insert(bid);
});
return Err(msg);
}

Ok(funds)
}

fn remove_bid(state: &mut State, principal: Principal) -> Result<Bid, String> {
Expand Down Expand Up @@ -249,7 +254,7 @@ fn add_bid(

assert!(
!state.auction.bids.iter().any(|bid| bid.user == user_id),
"no bids exist for the user"
"bid already exists for the user"
);

state.auction.bids.insert(Bid {
Expand Down
2 changes: 1 addition & 1 deletion src/backend/env/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ pub fn create_feature(caller: Principal, post_id: PostId, now: Time) -> Result<(

let _ = state.system_message(
format!(
"A [new feature](#/post/{}) was created by `@{}`",
"A [new feature](#/post/{}) was created by @{}",
post_id, user_name
),
CONFIG.dao_realm.into(),
Expand Down
33 changes: 19 additions & 14 deletions src/backend/env/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,9 +240,11 @@ pub struct State {

pub timers: Timers,

#[serde(default)]
// Maps temporal session principals (delegates) created on custom domains to canonical user principals.
delegations: HashMap<Principal, (Principal, String, Time)>,

#[serde(default)]
pub cold_wallets: HashMap<Principal, UserId>,
}

#[derive(Default, Deserialize, Serialize)]
Expand Down Expand Up @@ -380,9 +382,10 @@ impl State {

self.weekly_chores_delay_votes.insert(user.id);

if self.weekly_chores_delay_votes.len() * 100
/ self.users.values().filter(|user| user.stalwart).count()
>= CONFIG.report_confirmation_percentage as usize
let stalwart_count = self.users.values().filter(|user| user.stalwart).count();
if stalwart_count > 0
&& self.weekly_chores_delay_votes.len() * 100 / stalwart_count
>= CONFIG.report_confirmation_percentage as usize
{
self.timers.last_weekly += WEEK;
self.logger.info(format!(
Expand Down Expand Up @@ -412,7 +415,7 @@ impl State {
}

pub fn link_cold_wallet(&mut self, caller: Principal, user_id: UserId) -> Result<(), String> {
if self.principal_to_user(caller).is_some() {
if self.principal_to_user(caller).is_some() || self.cold_wallets.contains_key(&caller) {
return Err("this wallet is linked already".into());
}
let user = self.users.get_mut(&user_id).ok_or("user not found")?;
Expand All @@ -425,7 +428,7 @@ impl State {
.get(&account(caller))
.copied()
.unwrap_or_default();
self.principals.insert(caller, user.id);
self.cold_wallets.insert(caller, user.id);
Ok(())
}

Expand All @@ -437,7 +440,7 @@ impl State {
let principal = user.cold_wallet.take();
user.cold_balance = 0;
if let Some(principal) = principal {
self.principals.remove(&principal);
self.cold_wallets.remove(&principal);
}
}
Ok(())
Expand Down Expand Up @@ -1655,7 +1658,7 @@ impl State {

let _ = state.system_message(
format!(
"`@{}` is the lucky receiver of `{}` ${} as a weekly random reward! 🎲",
"@{} is the lucky receiver of `{}` ${} as a weekly random reward! 🎲",
winner_name,
CONFIG.random_reward_amount / base(),
CONFIG.token_symbol,
Expand Down Expand Up @@ -3062,8 +3065,9 @@ pub(crate) mod tests {
assert_eq!(user.total_balance(), 80000);
assert_eq!(state.principals.len(), 3);
state.link_cold_wallet(pr(200), 1).unwrap();
assert_eq!(state.principals.len(), 4);
assert_eq!(state.principal_to_user(pr(200)).unwrap().id, 1);
assert_eq!(state.principals.len(), 3);
assert_eq!(state.cold_wallets.get(&pr(200)), Some(&1));
assert!(state.principal_to_user(pr(200)).is_none());
let user = state.users.get(&1).unwrap();
assert_eq!(user.total_balance(), 80000 + cold_balance);
assert_eq!(
Expand All @@ -3073,18 +3077,19 @@ pub(crate) mod tests {
let voters = state.active_voters(0).collect::<BTreeMap<_, _>>();
assert_eq!(*voters.get(&1).unwrap(), (2 << 2) * 10000 + cold_balance);

state.emergency_votes.insert(pr(200), 1000);
state.emergency_votes.insert(pr(1), 1000);
assert_eq!(
state.unlink_cold_wallet(pr(200)),
state.unlink_cold_wallet(pr(1)),
Err("a vote on a pending proposal detected".into())
);

state.emergency_votes.clear();
assert!(state.unlink_cold_wallet(pr(200)).is_ok(),);
assert!(state.unlink_cold_wallet(pr(1)).is_ok(),);
let user = state.principal_to_user(pr(1)).unwrap();
assert_eq!(user.id, 1);
assert!(user.cold_wallet.is_none());
assert_eq!(state.principals.len(), 3);
assert!(state.cold_wallets.is_empty());

let voters = state.active_voters(0).collect::<BTreeMap<_, _>>();
assert_eq!(*voters.get(&1).unwrap(), (2 << 2) * 10000);
Expand Down Expand Up @@ -3755,7 +3760,7 @@ pub(crate) mod tests {
assert_eq!(state.user("2").unwrap().id, u3);
assert!(state.user("user22").is_none());
assert_eq!(state.user(&pr(2).to_text()).unwrap().id, u3);
assert_eq!(state.user(&cold_wallet.to_text()).unwrap().id, u2);
assert!(state.user(&cold_wallet.to_text()).is_none());
});
}

Expand Down
2 changes: 1 addition & 1 deletion src/backend/env/post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ impl Post {
}
poll.voters.insert(user_id);
poll.votes.entry(vote).or_default().insert(if anonymously {
MAX_USER_ID - 1
MAX_USER_ID - poll.voters.len() as u64
} else {
user_id
});
Expand Down
2 changes: 1 addition & 1 deletion src/backend/env/proposals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ pub fn create_proposal(
.expect("couldn't mutate post");
let _ = state.system_message(
format!(
"A new [proposal](#/post/{}) was submitted by `@{}`",
"A new [proposal](#/post/{}) was submitted by @{}",
post_id, &proposer_name
),
CONFIG.dao_realm.into(),
Expand Down
18 changes: 10 additions & 8 deletions src/backend/env/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -557,15 +557,17 @@ pub fn append_to_ledger(state: &mut State, mut tx: Transaction) -> u128 {
}

fn update_user_balance(state: &mut State, principal: Principal, balance: Token) {
let Some(user) = state.principal_to_user_mut(principal) else {
if let Some(user) = state.principal_to_user_mut(principal) {
user.balance = balance;
return;
};
if user.principal == principal {
user.balance = balance
} else if user.cold_wallet == Some(principal) {
user.cold_balance = balance
} else {
unreachable!("unidentifiable principal")
}
if let Some(user) = state
.cold_wallets
.get(&principal)
.copied()
.and_then(|id| state.users.get_mut(&id))
{
user.cold_balance = balance;
}
}

Expand Down
19 changes: 16 additions & 3 deletions src/backend/updates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,17 @@ fn post_upgrade() {
}

#[allow(clippy::all)]
fn sync_post_upgrade_fixtures() {}
fn sync_post_upgrade_fixtures() {
mutate(|state| {
// Migrate cold wallet principals from `principals` to `cold_wallets`
for user in state.users.values() {
if let Some(cold_wallet) = user.cold_wallet {
state.cold_wallets.insert(cold_wallet, user.id);
state.principals.remove(&cold_wallet);
}
}
});
}

#[allow(clippy::all)]
async fn async_post_upgrade_fixtures() {}
Expand Down Expand Up @@ -654,8 +664,11 @@ async fn set_emergency_release(binary: ByteBuf) {
if binary.is_empty()
|| state
.principal_to_user(raw_caller(state).unwrap())
.map(|user| user.account_age(WEEK) < CONFIG.min_stalwart_account_age_weeks)
.unwrap_or_default()
.map(|user| {
user.account_age(WEEK) < CONFIG.min_stalwart_account_age_weeks
|| user.total_balance() < 2000 * token::base()
})
.unwrap_or(true)
{
return;
}
Expand Down
3 changes: 2 additions & 1 deletion src/frontend/src/common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,8 @@ export const Loading = ({
const [dot, setDot] = React.useState(0);
const md = <span> ■ </span>;
React.useEffect(() => {
setTimeout(() => setDot(dot + 1), 200);
const id = setTimeout(() => setDot(dot + 1), 200);
return () => clearTimeout(id);
}, [dot]);
return (
<div
Expand Down
9 changes: 3 additions & 6 deletions src/frontend/src/content.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as React from "react";
import ReactMarkdown from "react-markdown";
import Markdown from "./markdown";
import { ArrowDown, domain, RealmSpan, timeAgo } from "./common";
import remarkGfm from "remark-gfm";
import { BlogTitle } from "./types";
import { previewImg } from "./image_preview";

Expand Down Expand Up @@ -91,10 +90,9 @@ export const Content = ({

if (!post)
return (
<ReactMarkdown
<Markdown
components={simpleComponents as unknown as any}
children={linkedValue}
remarkPlugins={[remarkGfm]}
/>
);

Expand Down Expand Up @@ -220,9 +218,8 @@ const markdownizer = (
) =>
!value ? null : (
<div className={`selectable ${className}`}>
<ReactMarkdown
<Markdown
children={value}
remarkPlugins={[remarkGfm]}
components={{
h1: ({ node, children, ...props }) => {
if (!blogTitle) return <h1 {...props}>{children}</h1>;
Expand Down
Loading
Loading