Skip to content

Commit

Permalink
Synchronize example docs and v22 examples.
Browse files Browse the repository at this point in the history
This omits token and liquidity pool examples.
  • Loading branch information
dmkozh committed Dec 20, 2024
1 parent a0d3a2d commit 72cfa62
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 310 deletions.
49 changes: 26 additions & 23 deletions docs/build/apps/guestbook/frontend.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -342,26 +342,26 @@ The `[id]` part of the filename tells this route that we expect to have a path-b
:::

```js title="src/routes/read/[id]/+page.server.ts"
import { error } from '@sveltejs/kit';
import guestbook from '$lib/contracts/ye_olde_guestbook';
import type { PageServerLoad } from './$types';
import { error } from "@sveltejs/kit";
import guestbook from "$lib/contracts/ye_olde_guestbook";
import type { PageServerLoad } from "./$types";

export const load: PageServerLoad = async ({ params }) => {
try {
let { result } = await guestbook.read_message({
message_id: parseInt(params.id),
});
try {
let { result } = await guestbook.read_message({
message_id: parseInt(params.id),
});

return {
id: params.id,
message: result.unwrap(),
};
} catch (err) {
error(500, {
message:
"Sorry, something went wrong. Most likely, the message you're looking for doesn't exist.",
});
}
return {
id: params.id,
message: result.unwrap(),
};
} catch (err) {
error(500, {
message:
"Sorry, something went wrong. Most likely, the message you're looking for doesn't exist.",
});
}
};
```

Expand Down Expand Up @@ -394,14 +394,17 @@ Great! If you know the ID of the entry you want to read. Most of the time, you p
For this, we'll (again) keep as much of the query logic server-side as possible. These ledger entry results can be cached. And, the client doesn't need to make even more round trips just to query these entries. The route that performs this query is another `+page.server.ts` file:

```js title="src/routes/read/+page.server.ts"
import { getAllMessages, getWelcomeMessage } from '$lib/server/getLedgerEntries';
import type { PageServerLoad } from './$types';
import {
getAllMessages,
getWelcomeMessage,
} from "$lib/server/getLedgerEntries";
import type { PageServerLoad } from "./$types";

export const load: PageServerLoad = async () => {
return {
welcomeMessage: await getWelcomeMessage(),
messages: await getAllMessages(),
};
return {
welcomeMessage: await getWelcomeMessage(),
messages: await getAllMessages(),
};
};
```

Expand Down
47 changes: 23 additions & 24 deletions docs/build/apps/guestbook/setup-passkeys.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,14 @@ The implementation of this is outside the scope of this tutorial, but be sure to
:::

```js title="src/routes/api/send/+server.ts"

import { server } from '$lib/server/passkeyServer';
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { server } from "$lib/server/passkeyServer";
import { json } from "@sveltejs/kit";
import type { RequestHandler } from "./$types";

export const POST: RequestHandler = async ({ request }) => {
const { xdr } = await request.json();
const res = await server.send(xdr);
return json(res);
const { xdr } = await request.json();
const res = await server.send(xdr);
return json(res);
};
```

Expand Down Expand Up @@ -210,15 +209,15 @@ This allows us to write the `fetch` code once, and use it consistently everywher
* @returns JSON object containing the RPC's response
*/
export async function send(xdr: string) {
return fetch('/api/send', {
method: 'POST',
body: JSON.stringify({
xdr,
}),
}).then(async (res) => {
if (res.ok) return res.json();
else throw await res.text();
});
return fetch("/api/send", {
method: "POST",
body: JSON.stringify({
xdr,
}),
}).then(async (res) => {
if (res.ok) return res.json();
else throw await res.text();
});
}

/**
Expand All @@ -229,10 +228,10 @@ export async function send(xdr: string) {
* @returns The contract address to which the specified signer has been added
*/
export async function getContractId(signer: string) {
return fetch(`/api/contract/${signer}`).then(async (res) => {
if (res.ok) return res.text();
else throw await res.text();
});
return fetch(`/api/contract/${signer}`).then(async (res) => {
if (res.ok) return res.text();
else throw await res.text();
});
}

