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

Synchronize example docs and v22 examples. #1150

Merged
merged 1 commit into from
Dec 20, 2024
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
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
Loading