Skip to content
This repository has been archived by the owner on Oct 30, 2023. It is now read-only.

Commit

Permalink
Update docs with internal feedback (#388)
Browse files Browse the repository at this point in the history
* add spoilers and link to counter example

* rename tooling to dev-tooling

* improve API examples

* formatting
  • Loading branch information
CyberHoward committed Jun 30, 2023
1 parent 8498c8b commit d4710f5
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 36 deletions.
2 changes: 1 addition & 1 deletion docs/src/1_intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
The Abstract platform provides a combination of [CosmWasm][1]-oriented products.

- On-chain smart-contract infrastructure ([Abstract SDK](#the-abstract-sdk))
- Tooling ([cw-orchestrator](#cw-orchestrator))
- Development tooling ([cw-orchestrator](#cw-orchestrator))
- Front-end libraries ([Abstract.js](#abstractjs))

Our products are designed to be composable, allowing developers to re-use the components they need to build their applications. While Abstract aims to simplify the development experience, it functions as a powerful tool, enabling you to innovate with less effort.
Expand Down
33 changes: 32 additions & 1 deletion docs/src/4_get_started/3_module_builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ Abstract provides multiple module bases, as detailed in our section on [modules]

The builder pattern employed in building an Abstract module is a slight variation of the actual design pattern. Instead, the module builder lets you set custom entry point handlers at compile time, meaning you end up with a `const` value that is heavily optimized by the compiler. This ensures that the overhead of using Abstract has a negatable effect on both the code's runtime and WASM binary size.

The code-snippets in this example
```admonish info
The code-snippets in this example can be found in the [counter app example](https://github.com/AbstractSDK/contracts/blob/main/packages/abstract-app/examples/counter.rs).
```

### App Type

Expand Down Expand Up @@ -75,6 +77,8 @@ Below we detail each one more closely. The `base` fields and variants mentioned

The instantiate entry point is a mutable entry point of the contract that can only be called on contract instantiation. Instantiation of a contract is essentially the association of a public address to a contract's state.

<details>

#### Function Signature

Expected function signature for the custom instantiate handler:
Expand All @@ -92,11 +96,14 @@ In order to instantiate an Abstract Module, you need to provide an InstantiateMs
```

When the module's instantiate function is called the struct's `module` field is passed to your custom instantiation handler for you to perform any custom logic.
</details>

### Execute

The execute entry point is a mutable entry point of the contract. Logic in this function can update the contract's state and trigger state changes in other contracts by calling them. It is where the majority of your contract's logic will reside.

<details>

#### Function Signature

Expected function signature for the custom execute handler:
Expand All @@ -114,11 +121,14 @@ Called when the App's `ExecuteMsg::Module` variant is called on the execute entr
```

The content of the `Module` variant is passed to your custom execute handler.
</details>

### Query

The query entry point is the non-mutable entry point of the contract. Like its name implies it it used to retrieve data from the contract's state. This state retrieval can have a computation component but it can not alter the contract's or any other state.

<details>

#### Function Signature

Expected function signature for the custom query handler:
Expand All @@ -136,11 +146,14 @@ Called when the App's `QueryMsg::Module` variant is called on the query entry po
```

The content of the `Module` variant is passed to your custom query handler.
</details>

### Migrate

The migrate entry point is a mutable entry point that is called **after** a code_id change is applied to the contract. A migration in CosmWasm essentially swaps out the code that's executed at the contract's address while keeping the state as-is. The implementation of this function is often used to change the format of the contract's state by loading the data as the original format and overwriting it with a new format. All adapter base implementations already perform version assertions that make it impossible to migrate to a contract with a different ID or with a version that is lesser or equal to the old version.

<details>

#### Function Signature

Expected function signature for the custom migrate handler:
Expand All @@ -157,10 +170,14 @@ Called when the App's migrate entry point is called. Uses the struct's `module`
{{#include ../../../packages/abstract-core/src/base.rs:migrate}}
```

</details>

### Reply

The reply entry point is a mutable entry point that is optionally called **after** a previous mutable action. It is often used by factory contracts to retrieve the contract of a newly instantiated contract. It essentially provides the ability perform callbacks on actions. A reply can be requested using CosmWasm's `SubMsg` type and requires a unique `ReplyId` which is a `u64`. The customizable handler takes an array of `(ReplyId, ReplyFn)` tuples and matches any incoming reply on the correct `ReplyId` for you.

<details>

#### Function Signature

Expected function signature for the custom reply handler:
Expand All @@ -173,10 +190,14 @@ Expected function signature for the custom reply handler:

There is no customizable message associated with this entry point.

</details>

### Sudo

The sudo entry point is a mutable entry point that can only be called by the chain's governance module. I.e. any calls made to this contract should have been required to have gone through the chain's governance process. This can vary from chain to chain.

<details>

#### Function Signature

Expected function signature for the custom sudo handler:
Expand All @@ -189,13 +210,17 @@ Expected function signature for the custom sudo handler:

There is no base message for this entry point. Your message will be the message that the endpoint accepts.

</details>

### Receive

The receive handler is a mutable entry point of the contract. It is similar to the `execute` handler but is specifically geared towards handling messages that expect a `Receive` variant in the `ExecuteMsg`. Examples of this include but are not limited to:

- Cw20 send messages
- Nois Network random number feed

<details>

#### Function Signature

Expected function signature for the custom receive handler:
Expand All @@ -212,12 +237,16 @@ Called when the App's `ExecuteMsg::Receive` variant is called on the execute ent
{{#include ../../../packages/abstract-core/src/base.rs:exec}}
```

</details>

### Ibc Callback

The ibc callback handler is a mutable entry point of the contract. It is similar to the `execute` handler but is specifically geared towards handling callbacks from IBC actions. Since interacting with IBC is an asynchronous process we aim to provide you with the means to easily work with IBC. Our SDK helps you send IBC messages while this handler helps you execute logic whenever the IBC action succeeds or fails. Our framework does this by optionally allowing you to add callback information to any IBC action. A callback requires a unique `CallbackId` which is a `String`. The callback handler takes an array of `(CallbackId, IbcCallbackFn)` tuples and matches any incoming callback on the correct `CallbackId` for you. Every call to this handler is verified by asserting that the caller is the framework's IBC-Client contract.

<!-- > We cover Abstract's IBC logic later in this book (TODO: add link to that section.) -->

<details>

#### Function Signature

```rust,ignore
Expand All @@ -232,6 +261,8 @@ Called when the App's `ExecuteMsg::IbcCallback` variant is called on the execute
{{#include ../../../packages/abstract-core/src/base.rs:exec}}
```

</details>

## Dependencies

Theres is one additional contractor method available on the module builder and that's the `with_dependencies` function. As it states it allows you to specify any smart-contract dependencies that your application might require. This is a key requirement for building truly composable and secure applications. We'll cover dependencies further in [our section on them]()
Expand Down
67 changes: 46 additions & 21 deletions docs/src/4_get_started/4_sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,51 +6,49 @@ Now that you've got your module set up you're ready for our hot sauce. While you

## How it works

The `abstract-sdk` crate is a toolbox for developers to create composable smart-contract APIs. It allows you to use composed functionality with a few keystrokes through it's combination of supertraits and blanket implementations. Supertraits are Rust traits that have one or multiple trait bounds while a blanket implementation is a Rust implementation that is automatically implemented for every object that meets the trait bounds. The Abstract SDK makes use of both to achieve its modular design.
The `abstract-sdk` crate is a toolbox for developers to create composable smart-contract APIs. It allows you to use composed functionality with a few keystrokes through it's combination of supertraits and blanket implementations. Supertraits are Rust traits that have one or multiple trait bounds while a blanket implementation is a Rust implementation that is automatically implemented for every object that meets the trait bounds. The Abstract SDK uses both to achieve its modular design.

### Features
## APIs

Features are the lowest-level traits that are contained within the SDK and they don't have any trait bounds. They generally act as data accessor traits. I.e. if a struct implements a feature it means that it has some way to get the information required by that feature.

Here's an example of such a feature:
Abstract API objects are Rust structs that expose some smart-contract functionality. Such an API can only be retrieved if a contract (or feature-object) implements the required features/api traits. Access to an API is automatically provided if the trait constraints for the API are met by the contract.

```rust
{{#include ../../../packages/abstract-sdk/src/base/features/abstract_name_service.rs:ans }}
```
Most of the APIs either return a `CosmosMsg` or an `AccountAction`. The `CosmosMsg` is a message that should be added as-is to the `Response` to perform some action.

Any structure that implements this traits has access to the Abstract Name Service, and thus has a way to resolve ANS entries. By composing these features it is possible to write advanced APIs that are automatically implemented on objects that support its required features.
### `CosmosMsg` Example

Now instead of letting you implement these traits yourself, we've already gone ahead and implemented them for the App and Adapter structs. Here's the implementation for the App:
This example sends coins from the local contract (module) to the account that the application is installed on which does not require the account itself to execute the action.

```rust
{{#include ../../../packages/abstract-app/src/features.rs:ans }}
```rust,ignore
{{#include ../../../packages/abstract-sdk/src/apis/bank.rs:deposit }}
```

So when you're building your application the module struct already has the features and data required to do the basic abstract operations. With this we can start creating more advanced functionality.
Alternatively `AccountAction` structs can also be returned by an API. An `AccountAction` is supposed to be forwarded to the Abstract Account to let the account perform action. `AccountAction`s can be executed with the [`Executor`](https://docs.rs/abstract-sdk/latest/abstract_sdk/struct.Executor.html) API. The returned `CosmosMsg` should be added to the action's `Response`.

> Other structs that implement a feature without being module bases are called *Feature Objects*.
### `AccountAction` Example

### APIs
This example sends coins from the account to another address which requires the account itself to execute the action.

The Abstract API objects are structs that expose some smart-contract functionality and that can only be retrieved if a contract or feature-object implements the required features/api traits. If the trait constraints for the API is met it is automatically implemented on the object and hence allows you to retrieve the API object.
```rust,ignore
{{#include ../../../packages/abstract-sdk/src/apis/bank.rs:transfer }}
```

Most of the APIs either return a `CosmosMsg` or an `AccountAction`. The `CosmosMsg` is a message that should be added as-is to the `Response` to perform some action. The `AccountAction` is a message that can be sent to the Abstract Account to perform some action. `AccountAction`s can be executed with the [`Executor`](https://docs.rs/abstract-sdk/latest/abstract_sdk/struct.Executor.html) API. The returned `CosmosMsg` should be added to the action's `Response`.
## Creating your own API

#### Example
The [`Bank`](https://docs.rs/abstract-sdk/latest/abstract_sdk/struct.Bank.html) API allows developers to transfer assets from and to the Account. We now want to use this API to create a `Splitter` API that splits the transfer of some amount of funds between a set of receivers.

The [`Bank`](https://docs.rs/abstract-sdk/latest/abstract_sdk/struct.Bank.html) API allows developers to transfer assets from and to the Account through their module object. We now want to use this API to create a `Splitter` API that splits the transfer of some amount of funds between a set of receivers.
> The code behind this example is available [here](https://github.com/AbstractSDK/contracts/blob/main/packages/abstract-sdk/src/apis/splitter.rs).
```rust,ignore
{{#include ../../../packages/abstract-sdk/src/apis/splitter.rs:splitter }}
```

These APIs can then be used by any contract that implements its required traits, in this case the `TransferInterface`.

```rust,no_run
```rust,ignore
{{#include ../../../packages/abstract-sdk/src/apis/splitter.rs:usage }}
```

### Available API Objects
## Available API Objects

The following API objects are available in the Abstract SDK:

Expand All @@ -64,6 +62,33 @@ The following API objects are available in the Abstract SDK:
- [`Modules`](https://docs.rs/abstract-sdk/latest/abstract_sdk/struct.Modules.html)
- [`AccountRegistry`](https://docs.rs/abstract-sdk/latest/abstract_sdk/struct.AccountRegistry.html)

Other projects have also started building APIs. Here are some examples:

- [`Cron Cats`](https://github.com/CronCats/abstract-croncat-app/blob/main/src/api.rs)
- More coming soon...

## Features

Features are the lowest-level traits that are contained within the SDK and they don't have any trait bounds. They generally act as data accessor traits. I.e. if a struct implements a feature it means that it has some way to get the information required by that feature.

Here's an example of such a feature:

```rust
{{#include ../../../packages/abstract-sdk/src/base/features/abstract_name_service.rs:ans }}
```

Any structure that implements this traits has access to the Abstract Name Service, and thus has a way to resolve ANS entries. By composing these features it is possible to write advanced APIs that are automatically implemented on objects that support its required features.

Now instead of letting you implement these traits yourself, we've already gone ahead and implemented them for the App and Adapter structs. Here's the implementation for the App:

```rust
{{#include ../../../packages/abstract-app/src/features.rs:ans }}
```

So when you're building your application the module struct already has the features and data required to do the basic abstract operations. With this we can start creating more advanced functionality.

> Other structs that implement a feature without being module bases are called *Feature Objects*.
## Usage

Add `abstract-sdk` to your `Cargo.toml` by running:
Expand Down
49 changes: 36 additions & 13 deletions packages/abstract-sdk/src/apis/bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,27 +229,44 @@ mod test {
use speculoos::prelude::*;

mod transfer_coins {
use abstract_core::proxy::ExecuteMsg;

use crate::{Execution, Executor};

use super::*;

#[test]
fn transfer_asset_to_sender() {
let app = MockModule::new();
let deps = mock_dependencies();
let expected_amount = 100u128;
let expected_recipient = Addr::unchecked("recipient");

let bank = app.bank(deps.as_ref());
let coins = coins(expected_amount, "asset");
let actual_res = bank.transfer(coins.clone(), &expected_recipient);
// ANCHOR: transfer
let recipient: Addr = Addr::unchecked("recipient");
let bank: Bank<'_, MockModule> = app.bank(deps.as_ref());
let coins: Vec<Coin> = coins(100u128, "asset");
let bank_transfer: AccountAction = bank.transfer(coins.clone(), &recipient).unwrap();

assert_that!(actual_res).is_ok();
let executor: Executor<'_, MockModule> = app.executor(deps.as_ref());
let account_message: CosmosMsg = executor.execute(vec![bank_transfer]).unwrap();
let response: Response = Response::new().add_message(account_message);
// ANCHOR_END: transfer

let expected_msg = CosmosMsg::Bank(BankMsg::Send {
to_address: expected_recipient.to_string(),
to_address: recipient.to_string(),
amount: coins,
});

assert_that!(actual_res.unwrap().messages()[0]).is_equal_to(&expected_msg);
assert_that!(response.messages[0].msg).is_equal_to(
&wasm_execute(
TEST_PROXY,
&ExecuteMsg::ModuleAction {
msgs: vec![expected_msg],
},
vec![],
)
.unwrap()
.into(),
);
}
}

Expand All @@ -262,18 +279,24 @@ mod test {
fn deposit() {
let app = MockModule::new();
let deps = mock_dependencies();
let expected_amount = 100u128;

let bank = app.bank(deps.as_ref());
let coins = coins(expected_amount, "asset");
let actual_res = bank.deposit(coins.clone()).unwrap()[0].clone();
// ANCHOR: deposit
// Get bank API struct from the app
let bank: Bank<'_, MockModule> = app.bank(deps.as_ref());
// Create coins to deposit
let coins: Vec<Coin> = coins(100u128, "asset");
// Construct messages for deposit (transfer from this contract to the account)
let deposit_msgs: Vec<CosmosMsg> = bank.deposit(coins.clone()).unwrap();
// Add to response
let response: Response = Response::new().add_messages(deposit_msgs);
// ANCHOR_END: deposit

let expected_msg: CosmosMsg = CosmosMsg::Bank(BankMsg::Send {
to_address: TEST_PROXY.to_string(),
amount: coins,
});

assert_that!(actual_res).is_equal_to::<CosmosMsg>(expected_msg);
assert_that!(response.messages[0].msg).is_equal_to::<CosmosMsg>(expected_msg);
}
}

Expand Down

0 comments on commit d4710f5

Please sign in to comment.