/**
Expand All @@ -242,10 +241,10 @@ export async function getContractId(signer: string) {
* @param address - The contract address to fund on the Testnet
*/
export async function fundContract(address: string) {
return fetch(`/api/fund/${address}`).then(async (res) => {
if (res.ok) return res.json();
else throw await res.text();
});
return fetch(`/api/fund/${address}`).then(async (res) => {
if (res.ok) return res.json();
else throw await res.text();
});
}
```

Expand Down
4 changes: 2 additions & 2 deletions docs/build/smart-contracts/example-contracts/auth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ fn test() {
let env = Env::default();
env.mock_all_auths();

let contract_id = env.register_contract(None, IncrementContract);
let contract_id = env.register(IncrementContract, {});
let client = IncrementContractClient::new(&env, &contract_id);

let user_1 = Address::random(&env);
Expand Down Expand Up @@ -222,7 +222,7 @@ env.mock_all_auths();
The contract is registered with the environment using the contract type.

```rust
let contract_id = env.register_contract(None, IncrementContract);
let contract_id = env.register(IncrementContract, {});
```

All public functions within an `impl` block that is annotated with the `#[contractimpl]` attribute have a corresponding function generated in a generated client type. The client type will be named the same as the contract type with `Client` appended. For example, in our contract the contract type is `IncrementContract`, and the client is named `IncrementContractClient`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,11 @@ Open the `cross_contract/contract_b/src/test.rs` file to follow along.
fn test() {
let env = Env::default();

// Register contract A using the imported Wasm.
let contract_a_id = env.register_contract_wasm(None, contract_a::Wasm);
// Register contract A using the imported WASM.
let contract_a_id = env.register(contract_a::WASM, ());

// Register contract B defined in this crate.
let contract_b_id = env.register_contract(None, ContractB);
let contract_b_id = env.register(ContractB, ());

// Create a client for calling contract B.
let client = ContractBClient::new(&env, &contract_b_id);
Expand All @@ -196,13 +196,13 @@ let env = Env::default();
Contract A is registered with the environment using the imported Wasm.

```rust
let contract_a_id = env.register_contract_wasm(None, contract_a::Wasm);
let contract_a_id = env.register(contract_a::WASM, ());
```

Contract B is registered with the environment using the contract type.
Contract B is registered with the environment using the contract type and the contract instance is compiled into the Rust binary.

```rust
let contract_b_id = env.register_contract(None, ContractB);
let contract_b_id = env.register(ContractB, ());
```

All public functions within an `impl` block that is annotated with the `#[contractimpl]` attribute have a corresponding function generated in a generated client type. The client type will be named the same as the contract type with `Client` appended. For example, in our contract the contract type is `ContractB`, and the client is named `ContractBClient`. The client can be constructed and used in the same way that client generated for Contract A can be.
Expand Down
98 changes: 53 additions & 45 deletions docs/build/smart-contracts/example-contracts/custom-account.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ enum DataKey {
}
...
// Initialize the contract with a list of ed25519 public key ('signers').
pub fn init(env: Env, signers: Vec<BytesN<32>>) {
pub fn __constructor(env: Env, signers: Vec<BytesN<32>>) {
// In reality this would need some additional validation on signers
// (deduplication etc.).
for signer in signers.iter() {
Expand All @@ -102,6 +102,8 @@ pub fn init(env: Env, signers: Vec<BytesN<32>>) {

This account contract needs to work with the public keys explicitly. Here we initialize the contract with ed25519 keys.

We use constructor in order to ensure that the contract instance is created and initialized atomically (without constructor there is a risk that someone frontruns the initialization of the contract and sets their own public keys).

### Policy modification

```rust
Expand Down Expand Up @@ -173,7 +175,7 @@ Here it is implemented in two steps. First, authentication is performed using th
```rust
fn authenticate(
env: &Env,
signature_payload: &BytesN<32>,
signature_payload: &Hash<32>,
signatures: &Vec<AccSignature>,
) -> Result<(), AccError> {
for i in 0..signatures.len() {
Expand Down Expand Up @@ -213,63 +215,69 @@ fn verify_authorization_policy(
all_signed: bool,
spend_left_per_token: &mut Map<Address, i128>,
) -> Result<(), AccError> {
// For the account control every signer must sign the invocation.
// There are no limitations when every signers signs the transaction.
if all_signed {
return Ok(());
}
let contract_context = match context {
Context::Contract(c) => {
// Allow modifying this contract only if every signer has signed for it.
if &c.contract == curr_contract {
if !all_signed {
return Err(AccError::NotEnoughSigners);
}
return Err(AccError::NotEnoughSigners);
}
c
}
Context::CreateContractHostFn(_) => return Err(AccError::InvalidContext),
// Allow creating new contracts only if every signer has signed for it.
Context::CreateContractHostFn(_) | Context::CreateContractWithCtorHostFn(_) => {
return Err(AccError::NotEnoughSigners);
}
};
```

We verify the policy per `Context`. i.e. Per one `require_auth` call. The policy for the account contract itself enforces every signer to have signed the method call.
We verify the policy per `Context`. i.e. per one `require_auth` call for the address of this account. The policy for the account contract itself enforces every signer to have signed the method call.

```rust
// Otherwise, we're only interested in functions that spend tokens.
if contract_context.fn_name != TRANSFER_FN
&& contract_context.fn_name != Symbol::new(env, "approve")
{
return Ok(());
}

let spend_left: Option<i128> =
if let Some(spend_left) = spend_left_per_token.get(contract_context.contract.clone()) {
Some(spend_left)
} else if let Some(limit_left) = env
.storage()
.instance()
.get::<_, i128>(&DataKey::SpendLimit(contract_context.contract.clone()))
// Besides the checks above we're only interested in functions that spend tokens.
if contract_context.fn_name != TRANSFER_FN
&& contract_context.fn_name != APPROVE_FN
&& contract_context.fn_name != BURN_FN
{
Some(limit_left)
} else {
None
};

// 'None' means that the contract is outside of the policy.
if let Some(spend_left) = spend_left {
// 'amount' is the third argument in both `approve` and `transfer`.
// If the contract has a different signature, it's safer to panic
// here, as it's expected to have the standard interface.
let spent: i128 = contract_context
.args
.get(2)
.unwrap()
.try_into_val(env)
.unwrap();
if spent < 0 {
return Err(AccError::NegativeAmount);
return Ok(());
}
if !all_signed && spent > spend_left {
return Err(AccError::NotEnoughSigners);

let spend_left: Option<i128> =
if let Some(spend_left) = spend_left_per_token.get(contract_context.contract.clone()) {
Some(spend_left)
} else if let Some(limit_left) = env
.storage()
.instance()
.get::<_, i128>(&DataKey::SpendLimit(contract_context.contract.clone()))
{
Some(limit_left)
} else {
None
};

// 'None' means that the contract is outside of the policy.
if let Some(spend_left) = spend_left {
// 'amount' is the third argument in both `approve` and `transfer`.
// If the contract has a different signature, it's safer to panic
// here, as it's expected to have the standard interface.
let spent: i128 = contract_context
.args
.get(2)
.unwrap()
.try_into_val(env)
.unwrap();
if spent < 0 {
return Err(AccError::NegativeAmount);
}
if !all_signed && spent > spend_left {
return Err(AccError::NotEnoughSigners);
}
spend_left_per_token.set(contract_context.contract.clone(), spend_left - spent);
}
spend_left_per_token.set(contract_context.contract.clone(), spend_left - spent);
}
Ok(())
Ok(())
```

Then we check for the standard token function names and verify that for these function we don't exceed the spending limits.
Expand Down
Loading

0 comments on commit 72cfa62

Please sign in to comment.