├── .cargo
└── config
├── .gitignore
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── contracts
├── active-pool
│ ├── .cargo
│ │ └── config
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── contract.rs
│ │ ├── error.rs
│ │ ├── lib.rs
│ │ ├── state.rs
│ │ ├── sudo.rs
│ │ └── tests.rs
├── band-ibc-oracle
│ ├── Cargo.toml
│ ├── README.md
│ ├── examples
│ │ └── schema.rs
│ ├── schema
│ │ ├── channel_response.json
│ │ ├── execute_msg.json
│ │ ├── init_msg.json
│ │ ├── list_channels_response.json
│ │ ├── port_response.json
│ │ └── query_msg.json
│ └── src
│ │ ├── contract.rs
│ │ ├── error.rs
│ │ ├── ibc.rs
│ │ ├── lib.rs
│ │ ├── msg.rs
│ │ └── state.rs
├── coll-surplus-pool
│ ├── .cargo
│ │ └── config
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── contract.rs
│ │ ├── error.rs
│ │ ├── lib.rs
│ │ ├── state.rs
│ │ └── sudo.rs
├── default-pool
│ ├── .cargo
│ │ └── config
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── contract.rs
│ │ ├── error.rs
│ │ ├── lib.rs
│ │ ├── state.rs
│ │ └── sudo.rs
├── junoswap-oracle
│ ├── .cargo
│ │ └── config
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ │ ├── contract.rs
│ │ ├── error.rs
│ │ ├── lib.rs
│ │ ├── overflow_tests.rs
│ │ └── state.rs
└── ultra-token
│ ├── .cargo
│ └── config
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── README.md
│ ├── examples
│ └── schema.rs
│ ├── schema
│ ├── all_accounts_response.json
│ ├── all_allowances_response.json
│ ├── allowance_response.json
│ ├── balance_response.json
│ ├── execute_msg.json
│ ├── instantiate_msg.json
│ ├── query_msg.json
│ └── token_info_response.json
│ └── src
│ ├── allowances.rs
│ ├── contract.rs
│ ├── enumerable.rs
│ ├── error.rs
│ ├── lib.rs
│ ├── msg.rs
│ └── state.rs
├── packages
└── ultra-base
│ ├── .cargo
│ └── config
│ ├── Cargo.toml
│ └── src
│ ├── active_pool.rs
│ ├── asset.rs
│ ├── borrower_operations.rs
│ ├── coll_surplus_pool.rs
│ ├── default_pool.rs
│ ├── hint_helpers.rs
│ ├── lib.rs
│ ├── oracle.rs
│ ├── querier.rs
│ ├── sorted_troves.rs
│ ├── stability_pool.rs
│ ├── trove_manager.rs
│ └── ultra_math.rs
└── test-contracts
└── price-feed-test
├── .cargo
└── config
├── Cargo.toml
├── README.md
└── src
├── contract.rs
├── error.rs
├── lib.rs
├── msg.rs
└── state.rs
/.cargo/config:
--------------------------------------------------------------------------------
1 | [alias]
2 | wasm = "build --release --target wasm32-unknown-unknown"
3 | wasm-debug = "build --target wasm32-unknown-unknown"
4 | unit-test = "test --lib"
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # macOS
2 | .DS_Store
3 |
4 | # Text file backups
5 | **/*.rs.bk
6 |
7 | # Build results
8 | target/
9 |
10 | # IDEs
11 | .vscode/
12 | .idea/
13 | *.iml
14 |
15 | # Auto-gen
16 | .cargo-ok
17 |
18 | # Build artifacts
19 | *.wasm
20 | hash.txt
21 | contracts.txt
22 | artifacts/
23 |
24 | # code coverage
25 | tarpaulin-report.*
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = ["contracts/*", "packages/*", "test-contracts/*"]
3 |
4 | [profile.release.package.ultra-token]
5 | codegen-units = 1
6 | incremental = false
7 |
8 | [profile.release.package.active-pool]
9 | codegen-units = 1
10 | incremental = false
11 |
12 | [profile.release.package.default-pool]
13 | codegen-units = 1
14 | incremental = false
15 |
16 | [profile.release.package.trove-manager]
17 | codegen-units = 1
18 | incremental = false
19 |
20 | [profile.release.package.band-ibc-oracle]
21 | codegen-units = 1
22 | incremental = false
23 |
24 | [profile.release.package.junoswap-oracle]
25 | codegen-units = 1
26 | incremental = false
27 |
28 | [profile.release.package.coll-surplus-pool]
29 | codegen-units = 1
30 | incremental = false
31 |
32 | [profile.release]
33 | codegen-units = 1
34 | opt-level = 3
35 | debug = false
36 | rpath = false
37 | lto = true
38 | debug-assertions = false
39 | panic = 'abort'
40 | incremental = false
41 | overflow-checks = true
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 notional-labs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ⚡️ C O S M W A S M S T A B L E C O I N ⚡️
5 |
6 |
7 | coming soon to junø network
8 |
9 |
10 |
11 |
12 |
13 | ## 💡 Overview
14 | Ultra is a CosmWasm stablecoin protocol developed on JunoNetwork by an independent collective of Cosmos contributors. Inspired by [Liquity](https://www.liquity.org/), Ultra features rapid liquidations, instant redemptions, and fully autonomous stability.
15 |
--------------------------------------------------------------------------------
/contracts/active-pool/.cargo/config:
--------------------------------------------------------------------------------
1 | [alias]
2 | wasm = "build --release --target wasm32-unknown-unknown"
3 | wasm-debug = "build --target wasm32-unknown-unknown"
4 | schema = "run --example schema"
5 |
--------------------------------------------------------------------------------
/contracts/active-pool/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "active-pool"
3 | version = "0.1.0"
4 | authors = ["Chinh D.Nguyen "]
5 | edition = "2021"
6 |
7 | description = "The Active Pool holds the JUNO collateral and ULTRA debt (but not ULTRA tokens) for all active troves."
8 | repository = "https://github.com/notional-labs/UltraStableJuno"
9 |
10 | [lib]
11 | crate-type = ["cdylib", "rlib"]
12 |
13 | [features]
14 | backtraces = ["cosmwasm-std/backtraces"]
15 | # use library feature to disable all instantiate/execute/query exports
16 | library = []
17 |
18 | [dependencies]
19 | cw2 = { version = "0.13.4" }
20 | cosmwasm-std = { version = "1.0.0" }
21 | cw-storage-plus = { version = "0.13.4" }
22 | schemars = "0.8.1"
23 | serde = { version = "1.0.103", default-features = false, features = ["derive"] }
24 | thiserror = { version = "1.0.23" }
25 | ultra-base = { path = "../../packages/ultra-base", default-features = false }
26 |
27 | [dev-dependencies]
28 | cw-multi-test = { version = "0.13.4" }
29 |
30 |
31 |
--------------------------------------------------------------------------------
/contracts/active-pool/README.md:
--------------------------------------------------------------------------------
1 | # Active Pool
2 | The Active Pool holds the JUNO collateral and ULTRA debt (but not ULTRA tokens) for all active troves.
3 | When a trove is liquidated, it's JUNO and ULTRA debt are transferred from the Active Pool, to either the Stability Pool, the Default Pool, or both, depending on the liquidation conditions.
--------------------------------------------------------------------------------
/contracts/active-pool/src/contract.rs:
--------------------------------------------------------------------------------
1 | use std::vec;
2 |
3 | #[cfg(not(feature = "library"))]
4 | use cosmwasm_std::entry_point;
5 | use cosmwasm_std::{
6 | coin, to_binary, Addr, BankMsg, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError,
7 | StdResult, Storage, Uint128,
8 | };
9 |
10 | use cw2::set_contract_version;
11 |
12 | use crate::error::ContractError;
13 | use crate::state::{
14 | AddressesSet, AssetsInPool, SudoParams, ADDRESSES_SET, ASSETS_IN_POOL, SUDO_PARAMS,
15 | };
16 | use ultra_base::active_pool::{ExecuteMsg, InstantiateMsg, ParamsResponse, QueryMsg};
17 |
18 | // version info for migration info
19 | const CONTRACT_NAME: &str = "crates.io:active-pool";
20 | const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
21 |
22 | pub const NATIVE_JUNO_DENOM: &str = "ujuno";
23 |
24 | #[cfg_attr(not(feature = "library"), entry_point)]
25 | pub fn instantiate(
26 | deps: DepsMut,
27 | _env: Env,
28 | _info: MessageInfo,
29 | msg: InstantiateMsg,
30 | ) -> Result {
31 | // set the contract version in the contract storage
32 | set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
33 |
34 | // store sudo params (name and owner address)
35 | let sudo_params = SudoParams {
36 | name: msg.name,
37 | owner: deps.api.addr_validate(&msg.owner)?,
38 | };
39 |
40 | // initial assets in pool
41 | let assets_in_pool = AssetsInPool {
42 | juno: Uint128::zero(),
43 | ultra_debt: Uint128::zero(),
44 | };
45 |
46 | // save sudo params and initial assets in pool in contract storage
47 | SUDO_PARAMS.save(deps.storage, &sudo_params)?;
48 | ASSETS_IN_POOL.save(deps.storage, &assets_in_pool)?;
49 |
50 | Ok(Response::default())
51 | }
52 |
53 | #[cfg_attr(not(feature = "library"), entry_point)]
54 | pub fn execute(
55 | deps: DepsMut,
56 | env: Env,
57 | info: MessageInfo,
58 | msg: ExecuteMsg,
59 | ) -> Result {
60 | // Match on the `ExecuteMsg` to determine which function to call
61 | match msg {
62 | ExecuteMsg::IncreaseULTRADebt { amount } => {
63 | // Call the `execute_increase_ultra_debt` function
64 | execute_increase_ultra_debt(deps, env, info, amount)
65 | }
66 | ExecuteMsg::DecreaseULTRADebt { amount } => {
67 | // Call the `execute_decrease_ultra_debt` function
68 | execute_decrease_ultra_debt(deps, env, info, amount)
69 | }
70 | ExecuteMsg::SendJUNO { recipient, amount } => {
71 | // Call the `execute_send_juno` function
72 | execute_send_juno(deps, env, info, recipient, amount)
73 | }
74 | ExecuteMsg::SetAddresses {
75 | borrower_operations_address,
76 | trove_manager_address,
77 | stability_pool_address,
78 | default_pool_address,
79 | } =>
80 | // Call the `execute_set_addresses` function
81 | execute_set_addresses(
82 | deps,
83 | env,
84 | info,
85 | borrower_operations_address,
86 | trove_manager_address,
87 | stability_pool_address,
88 | default_pool_address,
89 | ),
90 | }
91 | }
92 |
93 | pub fn execute_increase_ultra_debt(
94 | deps: DepsMut, // A struct that holds references to mutable dependencies (e.g., storage)
95 | _env: Env, // An environment variable that holds information about the blockchain and the current transaction
96 | info: MessageInfo, // Information about the message that triggered this function call
97 | amount: Uint128, // The amount to increase the ultra debt by
98 | ) -> Result {
99 |
100 | // Check that the message sender is either the BO or the TM
101 | only_bo_or_tm(deps.storage, &info)?;
102 |
103 | // Load the current assets in the pool
104 | let mut assets_in_pool = ASSETS_IN_POOL.load(deps.storage)?;
105 |
106 | // Increase the ultra debt by the specified amount
107 | assets_in_pool.ultra_debt += amount;
108 |
109 | // Save the updated assets in the pool
110 | ASSETS_IN_POOL.save(deps.storage, &assets_in_pool)?;
111 |
112 | // Create a response object with information about the action taken
113 | let res = Response::new()
114 | .add_attribute("action", "increase_ultra_debt")
115 | .add_attribute("amount", amount);
116 |
117 | // Return the response
118 | Ok(res)
119 | }
120 |
121 | pub fn execute_decrease_ultra_debt(
122 | deps: DepsMut,
123 | _env: Env,
124 | info: MessageInfo,
125 | amount: Uint128,
126 | ) -> Result {
127 |
128 | // Ensure that the caller is either the Board of Directors, Treasury Manager, or Shareholder Proxy
129 | only_bo_or_tm_or_sp(deps.storage, &info)?;
130 |
131 | // Load the current value of the assets in the pool
132 | let mut assets_in_pool = ASSETS_IN_POOL.load(deps.storage)?;
133 |
134 | // Check that the new value of ultra_debt will not overflow, then update the value
135 | assets_in_pool.ultra_debt = assets_in_pool
136 | .ultra_debt
137 | .checked_sub(amount)
138 | .map_err(StdError::overflow)?;
139 |
140 | // Save the updated value of the assets in the pool
141 | ASSETS_IN_POOL.save(deps.storage, &assets_in_pool)?;
142 |
143 | // Create and return the response object
144 | let res = Response::new()
145 | .add_attribute("action", "decrease_ultra_debt")
146 | .add_attribute("amount", amount);
147 | Ok(res)
148 | }
149 |
150 | pub fn execute_send_juno(
151 | deps: DepsMut, // a set of dependencies
152 | _env: Env, // an environment object
153 | info: MessageInfo, // a message info object
154 | recipient: Addr, // the recipient address
155 | amount: Uint128, // the amount of JUNO tokens to send
156 | ) -> Result {
157 | only_bo_or_tm_or_sp(deps.storage, &info)?; // check that the caller is BO, TM, or SP
158 |
159 | let mut assets_in_pool = ASSETS_IN_POOL.load(deps.storage)?; // retrieve assets in pool from storage
160 | assets_in_pool.juno = assets_in_pool
161 | .juno
162 | .checked_sub(amount) // subtract the specified amount of JUNO tokens from the pool
163 | .map_err(StdError::overflow)?; // return error if there is an overflow
164 | ASSETS_IN_POOL.save(deps.storage, &assets_in_pool)?; // save updated assets in pool to storage
165 |
166 | let send_msg = BankMsg::Send { // construct a BankMsg::Send message
167 | to_address: recipient.to_string(),
168 | amount: vec![coin(amount.u128(), NATIVE_JUNO_DENOM.to_string())],
169 | };
170 | let res = Response::new() // create a new response
171 | .add_message(send_msg) // add the BankMsg::Send message to the response
172 | .add_attribute("action", "send_juno") // add an attribute to the response
173 | .add_attribute("recipient", recipient) // add an attribute to the response
174 | .add_attribute("amount", amount); // add an attribute to the response
175 | Ok(res) // return the response
176 | }
177 |
178 | // This function updates the set of contract addresses that the current contract depends on.
179 | // It only allows the contract owner to update these addresses.
180 | pub fn execute_set_addresses(
181 | deps: DepsMut, // The contract dependencies, including the storage and the API
182 | _env: Env, // The contract environment, which provides information about the blockchain
183 | info: MessageInfo, // Information about the message that triggered this contract execution
184 | borrower_operations_address: String, // The new address of the borrower operations contract
185 | trove_manager_address: String, // The new address of the trove manager contract
186 | stability_pool_address: String, // The new address of the stability pool contract
187 | default_pool_address: String, // The new address of the default pool contract
188 | ) -> Result {
189 | // Ensure that only the contract owner can update the addresses set
190 | only_owner(deps.storage, &info)?;
191 |
192 | // Validate and convert the new addresses to their HEX representation
193 | let new_addresses_set = AddressesSet {
194 | borrower_operations_address: deps.api.addr_validate(&borrower_operations_address)?,
195 | trove_manager_address: deps.api.addr_validate(&trove_manager_address)?,
196 | stability_pool_address: deps.api.addr_validate(&stability_pool_address)?,
197 | default_pool_address: deps.api.addr_validate(&default_pool_address)?,
198 | };
199 |
200 | // Save the new addresses set in the contract storage
201 | ADDRESSES_SET.save(deps.storage, &new_addresses_set)?;
202 |
203 | // Build and return the response with the updated addresses set
204 | let res = Response::new()
205 | .add_attribute("action", "set_addresses")
206 | .add_attribute("borrower_operations_address", borrower_operations_address)
207 | .add_attribute("trove_manager_address", trove_manager_address)
208 | .add_attribute("stability_pool_address", stability_pool_address)
209 | .add_attribute("default_pool_address", default_pool_address);
210 | Ok(res)
211 | }
212 |
213 | /// Checks to enforce that only borrower operations or default pool can call
214 | fn only_bo_or_dp(store: &dyn Storage, info: &MessageInfo) -> Result {
215 | // Load the set of addresses
216 | let addresses_set = ADDRESSES_SET.load(store)?;
217 | // Check if the caller is not the borrower operations address or the default pool address
218 | if addresses_set.borrower_operations_address != info.sender.as_ref()
219 | && addresses_set.default_pool_address != info.sender.as_ref()
220 | {
221 | // Return an error if the caller is not authorized
222 | return Err(ContractError::CallerIsNeitherBONorDP {});
223 | }
224 | // Return the caller's address if the caller is authorized
225 | Ok(info.sender.clone())
226 | }
227 |
228 | /// Checks to enforce that only borrower operations or trove manager or stability pool can call
229 | fn only_bo_or_tm_or_sp(store: &dyn Storage, info: &MessageInfo) -> Result {
230 | // Load the set of addresses
231 | let addresses_set = ADDRESSES_SET.load(store)?;
232 | // Check if the caller is not the borrower operations address, the trove manager address, or the stability pool address
233 | if addresses_set.borrower_operations_address != info.sender.as_ref()
234 | && addresses_set.trove_manager_address != info.sender.as_ref()
235 | && addresses_set.stability_pool_address != info.sender.as_ref()
236 | {
237 | // Return an error if the caller is not authorized
238 | return Err(ContractError::CallerIsNeitherBONorTMNorSP {});
239 | }
240 | // Return the caller's address if the caller is authorized
241 | Ok(info.sender.clone())
242 | }
243 |
244 | /// This function checks if the caller of the contract is either the borrower operations address or the trove manager address.
245 | /// If the caller is not one of these addresses, it returns an error.
246 | ///
247 | /// # Arguments
248 | ///
249 | /// * `store`: A reference to the contract's storage
250 | /// * `info`: Information about the message that called the contract
251 | ///
252 | /// # Returns
253 | ///
254 | /// * An `Addr` representing the caller of the contract if the caller is authorized
255 | /// * An error if the caller is not authorized
256 | fn only_bo_or_tm(store: &dyn Storage, info: &MessageInfo) -> Result {
257 | // Load the addresses set from storage
258 | let addresses_set = ADDRESSES_SET.load(store)?;
259 |
260 | // Check if the caller is the borrower operations address or the trove manager address.
261 | // If the caller is not one of these addresses, return an error.
262 | if addresses_set.borrower_operations_address != info.sender.as_ref()
263 | && addresses_set.trove_manager_address != info.sender.as_ref()
264 | {
265 | return Err(ContractError::CallerIsNeitherBONorTM {});
266 | }
267 |
268 | // If the caller is authorized, return the caller's address
269 | Ok(info.sender.clone())
270 | }
271 |
272 | /// This function checks if the caller of the contract is the owner of the contract.
273 | /// If the caller is not the owner, it returns an error.
274 | ///
275 | /// # Arguments
276 | ///
277 | /// * `store`: A reference to the contract's storage
278 | /// * `info`: Information about the message that called the contract
279 | ///
280 | /// # Returns
281 | ///
282 | /// * An `Addr` representing the caller of the contract if the caller is authorized
283 | /// * An error if the caller is not authorized
284 | fn only_owner(store: &dyn Storage, info: &MessageInfo) -> Result {
285 | // Load the contract's parameters from storage
286 | let params = SUDO_PARAMS.load(store)?;
287 |
288 | // Check if the caller is the owner of the contract.
289 | // If the caller is not the owner, return an error.
290 | if params.owner != info.sender.as_ref() {
291 | return Err(ContractError::UnauthorizedOwner {});
292 | }
293 |
294 | // If the caller is authorized, return the caller's address
295 | Ok(info.sender.clone())
296 | }
297 |
298 | // This line sets the entry point for the code depending on whether the "library"
299 | // feature is enabled or not.
300 | #[cfg_attr(not(feature = "library"), entry_point)]
301 |
302 | // This function processes different types of messages and returns the result as a binary value.
303 | pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult {
304 | // Match the type of the message.
305 | match msg {
306 | // If the message is a request to get the parameters, call `query_params` and return the result as a binary value.
307 | QueryMsg::GetParams {} => to_binary(&query_params(deps)?),
308 |
309 | // If the message is a request to get the JUNO state, call `query_juno_state` and return the result as a binary value.
310 | QueryMsg::GetJUNO {} => to_binary(&query_juno_state(deps)?),
311 |
312 | // If the message is a request to get the ULTRADebt state, call `query_ultra_debt_state` and return the result as a binary value.
313 | QueryMsg::GetULTRADebt {} => to_binary(&query_ultra_debt_state(deps)?),
314 |
315 | // If the message is a request to get the borrower operations address, call `query_borrower_operations_address` and return the result as a binary value.
316 | QueryMsg::GetBorrowerOperationsAddress {} => {
317 | to_binary(&query_borrower_operations_address(deps)?)
318 | }
319 |
320 | // If the message is a request to get the stability pool address, call `query_stability_pool_address` and return the result as a binary value.
321 | QueryMsg::GetStabilityPoolAddress {} => to_binary(&query_stability_pool_address(deps)?),
322 |
323 | // If the message is a request to get the default pool address, call `query_default_pool_address` and return the result as a binary value.
324 | QueryMsg::GetDefaultPoolAddress {} => to_binary(&query_default_pool_address(deps)?),
325 |
326 | // If the message is a request to get the trove manager address, call `query_trove_manager_address` and return the result as a binary value.
327 | QueryMsg::GetTroveManagerAddress {} => to_binary(&query_trove_manager_address(deps)?),
328 | }
329 | }
330 |
331 | // This function retrieves the JUNO state from storage and returns it as a result.
332 | pub fn query_juno_state(deps: Deps) -> StdResult {
333 | // Load the assets in the pool from storage.
334 | let info = ASSETS_IN_POOL.load(deps.storage)?;
335 |
336 | // Retrieve the JUNO state from the assets in the pool.
337 | let res = info.juno;
338 |
339 | // Return the JUNO state.
340 | Ok(res)
341 | }
342 |
343 | // This function queries the current ultra debt state
344 | pub fn query_ultra_debt_state(deps: Deps) -> StdResult {
345 | // Load the assets in the pool from storage
346 | let info = ASSETS_IN_POOL.load(deps.storage)?;
347 |
348 | // Return the current ultra debt state
349 | let res = info.ultra_debt;
350 | Ok(res)
351 | }
352 |
353 | // This function queries the current parameters
354 | pub fn query_params(deps: Deps) -> StdResult {
355 | // Load the sudo parameters from storage
356 | let info = SUDO_PARAMS.load(deps.storage)?;
357 |
358 | // Return the current name and owner of the parameters
359 | let res = ParamsResponse {
360 | name: info.name,
361 | owner: info.owner,
362 | };
363 | Ok(res)
364 | }
365 |
366 | // This function queries the current borrower operations address
367 | pub fn query_borrower_operations_address(deps: Deps) -> StdResult {
368 | // Load the addresses set from storage
369 | let addresses_set = ADDRESSES_SET.load(deps.storage)?;
370 |
371 | // Return the current borrower operations address
372 | let borrower_operations_address = addresses_set.borrower_operations_address;
373 | Ok(borrower_operations_address)
374 | }
375 |
376 | // This function queries the current stability pool address
377 | pub fn query_stability_pool_address(deps: Deps) -> StdResult {
378 | // Load the addresses set from storage
379 | let addresses_set = ADDRESSES_SET.load(deps.storage)?;
380 |
381 | // Return the current stability pool address
382 | let stability_pool_address = addresses_set.stability_pool_address;
383 | Ok(stability_pool_address)
384 | }
385 |
386 | // This function retrieves the default pool address from the ADDRESSES_SET,
387 | // loads it from storage, and returns it as a StdResult.
388 | pub fn query_default_pool_address(deps: Deps) -> StdResult {
389 | // Load the addresses set from storage.
390 | let addresses_set = ADDRESSES_SET.load(deps.storage)?;
391 | // Retrieve the default pool address from the set.
392 | let default_pool_address = addresses_set.default_pool_address;
393 | // Return the default pool address as a StdResult.
394 | Ok(default_pool_address)
395 | }
396 |
397 | // This function retrieves the trove manager address from the ADDRESSES_SET,
398 | // loads it from storage, and returns it as a StdResult.
399 | pub fn query_trove_manager_address(deps: Deps) -> StdResult {
400 | // Load the addresses set from storage.
401 | let addresses_set = ADDRESSES_SET.load(deps.storage)?;
402 | // Retrieve the trove manager address from the set.
403 | let trove_manager_address = addresses_set.trove_manager_address;
404 | // Return the trove manager address as a StdResult.
405 | Ok(trove_manager_address)
406 | }
407 |
--------------------------------------------------------------------------------
/contracts/active-pool/src/error.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::StdError;
2 | use thiserror::Error;
3 |
4 | #[derive(Error, Debug, PartialEq)]
5 | pub enum ContractError {
6 | #[error("{0}")]
7 | Std(#[from] StdError),
8 |
9 | #[error("UnauthorizedOwner")]
10 | UnauthorizedOwner {},
11 |
12 | #[error("ActivePool: Caller is neither BO nor Default Pool")]
13 | CallerIsNeitherBONorDP {},
14 |
15 | #[error("ActivePool: Caller is neither BorrowerOperations nor TroveManager nor StabilityPool")]
16 | CallerIsNeitherBONorTMNorSP {},
17 |
18 | #[error("ActivePool: Caller is neither BorrowerOperations nor TroveManager")]
19 | CallerIsNeitherBONorTM {},
20 | }
21 |
--------------------------------------------------------------------------------
/contracts/active-pool/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod contract;
2 | mod error;
3 | pub mod state;
4 | pub mod sudo;
5 | #[cfg(test)]
6 | mod tests;
7 |
8 | pub use crate::error::ContractError;
9 |
--------------------------------------------------------------------------------
/contracts/active-pool/src/state.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::{Addr, Uint128};
2 | use cw_storage_plus::Item;
3 | use schemars::JsonSchema;
4 | use serde::{Deserialize, Serialize};
5 |
6 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
7 | pub struct AddressesSet {
8 | pub borrower_operations_address: Addr,
9 | pub trove_manager_address: Addr,
10 | pub stability_pool_address: Addr,
11 | pub default_pool_address: Addr,
12 | }
13 |
14 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
15 | pub struct AssetsInPool {
16 | pub juno: Uint128,
17 | pub ultra_debt: Uint128,
18 | }
19 |
20 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
21 | pub struct SudoParams {
22 | pub name: String,
23 | pub owner: Addr,
24 | }
25 |
26 | pub const SUDO_PARAMS: Item = Item::new("sudo-params");
27 | pub const ADDRESSES_SET: Item = Item::new("addresses_set");
28 | pub const ASSETS_IN_POOL: Item = Item::new("assets_in_pool");
29 |
--------------------------------------------------------------------------------
/contracts/active-pool/src/sudo.rs:
--------------------------------------------------------------------------------
1 | use crate::error::ContractError;
2 | use crate::state::SUDO_PARAMS;
3 | use cosmwasm_std::{entry_point, Addr, DepsMut, Env, Response};
4 | use ultra_base::active_pool::SudoMsg;
5 |
6 | pub struct ParamInfo {
7 | name: Option,
8 | owner: Option,
9 | }
10 |
11 | #[cfg_attr(not(feature = "library"), entry_point)]
12 | pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> Result {
13 | match msg {
14 | SudoMsg::UpdateParams { name, owner } => {
15 | sudo_update_params(deps, env, ParamInfo { name, owner })
16 | }
17 | }
18 | }
19 |
20 | /// Only governance can update contract params
21 | pub fn sudo_update_params(
22 | deps: DepsMut,
23 | _env: Env,
24 | param_info: ParamInfo,
25 | ) -> Result {
26 | let ParamInfo { name, owner } = param_info;
27 |
28 | let mut params = SUDO_PARAMS.load(deps.storage)?;
29 |
30 | params.name = name.unwrap_or(params.name);
31 | params.owner = owner.unwrap_or(params.owner);
32 |
33 | SUDO_PARAMS.save(deps.storage, ¶ms)?;
34 |
35 | Ok(Response::new().add_attribute("action", "update_params"))
36 | }
37 |
--------------------------------------------------------------------------------
/contracts/active-pool/src/tests.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | contract::{instantiate, NATIVE_JUNO_DENOM},
3 | ContractError,
4 | };
5 |
6 | use ultra_base::active_pool::{ExecuteMsg, InstantiateMsg, ParamsResponse, QueryMsg, SudoMsg};
7 |
8 | use cosmwasm_std::{Addr, Empty, Uint128};
9 | use cw_multi_test::{App, Contract, ContractWrapper, Executor};
10 |
11 | const SOME: &str = "someone";
12 | const OWNER: &str = "owner";
13 | const BO: &str = "borrower-operations";
14 | const TM: &str = "trove-manager";
15 | const SP: &str = "stability-pool";
16 | const DP: &str = "default-pool";
17 |
18 | fn active_pool_contract() -> Box> {
19 | let contract = ContractWrapper::new(
20 | crate::contract::execute,
21 | crate::contract::instantiate,
22 | crate::contract::query,
23 | )
24 | .with_sudo(crate::sudo::sudo);
25 | Box::new(contract)
26 | }
27 |
28 | fn instantiate_active_pool(app: &mut App, msg: InstantiateMsg) -> Addr {
29 | let code_id = app.store_code(active_pool_contract());
30 | app.instantiate_contract(
31 | code_id,
32 | Addr::unchecked(SOME),
33 | &msg,
34 | &[],
35 | "active pool",
36 | None,
37 | )
38 | .unwrap()
39 | }
40 |
41 | #[test]
42 | fn test_instantiate() {
43 | let mut app = App::default();
44 |
45 | let msg = InstantiateMsg {
46 | name: String::from("Active Pool"),
47 | owner: OWNER.to_string(),
48 | };
49 |
50 | let active_pool_addr = instantiate_active_pool(&mut app, msg);
51 |
52 | let response: ParamsResponse = app
53 | .wrap()
54 | .query_wasm_smart(&active_pool_addr, &QueryMsg::GetParams {})
55 | .unwrap();
56 |
57 | assert_eq!(response.owner, Addr::unchecked(OWNER));
58 |
59 | assert_eq!(response.name, "Active Pool");
60 | }
61 |
62 | #[test]
63 | fn test_set_addresses() {
64 | let mut app = App::default();
65 |
66 | let msg = InstantiateMsg {
67 | name: String::from("Active Pool"),
68 | owner: OWNER.to_string(),
69 | };
70 |
71 | let active_pool_addr = instantiate_active_pool(&mut app, msg);
72 |
73 | let set_addresses_msg = ExecuteMsg::SetAddresses {
74 | borrower_operations_address: BO.to_string(),
75 | default_pool_address: DP.to_string(),
76 | stability_pool_address: SP.to_string(),
77 | trove_manager_address: TM.to_string(),
78 | };
79 |
80 | let err: ContractError = app
81 | .execute_contract(
82 | Addr::unchecked(SOME),
83 | active_pool_addr.clone(),
84 | &set_addresses_msg,
85 | &[],
86 | )
87 | .unwrap_err()
88 | .downcast()
89 | .unwrap();
90 | assert_eq!(err, ContractError::UnauthorizedOwner {});
91 |
92 | app.execute_contract(
93 | Addr::unchecked(OWNER),
94 | active_pool_addr.clone(),
95 | &set_addresses_msg,
96 | &[],
97 | )
98 | .unwrap();
99 |
100 | let bo_address: Addr = app
101 | .wrap()
102 | .query_wasm_smart(
103 | active_pool_addr.clone(),
104 | &QueryMsg::GetBorrowerOperationsAddress {},
105 | )
106 | .unwrap();
107 | assert_eq!(bo_address, Addr::unchecked(BO));
108 |
109 | let tm_address: Addr = app
110 | .wrap()
111 | .query_wasm_smart(
112 | active_pool_addr.clone(),
113 | &QueryMsg::GetTroveManagerAddress {},
114 | )
115 | .unwrap();
116 | assert_eq!(tm_address, Addr::unchecked(TM));
117 |
118 | let sp_address: Addr = app
119 | .wrap()
120 | .query_wasm_smart(
121 | active_pool_addr.clone(),
122 | &QueryMsg::GetStabilityPoolAddress {},
123 | )
124 | .unwrap();
125 | assert_eq!(sp_address, Addr::unchecked(SP));
126 |
127 | let dp_address: Addr = app
128 | .wrap()
129 | .query_wasm_smart(
130 | active_pool_addr.clone(),
131 | &QueryMsg::GetDefaultPoolAddress {},
132 | )
133 | .unwrap();
134 | assert_eq!(dp_address, Addr::unchecked(DP));
135 | }
136 |
137 | #[test]
138 | fn test_increase_decrease_ultra_debt() {
139 | let mut app = App::default();
140 | let msg = InstantiateMsg {
141 | name: String::from("Active Pool"),
142 | owner: OWNER.to_string(),
143 | };
144 |
145 | let active_pool_addr = instantiate_active_pool(&mut app, msg);
146 |
147 | let set_addresses_msg = ExecuteMsg::SetAddresses {
148 | borrower_operations_address: BO.to_string(),
149 | default_pool_address: DP.to_string(),
150 | stability_pool_address: SP.to_string(),
151 | trove_manager_address: TM.to_string(),
152 | };
153 |
154 | app.execute_contract(
155 | Addr::unchecked(OWNER),
156 | active_pool_addr.clone(),
157 | &set_addresses_msg,
158 | &[],
159 | )
160 | .unwrap();
161 |
162 | let increase_ultra_debt_msg = ExecuteMsg::IncreaseULTRADebt {
163 | amount: Uint128::new(100u128),
164 | };
165 |
166 | let decrease_ultra_debt_msg = ExecuteMsg::DecreaseULTRADebt {
167 | amount: Uint128::new(50u128),
168 | };
169 |
170 | let err: ContractError = app
171 | .execute_contract(
172 | Addr::unchecked(SOME),
173 | active_pool_addr.clone(),
174 | &increase_ultra_debt_msg,
175 | &[],
176 | )
177 | .unwrap_err()
178 | .downcast()
179 | .unwrap();
180 | assert_eq!(err, ContractError::CallerIsNeitherBONorTM {});
181 |
182 | app.execute_contract(
183 | Addr::unchecked(TM),
184 | active_pool_addr.clone(),
185 | &increase_ultra_debt_msg,
186 | &[],
187 | )
188 | .unwrap();
189 |
190 | let ultra_debt: Uint128 = app
191 | .wrap()
192 | .query_wasm_smart(active_pool_addr.clone(), &QueryMsg::GetULTRADebt {})
193 | .unwrap();
194 |
195 | assert_eq!(ultra_debt, Uint128::new(100u128));
196 |
197 | app.execute_contract(
198 | Addr::unchecked(TM),
199 | active_pool_addr.clone(),
200 | &decrease_ultra_debt_msg,
201 | &[],
202 | )
203 | .unwrap();
204 |
205 | let ultra_debt: Uint128 = app
206 | .wrap()
207 | .query_wasm_smart(active_pool_addr.clone(), &QueryMsg::GetULTRADebt {})
208 | .unwrap();
209 |
210 | assert_eq!(ultra_debt, Uint128::new(50u128));
211 | }
212 |
--------------------------------------------------------------------------------
/contracts/band-ibc-oracle/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "bandoracle"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [lib]
9 | crate-type = ["cdylib", "rlib"]
10 |
11 | [features]
12 | backtraces = ["cosmwasm-std/backtraces"]
13 | # use library feature to disable all init/handle/query exports
14 | library = []
15 |
16 | [dependencies]
17 | cw-utils = "0.13.4"
18 | cw2 = "0.13.4"
19 | cw0 = "0.10.3"
20 | cw20 = "0.13.4"
21 |
22 | cosmwasm-std = { version = "1.0.0", features = ["stargate"] }
23 | hex = "0.4.3"
24 | cw-storage-plus = "0.13.4"
25 | schemars = "0.8.1"
26 | serde = { version = "1.0.103", default-features = false, features = ["derive"] }
27 | thiserror = { version = "1.0.23" }
28 | [dev-dependencies]
29 | cosmwasm-schema = { version = "0.16.3" }
30 |
--------------------------------------------------------------------------------
/contracts/band-ibc-oracle/README.md:
--------------------------------------------------------------------------------
1 | # band-ibc-oracle
2 | Queries oracle data over ibc
3 |
--------------------------------------------------------------------------------
/contracts/band-ibc-oracle/examples/schema.rs:
--------------------------------------------------------------------------------
1 | use std::env::current_dir;
2 | use std::fs::create_dir_all;
3 |
4 | use cosmwasm_schema::{export_schema, remove_schemas, schema_for};
5 |
6 | use bandoracle::msg::{
7 | ChannelResponse, ExecuteMsg, InitMsg, ListChannelsResponse, PortResponse, QueryMsg,
8 | };
9 |
10 | fn main() {
11 | let mut out_dir = current_dir().unwrap();
12 | out_dir.push("schema");
13 | create_dir_all(&out_dir).unwrap();
14 | remove_schemas(&out_dir).unwrap();
15 | export_schema(&schema_for!(InitMsg), &out_dir);
16 | export_schema(&schema_for!(ExecuteMsg), &out_dir);
17 | export_schema(&schema_for!(QueryMsg), &out_dir);
18 | export_schema(&schema_for!(ChannelResponse), &out_dir);
19 | export_schema(&schema_for!(ListChannelsResponse), &out_dir);
20 | export_schema(&schema_for!(PortResponse), &out_dir);
21 | }
22 |
--------------------------------------------------------------------------------
/contracts/band-ibc-oracle/schema/channel_response.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "title": "ChannelResponse",
4 | "type": "object",
5 | "required": [
6 | "balances",
7 | "info",
8 | "total_sent"
9 | ],
10 | "properties": {
11 | "balances": {
12 | "description": "How many tokens we currently have pending over this channel",
13 | "type": "array",
14 | "items": {
15 | "$ref": "#/definitions/Amount"
16 | }
17 | },
18 | "info": {
19 | "description": "Information on the channel's connection",
20 | "allOf": [
21 | {
22 | "$ref": "#/definitions/ChannelInfo"
23 | }
24 | ]
25 | },
26 | "total_sent": {
27 | "description": "The total number of tokens that have been sent over this channel (even if many have been returned, so balance is low)",
28 | "type": "array",
29 | "items": {
30 | "$ref": "#/definitions/Amount"
31 | }
32 | }
33 | },
34 | "definitions": {
35 | "Amount": {
36 | "oneOf": [
37 | {
38 | "type": "object",
39 | "required": [
40 | "native"
41 | ],
42 | "properties": {
43 | "native": {
44 | "$ref": "#/definitions/Coin"
45 | }
46 | },
47 | "additionalProperties": false
48 | },
49 | {
50 | "type": "object",
51 | "required": [
52 | "cw20"
53 | ],
54 | "properties": {
55 | "cw20": {
56 | "$ref": "#/definitions/Cw20Coin"
57 | }
58 | },
59 | "additionalProperties": false
60 | }
61 | ]
62 | },
63 | "ChannelInfo": {
64 | "type": "object",
65 | "required": [
66 | "connection_id",
67 | "counterparty_endpoint",
68 | "id"
69 | ],
70 | "properties": {
71 | "connection_id": {
72 | "description": "the connection this exists on (you can use to query client/consensus info)",
73 | "type": "string"
74 | },
75 | "counterparty_endpoint": {
76 | "description": "the remote channel/port we connect to",
77 | "allOf": [
78 | {
79 | "$ref": "#/definitions/IbcEndpoint"
80 | }
81 | ]
82 | },
83 | "id": {
84 | "description": "id of this channel",
85 | "type": "string"
86 | }
87 | }
88 | },
89 | "Coin": {
90 | "type": "object",
91 | "required": [
92 | "amount",
93 | "denom"
94 | ],
95 | "properties": {
96 | "amount": {
97 | "$ref": "#/definitions/Uint128"
98 | },
99 | "denom": {
100 | "type": "string"
101 | }
102 | }
103 | },
104 | "Cw20Coin": {
105 | "type": "object",
106 | "required": [
107 | "address",
108 | "amount"
109 | ],
110 | "properties": {
111 | "address": {
112 | "type": "string"
113 | },
114 | "amount": {
115 | "$ref": "#/definitions/Uint128"
116 | }
117 | }
118 | },
119 | "IbcEndpoint": {
120 | "type": "object",
121 | "required": [
122 | "channel_id",
123 | "port_id"
124 | ],
125 | "properties": {
126 | "channel_id": {
127 | "type": "string"
128 | },
129 | "port_id": {
130 | "type": "string"
131 | }
132 | }
133 | },
134 | "Uint128": {
135 | "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
136 | "type": "string"
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/contracts/band-ibc-oracle/schema/execute_msg.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "title": "ExecuteMsg",
4 | "oneOf": [
5 | {
6 | "description": "This accepts a properly-encoded ReceiveMsg from a cw20 contract This allow ibc request bandd",
7 | "type": "object",
8 | "required": [
9 | "oracle_request"
10 | ],
11 | "properties": {
12 | "oracle_request": {
13 | "$ref": "#/definitions/OracleMsg"
14 | }
15 | },
16 | "additionalProperties": false
17 | }
18 | ],
19 | "definitions": {
20 | "OracleMsg": {
21 | "type": "object",
22 | "required": [
23 | "ask_count",
24 | "call_data",
25 | "channel",
26 | "client_id",
27 | "denom",
28 | "min_count",
29 | "oracle_script_id"
30 | ],
31 | "properties": {
32 | "ask_count": {
33 | "type": "integer",
34 | "format": "int64"
35 | },
36 | "call_data": {
37 | "type": "string"
38 | },
39 | "channel": {
40 | "description": "The local channel to send the packets on",
41 | "type": "string"
42 | },
43 | "client_id": {
44 | "type": "string"
45 | },
46 | "denom": {
47 | "type": "string"
48 | },
49 | "min_count": {
50 | "type": "integer",
51 | "format": "int64"
52 | },
53 | "oracle_script_id": {
54 | "type": "integer",
55 | "format": "int64"
56 | },
57 | "timeout": {
58 | "description": "How long the packet lives in seconds. If not specified, use default_timeout",
59 | "type": [
60 | "integer",
61 | "null"
62 | ],
63 | "format": "uint64",
64 | "minimum": 0.0
65 | }
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/contracts/band-ibc-oracle/schema/init_msg.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "title": "InitMsg",
4 | "type": "object",
5 | "required": [
6 | "default_timeout"
7 | ],
8 | "properties": {
9 | "default_timeout": {
10 | "description": "Default timeout for ibc packets, specified in seconds",
11 | "type": "integer",
12 | "format": "uint64",
13 | "minimum": 0.0
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/contracts/band-ibc-oracle/schema/list_channels_response.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "title": "ListChannelsResponse",
4 | "type": "object",
5 | "required": [
6 | "channels"
7 | ],
8 | "properties": {
9 | "channels": {
10 | "type": "array",
11 | "items": {
12 | "$ref": "#/definitions/ChannelInfo"
13 | }
14 | }
15 | },
16 | "definitions": {
17 | "ChannelInfo": {
18 | "type": "object",
19 | "required": [
20 | "connection_id",
21 | "counterparty_endpoint",
22 | "id"
23 | ],
24 | "properties": {
25 | "connection_id": {
26 | "description": "the connection this exists on (you can use to query client/consensus info)",
27 | "type": "string"
28 | },
29 | "counterparty_endpoint": {
30 | "description": "the remote channel/port we connect to",
31 | "allOf": [
32 | {
33 | "$ref": "#/definitions/IbcEndpoint"
34 | }
35 | ]
36 | },
37 | "id": {
38 | "description": "id of this channel",
39 | "type": "string"
40 | }
41 | }
42 | },
43 | "IbcEndpoint": {
44 | "type": "object",
45 | "required": [
46 | "channel_id",
47 | "port_id"
48 | ],
49 | "properties": {
50 | "channel_id": {
51 | "type": "string"
52 | },
53 | "port_id": {
54 | "type": "string"
55 | }
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/contracts/band-ibc-oracle/schema/port_response.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "title": "PortResponse",
4 | "type": "object",
5 | "required": [
6 | "port_id"
7 | ],
8 | "properties": {
9 | "port_id": {
10 | "type": "string"
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/contracts/band-ibc-oracle/schema/query_msg.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "title": "QueryMsg",
4 | "oneOf": [
5 | {
6 | "description": "Return the port ID bound by this contract. Returns PortResponse",
7 | "type": "object",
8 | "required": [
9 | "port"
10 | ],
11 | "properties": {
12 | "port": {
13 | "type": "object"
14 | }
15 | },
16 | "additionalProperties": false
17 | },
18 | {
19 | "description": "Show all channels we have connected to. Return type is ListChannelsResponse.",
20 | "type": "object",
21 | "required": [
22 | "list_channels"
23 | ],
24 | "properties": {
25 | "list_channels": {
26 | "type": "object"
27 | }
28 | },
29 | "additionalProperties": false
30 | },
31 | {
32 | "description": "Returns the details of the name channel, error if not created. Return type: ChannelResponse.",
33 | "type": "object",
34 | "required": [
35 | "channel"
36 | ],
37 | "properties": {
38 | "channel": {
39 | "type": "object",
40 | "required": [
41 | "id"
42 | ],
43 | "properties": {
44 | "id": {
45 | "type": "string"
46 | }
47 | }
48 | }
49 | },
50 | "additionalProperties": false
51 | }
52 | ]
53 | }
54 |
--------------------------------------------------------------------------------
/contracts/band-ibc-oracle/src/contract.rs:
--------------------------------------------------------------------------------
1 | #[cfg(not(feature = "library"))]
2 | use cosmwasm_std::entry_point;
3 | use cosmwasm_std::{
4 | to_binary, Binary, Deps, DepsMut, Env, IbcMsg, IbcQuery, MessageInfo, Order, PortIdResponse,
5 | Response, StdResult,
6 | };
7 |
8 | use cw2::{get_contract_version, set_contract_version};
9 |
10 | use crate::error::ContractError;
11 | use crate::ibc::OracleRequestPacket;
12 | use crate::msg::{
13 | ChannelResponse, ExecuteMsg, InitMsg, ListChannelsResponse, MigrateMsg, OracleMsg,
14 | PortResponse, QueryMsg,
15 | };
16 | use crate::state::{Config, CHANNEL_INFO, CONFIG};
17 |
18 | // version info for migration info
19 | const CONTRACT_NAME: &str = "band-protocol";
20 | const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
21 |
22 | #[cfg_attr(not(feature = "library"), entry_point)]
23 | pub fn instantiate(
24 | deps: DepsMut,
25 | _env: Env,
26 | _info: MessageInfo,
27 | msg: InitMsg,
28 | ) -> Result {
29 | set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
30 | let cfg = Config {
31 | default_timeout: msg.default_timeout,
32 | };
33 | CONFIG.save(deps.storage, &cfg)?;
34 | Ok(Response::default())
35 | }
36 |
37 | #[cfg_attr(not(feature = "library"), entry_point)]
38 | pub fn execute(
39 | deps: DepsMut,
40 | env: Env,
41 | _info: MessageInfo,
42 | msg: ExecuteMsg,
43 | ) -> Result {
44 | match msg {
45 | // ExecuteMsg::ReceiveOracle(msg) => execute_receive(deps, env, info, msg),
46 | ExecuteMsg::OracleRequest(msg) => execute_oracle(deps, env, msg),
47 | }
48 | }
49 |
50 | pub fn execute_oracle(deps: DepsMut, env: Env, msg: OracleMsg) -> Result {
51 | // ensure the requested channel is registered
52 | // FIXME: add a .has method to map to make this faster
53 | if CHANNEL_INFO.may_load(deps.storage, &msg.channel)?.is_none() {
54 | return Err(ContractError::NoSuchChannel { id: msg.channel });
55 | }
56 |
57 | // delta from user is in seconds
58 | let timeout_delta = match msg.timeout {
59 | Some(t) => t,
60 | None => CONFIG.load(deps.storage)?.default_timeout,
61 | };
62 | // timeout is in nanoseconds
63 | let timeout = env.block.time.plus_seconds(timeout_delta);
64 | let calldata = hex::decode(msg.call_data).expect("Decoding failed");
65 |
66 | // build band packet
67 | let packet = OracleRequestPacket::new(
68 | msg.client_id,
69 | msg.oracle_script_id,
70 | calldata,
71 | msg.ask_count,
72 | msg.min_count,
73 | msg.denom,
74 | 250000,
75 | 50000,
76 | 300000,
77 | );
78 | packet.validate()?;
79 |
80 | // prepare message
81 | let msg = IbcMsg::SendPacket {
82 | channel_id: msg.channel,
83 | data: to_binary(&packet)?,
84 | timeout: timeout.into(),
85 | };
86 |
87 | // Note: we update local state when we get ack - do not count this transfer towards anything until acked
88 | // similar event messages like ibctransfer module
89 |
90 | // send response
91 | let res = Response::new().add_message(msg);
92 | Ok(res)
93 | }
94 |
95 | #[cfg_attr(not(feature = "library"), entry_point)]
96 | pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result {
97 | let version = get_contract_version(deps.storage)?;
98 | if version.contract != CONTRACT_NAME {
99 | return Err(ContractError::CannotMigrate {
100 | previous_contract: version.contract,
101 | });
102 | }
103 | Ok(Response::default())
104 | }
105 |
106 | #[cfg_attr(not(feature = "library"), entry_point)]
107 | pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult {
108 | match msg {
109 | QueryMsg::Port {} => to_binary(&query_port(deps)?),
110 | QueryMsg::ListChannels {} => to_binary(&query_list(deps)?),
111 | QueryMsg::Channel { id } => to_binary(&query_channel(deps, id)?),
112 | }
113 | }
114 |
115 | fn query_port(deps: Deps) -> StdResult {
116 | let query = IbcQuery::PortId {}.into();
117 | let PortIdResponse { port_id } = deps.querier.query(&query)?;
118 | Ok(PortResponse { port_id })
119 | }
120 |
121 | fn query_list(deps: Deps) -> StdResult {
122 | let channels: StdResult> = CHANNEL_INFO
123 | .range(deps.storage, None, None, Order::Ascending)
124 | .map(|r| r.map(|(_, v)| v))
125 | .collect();
126 | Ok(ListChannelsResponse {
127 | channels: channels?,
128 | })
129 | }
130 |
131 | // make public for ibc tests
132 | pub fn query_channel(deps: Deps, id: String) -> StdResult {
133 | let info = CHANNEL_INFO.load(deps.storage, &id)?;
134 | // this returns Vec<(outstanding, total)>
135 | Ok(ChannelResponse { info })
136 | }
137 |
--------------------------------------------------------------------------------
/contracts/band-ibc-oracle/src/error.rs:
--------------------------------------------------------------------------------
1 | use std::string::FromUtf8Error;
2 | use thiserror::Error;
3 |
4 | use cosmwasm_std::StdError;
5 |
6 | /// Never is a placeholder to ensure we don't return any errors
7 | #[derive(Error, Debug)]
8 | pub enum Never {}
9 |
10 | #[derive(Error, Debug, PartialEq)]
11 | pub enum ContractError {
12 | #[error("{0}")]
13 | Std(#[from] StdError),
14 |
15 | #[error("Channel doesn't exist: {id}")]
16 | NoSuchChannel { id: String },
17 |
18 | #[error("Only supports channel with ibc version bandchan-1, got {version}")]
19 | InvalidIbcVersion { version: String },
20 |
21 | #[error("Only supports unordered channel")]
22 | OnlyOrderedChannel {},
23 |
24 | #[error("Only accepts tokens that originate on this chain, not native tokens of remote chain")]
25 | NoForeignTokens {},
26 |
27 | #[error("Parsed port from denom ({port}) doesn't match packet")]
28 | FromOtherPort { port: String },
29 |
30 | #[error("Parsed channel from denom ({channel}) doesn't match packet")]
31 | FromOtherChannel { channel: String },
32 |
33 | #[error("Cannot migrate from different contract type: {previous_contract}")]
34 | CannotMigrate { previous_contract: String },
35 |
36 | #[error("Got a submessage reply with unknown id: {id}")]
37 | UnknownReplyId { id: u64 },
38 | }
39 |
40 | impl From for ContractError {
41 | fn from(_: FromUtf8Error) -> Self {
42 | ContractError::Std(StdError::invalid_utf8("parsing denom key"))
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/contracts/band-ibc-oracle/src/ibc.rs:
--------------------------------------------------------------------------------
1 | use schemars::JsonSchema;
2 | use serde::{Deserialize, Serialize};
3 |
4 | use cosmwasm_std::{
5 | attr, coins, entry_point, from_binary, to_binary, Binary, Coin, DepsMut, Env, IbcBasicResponse,
6 | IbcChannel, IbcChannelCloseMsg, IbcChannelConnectMsg, IbcChannelOpenMsg, IbcOrder, IbcPacket,
7 | IbcPacketAckMsg, IbcPacketReceiveMsg, IbcPacketTimeoutMsg, IbcReceiveResponse, Reply, Response,
8 | SubMsgResult,
9 | };
10 |
11 | use crate::error::{ContractError, Never};
12 | use crate::state::{ChannelInfo, CHANNEL_INFO};
13 |
14 | pub const IBC_VERSION: &str = "bandchain-1";
15 | pub const IBC_ORDERING: IbcOrder = IbcOrder::Unordered;
16 |
17 | /// The format for sending an ics20 packet.
18 | /// Proto defined here: https://github.com/cosmos/cosmos-sdk/blob/v0.42.0/proto/ibc/applications/transfer/v1/transfer.proto#L11-L20
19 | /// This is compatible with the JSON serialization
20 |
21 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug, Default)]
22 | pub struct OracleRequestPacket {
23 | // the unique identifier of this oracle request, as specified by the client. This same unique ID will be sent back to the requester with the oracle response.
24 | pub client_id: String,
25 | // The unique identifier number assigned to the oracle script when it was first registered on Bandchain
26 | pub oracle_script_id: i64,
27 | // The data passed over to the oracle script for the script to use during its execution
28 | pub calldata: Vec,
29 | // The number of validators that are requested to respond to this request
30 | pub ask_count: i64,
31 | // The minimum number of validators necessary for the request to proceed to the execution phase
32 | pub min_count: i64,
33 | // FeeLimit is the maximum tokens that will be paid to all data source
34 | pub fee_limit: Vec,
35 | // Prepare Gas is amount of gas to pay to prepare raw requests
36 | pub prepare_gas: i64,
37 | // Execute Gas is amount of gas to reserve for executing
38 | pub execute_gas: i64,
39 | }
40 |
41 | impl OracleRequestPacket {
42 | pub fn new(
43 | client_id: String,
44 | oracle_script_id: i64,
45 | calldata: Vec,
46 | ask_count: i64,
47 | min_count: i64,
48 | denom: String,
49 | amount: u128,
50 | prepare_gas: i64,
51 | execute_gas: i64,
52 | ) -> Self {
53 | let fee_limit = coins(amount, denom);
54 | OracleRequestPacket {
55 | client_id,
56 | oracle_script_id,
57 | calldata,
58 | ask_count,
59 | min_count,
60 | fee_limit,
61 | prepare_gas,
62 | execute_gas,
63 | }
64 | }
65 |
66 | pub fn validate(&self) -> Result<(), ContractError> {
67 | Ok(())
68 | }
69 | }
70 |
71 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug, Default)]
72 | pub struct OracleResponsePacket {
73 | // ClientID is the unique identifier matched with that of the oracle request
74 | // packet.
75 | pub client_id: String,
76 | // RequestID is BandChain's unique identifier for this oracle request.
77 | pub request_id: u64,
78 | // AnsCount is the number of validators among to the asked validators that
79 | // actually responded to this oracle request prior to this oracle request
80 | // being resolved.
81 | pub ans_count: u64,
82 | // RequestTime is the UNIX epoch time at which the request was sent to
83 | // BandChain.
84 | pub request_time: i64,
85 | // ResolveTime is the UNIX epoch time at which the request was resolved to the
86 | // final result.
87 | pub resolve_time: i64,
88 | // ResolveStatus is the status of this oracle request, which can be OK,
89 | // FAILURE, or EXPIRED.
90 | pub resolve_status: i32,
91 | // Result is the final aggregated value encoded in OBI format. Only available
92 | // if status if OK.
93 | pub result: Vec,
94 | }
95 |
96 | impl OracleResponsePacket {
97 | pub fn validate(&self) -> Result<(), ContractError> {
98 | Ok(())
99 | }
100 | }
101 |
102 | /// This is a generic ICS acknowledgement format.
103 | /// Proto defined here: https://github.com/cosmos/cosmos-sdk/blob/v0.42.0/proto/ibc/core/channel/v1/channel.proto#L141-L147
104 | /// This is compatible with the JSON serialization
105 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
106 | #[serde(rename_all = "snake_case")]
107 | pub enum Ics20Ack {
108 | Result(Binary),
109 | Error(String),
110 | }
111 |
112 | // create a serialized success message
113 | fn _ack_success() -> Binary {
114 | let res = Ics20Ack::Result(b"1".into());
115 | to_binary(&res).unwrap()
116 | }
117 |
118 | // create a serialized error message
119 | fn ack_fail(err: String) -> Binary {
120 | let res = Ics20Ack::Error(err);
121 | to_binary(&res).unwrap()
122 | }
123 |
124 | const SEND_TOKEN_ID: u64 = 1337;
125 |
126 | #[cfg_attr(not(feature = "library"), entry_point)]
127 | pub fn reply(_deps: DepsMut, _env: Env, reply: Reply) -> Result {
128 | if reply.id != SEND_TOKEN_ID {
129 | return Err(ContractError::UnknownReplyId { id: reply.id });
130 | }
131 | let res = match reply.result {
132 | SubMsgResult::Ok(_) => Response::new(),
133 | SubMsgResult::Err(err) => {
134 | // encode an acknowledgement error
135 | Response::new().set_data(ack_fail(err))
136 | }
137 | };
138 | Ok(res)
139 | }
140 |
141 | #[cfg_attr(not(feature = "library"), entry_point)]
142 | /// enforces ordering and versioning constraints
143 | pub fn ibc_channel_open(
144 | _deps: DepsMut,
145 | _env: Env,
146 | msg: IbcChannelOpenMsg,
147 | ) -> Result<(), ContractError> {
148 | enforce_order_and_version(msg.channel(), msg.counterparty_version())?;
149 | Ok(())
150 | }
151 |
152 | #[cfg_attr(not(feature = "library"), entry_point)]
153 | /// record the channel in CHANNEL_INFO
154 | pub fn ibc_channel_connect(
155 | deps: DepsMut,
156 | _env: Env,
157 | msg: IbcChannelConnectMsg,
158 | ) -> Result {
159 | // we need to check the counter party version in try and ack (sometimes here)
160 | enforce_order_and_version(msg.channel(), msg.counterparty_version())?;
161 |
162 | let channel: IbcChannel = msg.into();
163 | let info = ChannelInfo {
164 | id: channel.endpoint.channel_id,
165 | counterparty_endpoint: channel.counterparty_endpoint,
166 | connection_id: channel.connection_id,
167 | };
168 | CHANNEL_INFO.save(deps.storage, &info.id, &info)?;
169 |
170 | Ok(IbcBasicResponse::default())
171 | }
172 |
173 | fn enforce_order_and_version(
174 | channel: &IbcChannel,
175 | counterparty_version: Option<&str>,
176 | ) -> Result<(), ContractError> {
177 | if channel.version != IBC_VERSION {
178 | return Err(ContractError::InvalidIbcVersion {
179 | version: channel.version.clone(),
180 | });
181 | }
182 | if let Some(version) = counterparty_version {
183 | if version != IBC_VERSION {
184 | return Err(ContractError::InvalidIbcVersion {
185 | version: version.to_string(),
186 | });
187 | }
188 | }
189 | if channel.order != IBC_ORDERING {
190 | return Err(ContractError::OnlyOrderedChannel {});
191 | }
192 | Ok(())
193 | }
194 |
195 | #[cfg_attr(not(feature = "library"), entry_point)]
196 | pub fn ibc_channel_close(
197 | _deps: DepsMut,
198 | _env: Env,
199 | _channel: IbcChannelCloseMsg,
200 | ) -> Result {
201 | // TODO: what to do here?
202 | // we will have locked funds that need to be returned somehow
203 | unimplemented!();
204 | }
205 |
206 | #[cfg_attr(not(feature = "library"), entry_point)]
207 | /// Check to see if we have any balance here
208 | /// We should not return an error if possible, but rather an acknowledgement of failure
209 | pub fn ibc_packet_receive(
210 | _deps: DepsMut,
211 | _env: Env,
212 | msg: IbcPacketReceiveMsg,
213 | ) -> Result {
214 | let packet = msg.packet;
215 | let res = match do_ibc_packet_receive(&packet) {
216 | Ok(msg) => {
217 | // build attributes first so we don't have to clone msg below
218 | // similar event messages like ibctransfer module
219 |
220 | // This cannot fail as we parse it in do_ibc_packet_receive. Best to pass the data somehow?
221 |
222 | let attributes = vec![
223 | attr("action", "receive"),
224 | attr("msg", msg),
225 | attr("status", "sucess"),
226 | ];
227 | IbcReceiveResponse::new().add_attributes(attributes)
228 | }
229 | Err(err) => IbcReceiveResponse::new()
230 | .set_ack(ack_fail(err.to_string()))
231 | .add_attributes(vec![
232 | attr("action", "receive"),
233 | attr("status", "false"),
234 | attr("error", err.to_string()),
235 | ]),
236 | };
237 |
238 | Ok(res)
239 | }
240 |
241 | // this does the work of ibc_packet_receive, we wrap it to turn errors into acknowledgements
242 | fn do_ibc_packet_receive(packet: &IbcPacket) -> Result {
243 | let msg = packet.data.to_string();
244 | Ok(msg)
245 | }
246 |
247 | #[cfg_attr(not(feature = "library"), entry_point)]
248 | /// check if success or failure and update balance, or return funds
249 | pub fn ibc_packet_ack(
250 | deps: DepsMut,
251 | _env: Env,
252 | msg: IbcPacketAckMsg,
253 | ) -> Result {
254 | // TODO: trap error like in receive?
255 | let ics20msg: Ics20Ack = from_binary(&msg.acknowledgement.data)?;
256 | match ics20msg {
257 | Ics20Ack::Result(_) => on_packet_success(deps, msg.original_packet),
258 | Ics20Ack::Error(err) => on_packet_failure(deps, msg.original_packet, err),
259 | }
260 | }
261 |
262 | #[cfg_attr(not(feature = "library"), entry_point)]
263 | /// return fund to original sender (same as failure in ibc_packet_ack)
264 | pub fn ibc_packet_timeout(
265 | deps: DepsMut,
266 | _env: Env,
267 | msg: IbcPacketTimeoutMsg,
268 | ) -> Result {
269 | // TODO: trap error like in receive?
270 | let packet = msg.packet;
271 | on_packet_failure(deps, packet, "timeout".to_string())
272 | }
273 |
274 | // update the balance stored on this (channel, denom) index
275 | fn on_packet_success(_deps: DepsMut, packet: IbcPacket) -> Result {
276 | let msg: OracleRequestPacket = from_binary(&packet.data)?;
277 | // similar event messages like ibctransfer module
278 | let attributes = vec![
279 | attr("action", "acknowledge"),
280 | attr("receiver", &msg.client_id),
281 | ];
282 | Ok(IbcBasicResponse::new().add_attributes(attributes))
283 | }
284 |
285 | // return the tokens to sender
286 | fn on_packet_failure(
287 | _deps: DepsMut,
288 | packet: IbcPacket,
289 | _err: String,
290 | ) -> Result {
291 | let _msg: OracleRequestPacket = from_binary(&packet.data)?;
292 | // similar event messages like ibctransfer module
293 | let attributes = vec![attr("action", "acknowledge"), attr("status", "fail")];
294 | Ok(IbcBasicResponse::new().add_attributes(attributes))
295 | }
296 |
--------------------------------------------------------------------------------
/contracts/band-ibc-oracle/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod contract;
2 | mod error;
3 | pub mod ibc;
4 | pub mod msg;
5 | pub mod state;
6 |
7 | pub use crate::error::ContractError;
8 |
--------------------------------------------------------------------------------
/contracts/band-ibc-oracle/src/msg.rs:
--------------------------------------------------------------------------------
1 | use schemars::JsonSchema;
2 | use serde::{Deserialize, Serialize};
3 |
4 | use crate::state::ChannelInfo;
5 |
6 | #[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)]
7 | pub struct InitMsg {
8 | pub default_timeout: u64,
9 | }
10 |
11 | #[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)]
12 | pub struct MigrateMsg {}
13 |
14 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
15 | #[serde(rename_all = "snake_case")]
16 | pub enum ExecuteMsg {
17 | /// This accepts a properly-encoded ReceiveMsg from a cw20 contract
18 | // ReceiveOracle(OracleReceiveMsg),
19 | ///This allow ibc request bandd
20 | OracleRequest(OracleMsg),
21 | }
22 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
23 | pub struct OracleMsg {
24 | /// The local channel to send the packets on
25 | pub channel: String,
26 | /// How long the packet lives in seconds. If not specified, use default_timeout
27 | pub timeout: Option,
28 | // the unique identifier of this oracle request, as specified by the client. This same unique ID will be sent back to the requester with the oracle response.
29 | pub client_id: String,
30 | // The unique identifier number assigned to the oracle script when it was first registered on Bandchain
31 | pub oracle_script_id: i64,
32 | // The data passed over to the oracle script for the script to use during its execution
33 | pub call_data: String,
34 | // The number of validators that are requested to respond to this request
35 | pub ask_count: i64,
36 | // The minimum number of validators necessary for the request to proceed to the execution phase
37 | pub min_count: i64,
38 |
39 | pub denom: String,
40 | }
41 |
42 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
43 | #[serde(rename_all = "snake_case")]
44 | pub enum QueryMsg {
45 | /// Return the port ID bound by this contract. Returns PortResponse
46 | Port {},
47 | /// Show all channels we have connected to. Return type is ListChannelsResponse.
48 | ListChannels {},
49 | /// Returns the details of the name channel, error if not created.
50 | /// Return type: ChannelResponse.
51 | Channel { id: String },
52 | }
53 |
54 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
55 | pub struct ListChannelsResponse {
56 | pub channels: Vec,
57 | }
58 |
59 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
60 | pub struct ChannelResponse {
61 | /// Information on the channel's connection
62 | pub info: ChannelInfo,
63 | }
64 |
65 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
66 | pub struct PortResponse {
67 | pub port_id: String,
68 | }
69 |
--------------------------------------------------------------------------------
/contracts/band-ibc-oracle/src/state.rs:
--------------------------------------------------------------------------------
1 | use schemars::JsonSchema;
2 | use serde::{Deserialize, Serialize};
3 |
4 | use cosmwasm_std::{IbcEndpoint, Uint128};
5 | use cw_storage_plus::{Item, Map};
6 |
7 | pub const CONFIG: Item = Item::new("ibcband_config");
8 |
9 | // static info on one channel that doesn't change
10 | pub const CHANNEL_INFO: Map<&str, ChannelInfo> = Map::new("channel_info");
11 |
12 | // indexed by (channel_id, denom) maintaining the balance of the channel in that currency
13 | pub const CHANNEL_STATE: Map<(&str, &str), ChannelState> = Map::new("channel_state");
14 |
15 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug, Default)]
16 | pub struct ChannelState {
17 | pub outstanding: Uint128,
18 | pub total_sent: Uint128,
19 | }
20 |
21 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug, Default)]
22 | pub struct Config {
23 | pub default_timeout: u64,
24 | }
25 |
26 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
27 | pub struct ChannelInfo {
28 | /// id of this channel
29 | pub id: String,
30 | /// the remote channel/port we connect to
31 | pub counterparty_endpoint: IbcEndpoint,
32 | /// the connection this exists on (you can use to query client/consensus info)
33 | pub connection_id: String,
34 | }
35 |
36 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
37 | pub struct Result {
38 | pub query_time: String,
39 | pub result: Vec,
40 | }
41 |
--------------------------------------------------------------------------------
/contracts/coll-surplus-pool/.cargo/config:
--------------------------------------------------------------------------------
1 | [alias]
2 | wasm = "build --release --target wasm32-unknown-unknown"
3 | wasm-debug = "build --target wasm32-unknown-unknown"
4 | schema = "run --example schema"
5 |
--------------------------------------------------------------------------------
/contracts/coll-surplus-pool/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "coll-surplus-pool"
3 | version = "0.1.0"
4 | authors = ["Chinh D.Nguyen "]
5 | edition = "2021"
6 |
7 | description = "The Coll Surplus Pool holds the JUNO surplus from Troves that have been fully redeemed from as well as from Troves with an ICR > MCR that were liquidated in Recovery Mode."
8 | repository = "https://github.com/notional-labs/UltraStableJuno"
9 |
10 | [lib]
11 | crate-type = ["cdylib", "rlib"]
12 |
13 | [features]
14 | backtraces = ["cosmwasm-std/backtraces"]
15 | # use library feature to disable all instantiate/execute/query exports
16 | library = []
17 |
18 | [dependencies]
19 | cw2 = { version = "0.13.4" }
20 | cosmwasm-std = { version = "1.0.0" }
21 | cw-storage-plus = { version = "0.13.4" }
22 | schemars = "0.8.1"
23 | serde = { version = "1.0.103", default-features = false, features = ["derive"] }
24 | thiserror = { version = "1.0.23" }
25 | ultra-base = { path = "../../packages/ultra-base", default-features = false }
26 |
27 | [dev-dependencies]
28 | cw-multi-test = { version = "0.13.4" }
29 |
30 |
31 |
--------------------------------------------------------------------------------
/contracts/coll-surplus-pool/README.md:
--------------------------------------------------------------------------------
1 | # Coll Surplus Pool contract
2 | The Coll Surplus Pool holds the JUNO surplus from Troves that have been fully redeemed from as well as from Troves with an ICR > MCR that were liquidated in Recovery Mode. Sends the surplus back to the owning borrower, when told to do so by `borrower-operations`.
--------------------------------------------------------------------------------
/contracts/coll-surplus-pool/src/contract.rs:
--------------------------------------------------------------------------------
1 | use std::vec;
2 |
3 | #[cfg(not(feature = "library"))]
4 | use cosmwasm_std::entry_point;
5 | use cosmwasm_std::{
6 | coin, to_binary, Addr, BankMsg, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError,
7 | StdResult, Storage, Uint128,
8 | };
9 |
10 | use cw2::set_contract_version;
11 |
12 | use crate::error::ContractError;
13 | use crate::state::{
14 | AddressesSet, SudoParams, TotalCollsInPool, ADDRESSES_SET, COLL_OF_ACCOUNT, SUDO_PARAMS,
15 | TOTAL_COLLS_IN_POOL,
16 | };
17 | use ultra_base::coll_surplus_pool::{ExecuteMsg, InstantiateMsg, ParamsResponse, QueryMsg};
18 |
19 | // version info for migration info
20 | const CONTRACT_NAME: &str = "crates.io:active-pool";
21 | const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
22 |
23 | pub const NATIVE_JUNO_DENOM: &str = "ujuno";
24 |
25 | #[cfg_attr(not(feature = "library"), entry_point)]
26 | pub fn instantiate(
27 | deps: DepsMut,
28 | _env: Env,
29 | _info: MessageInfo,
30 | msg: InstantiateMsg,
31 | ) -> Result {
32 | set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
33 |
34 | // store sudo params
35 | let sudo_params = SudoParams {
36 | name: msg.name,
37 | owner: deps.api.addr_validate(&msg.owner)?,
38 | };
39 |
40 | // initial assets in pool
41 | let assets_in_pool = TotalCollsInPool {
42 | juno: Uint128::zero(),
43 | };
44 |
45 | SUDO_PARAMS.save(deps.storage, &sudo_params)?;
46 | TOTAL_COLLS_IN_POOL.save(deps.storage, &assets_in_pool)?;
47 |
48 | Ok(Response::default())
49 | }
50 |
51 | #[cfg_attr(not(feature = "library"), entry_point)]
52 | pub fn execute(
53 | deps: DepsMut,
54 | env: Env,
55 | info: MessageInfo,
56 | msg: ExecuteMsg,
57 | ) -> Result {
58 | match msg {
59 | ExecuteMsg::AccountSurplus { account, amount } => {
60 | execute_account_surplus(deps, env, info, account, amount)
61 | }
62 | ExecuteMsg::ClaimColl { account } => execute_claim_coll(deps, env, info, account),
63 |
64 | ExecuteMsg::SetAddresses {
65 | borrower_operations_address,
66 | trove_manager_address,
67 | active_pool_address,
68 | } => execute_set_addresses(
69 | deps,
70 | env,
71 | info,
72 | borrower_operations_address,
73 | trove_manager_address,
74 | active_pool_address,
75 | ),
76 | }
77 | }
78 |
79 | pub fn execute_account_surplus(
80 | deps: DepsMut,
81 | _env: Env,
82 | info: MessageInfo,
83 | account: Addr,
84 | amount: Uint128,
85 | ) -> Result {
86 | only_tm(deps.storage, &info)?;
87 |
88 | let mut coll_of_account = COLL_OF_ACCOUNT.load(deps.storage, account.clone())?;
89 | coll_of_account += amount;
90 | COLL_OF_ACCOUNT.save(deps.storage, account.clone(), &coll_of_account)?;
91 |
92 | let res = Response::new()
93 | .add_attribute("action", "account_surplus")
94 | .add_attribute("account", account);
95 | Ok(res)
96 | }
97 |
98 | pub fn execute_claim_coll(
99 | deps: DepsMut,
100 | _env: Env,
101 | info: MessageInfo,
102 | account: Addr,
103 | ) -> Result {
104 | only_bo(deps.storage, &info)?;
105 |
106 | let mut coll_of_account = COLL_OF_ACCOUNT.load(deps.storage, account.clone())?;
107 | let mut total_colls_in_pool = TOTAL_COLLS_IN_POOL.load(deps.storage)?;
108 |
109 | if coll_of_account.is_zero() {
110 | return Err(ContractError::NoCollAvailableToClaim {});
111 | }
112 | let send_msg = BankMsg::Send {
113 | to_address: account.to_string(),
114 | amount: vec![coin(coll_of_account.u128(), NATIVE_JUNO_DENOM.to_string())],
115 | };
116 |
117 | total_colls_in_pool.juno = total_colls_in_pool
118 | .juno
119 | .checked_sub(coll_of_account)
120 | .map_err(StdError::overflow)?;
121 | coll_of_account = Uint128::zero();
122 |
123 | COLL_OF_ACCOUNT.save(deps.storage, account.clone(), &coll_of_account)?;
124 | TOTAL_COLLS_IN_POOL.save(deps.storage, &total_colls_in_pool)?;
125 |
126 | let res = Response::new()
127 | .add_message(send_msg)
128 | .add_attribute("action", "claim_coll")
129 | .add_attribute("account", account);
130 | Ok(res)
131 | }
132 |
133 | pub fn execute_set_addresses(
134 | deps: DepsMut,
135 | _env: Env,
136 | info: MessageInfo,
137 | borrower_operations_address: String,
138 | trove_manager_address: String,
139 | active_pool_address: String,
140 | ) -> Result {
141 | only_owner(deps.storage, &info)?;
142 |
143 | let new_addresses_set = AddressesSet {
144 | borrower_operations_address: deps.api.addr_validate(&borrower_operations_address)?,
145 | trove_manager_address: deps.api.addr_validate(&trove_manager_address)?,
146 | active_pool_address: deps.api.addr_validate(&active_pool_address)?,
147 | };
148 |
149 | ADDRESSES_SET.save(deps.storage, &new_addresses_set)?;
150 | let res = Response::new()
151 | .add_attribute("action", "set_addresses")
152 | .add_attribute("borrower_operations_address", borrower_operations_address)
153 | .add_attribute("trove_manager_address", trove_manager_address)
154 | .add_attribute("stability_pool_address", active_pool_address);
155 | Ok(res)
156 | }
157 |
158 | /// Checks to enfore only borrower operations can call
159 | fn only_bo(store: &dyn Storage, info: &MessageInfo) -> Result {
160 | let addresses_set = ADDRESSES_SET.load(store)?;
161 | if addresses_set.borrower_operations_address != info.sender.as_ref() {
162 | return Err(ContractError::CallerIsNotBO {});
163 | }
164 | Ok(info.sender.clone())
165 | }
166 | /// Checks to enfore only trove manager can call
167 | fn only_tm(store: &dyn Storage, info: &MessageInfo) -> Result {
168 | let addresses_set = ADDRESSES_SET.load(store)?;
169 | if addresses_set.trove_manager_address != info.sender.as_ref() {
170 | return Err(ContractError::CallerIsNotTM {});
171 | }
172 | Ok(info.sender.clone())
173 | }
174 | /// Checks to enfore only active pool can call
175 | fn only_ap(store: &dyn Storage, info: &MessageInfo) -> Result {
176 | let addresses_set = ADDRESSES_SET.load(store)?;
177 | if addresses_set.trove_manager_address != info.sender.as_ref() {
178 | return Err(ContractError::CallerIsNotTM {});
179 | }
180 | Ok(info.sender.clone())
181 | }
182 | /// Checks to enfore only owner can call
183 | fn only_owner(store: &dyn Storage, info: &MessageInfo) -> Result {
184 | let params = SUDO_PARAMS.load(store)?;
185 | if params.owner != info.sender.as_ref() {
186 | return Err(ContractError::UnauthorizedOwner {});
187 | }
188 | Ok(info.sender.clone())
189 | }
190 |
191 | #[cfg_attr(not(feature = "library"), entry_point)]
192 | pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult {
193 | match msg {
194 | QueryMsg::GetParams {} => to_binary(&query_params(deps)?),
195 | QueryMsg::GetJUNO {} => to_binary(&query_juno_state(deps)?),
196 | QueryMsg::GetCollateral { account } => to_binary(&query_coll_of_account(deps, account)?),
197 | QueryMsg::GetBorrowerOperationsAddress {} => {
198 | to_binary(&query_borrower_operations_address(deps)?)
199 | }
200 | QueryMsg::GetActivePoolAddress {} => to_binary(&query_active_pool_address(deps)?),
201 | QueryMsg::GetTroveManagerAddress {} => to_binary(&query_trove_manager_address(deps)?),
202 | }
203 | }
204 |
205 | pub fn query_juno_state(deps: Deps) -> StdResult {
206 | let info = TOTAL_COLLS_IN_POOL.load(deps.storage)?;
207 | let res = info.juno;
208 | Ok(res)
209 | }
210 |
211 | pub fn query_coll_of_account(deps: Deps, account: Addr) -> StdResult {
212 | let info = COLL_OF_ACCOUNT.load(deps.storage, account)?;
213 | Ok(info)
214 | }
215 |
216 | pub fn query_params(deps: Deps) -> StdResult {
217 | let info = SUDO_PARAMS.load(deps.storage)?;
218 | let res = ParamsResponse {
219 | name: info.name,
220 | owner: info.owner,
221 | };
222 | Ok(res)
223 | }
224 |
225 | pub fn query_borrower_operations_address(deps: Deps) -> StdResult {
226 | let addresses_set = ADDRESSES_SET.load(deps.storage)?;
227 | let borrower_operations_address = addresses_set.borrower_operations_address;
228 | Ok(borrower_operations_address)
229 | }
230 |
231 | pub fn query_active_pool_address(deps: Deps) -> StdResult {
232 | let addresses_set = ADDRESSES_SET.load(deps.storage)?;
233 | let active_pool_address = addresses_set.active_pool_address;
234 | Ok(active_pool_address)
235 | }
236 |
237 | pub fn query_trove_manager_address(deps: Deps) -> StdResult {
238 | let addresses_set = ADDRESSES_SET.load(deps.storage)?;
239 | let trove_manager_address = addresses_set.trove_manager_address;
240 | Ok(trove_manager_address)
241 | }
242 |
--------------------------------------------------------------------------------
/contracts/coll-surplus-pool/src/error.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::StdError;
2 | use thiserror::Error;
3 |
4 | #[derive(Error, Debug, PartialEq)]
5 | pub enum ContractError {
6 | #[error("{0}")]
7 | Std(#[from] StdError),
8 |
9 | #[error("UnauthorizedOwner")]
10 | UnauthorizedOwner {},
11 |
12 | #[error("CollSurplusPool: Caller is not Borrower Operations")]
13 | CallerIsNotBO {},
14 |
15 | #[error("CollSurplusPool: Caller is not TroveManager")]
16 | CallerIsNotTM {},
17 |
18 | #[error("CollSurplusPool: Caller is not Active Pool")]
19 | CallerIsNotAP {},
20 |
21 | #[error("CollSurplusPool: No collateral available to claim")]
22 | NoCollAvailableToClaim {},
23 | }
24 |
--------------------------------------------------------------------------------
/contracts/coll-surplus-pool/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod contract;
2 | mod error;
3 | pub mod state;
4 | pub mod sudo;
5 |
6 | pub use crate::error::ContractError;
7 |
--------------------------------------------------------------------------------
/contracts/coll-surplus-pool/src/state.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::{Addr, Uint128};
2 | use cw_storage_plus::{Item, Map};
3 | use schemars::JsonSchema;
4 | use serde::{Deserialize, Serialize};
5 |
6 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
7 | pub struct AddressesSet {
8 | pub borrower_operations_address: Addr,
9 | pub trove_manager_address: Addr,
10 | pub active_pool_address: Addr,
11 | }
12 |
13 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
14 | pub struct TotalCollsInPool {
15 | pub juno: Uint128,
16 | }
17 |
18 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
19 | pub struct SudoParams {
20 | pub name: String,
21 | pub owner: Addr,
22 | }
23 |
24 | pub const SUDO_PARAMS: Item = Item::new("sudo-params");
25 | pub const ADDRESSES_SET: Item = Item::new("addresses_set");
26 | pub const TOTAL_COLLS_IN_POOL: Item = Item::new("total_colls_in_pool");
27 | pub const COLL_OF_ACCOUNT: Map = Map::new("coll-of-account");
28 |
--------------------------------------------------------------------------------
/contracts/coll-surplus-pool/src/sudo.rs:
--------------------------------------------------------------------------------
1 | use crate::error::ContractError;
2 | use crate::state::SUDO_PARAMS;
3 | use cosmwasm_std::{entry_point, Addr, DepsMut, Env, Response};
4 | use ultra_base::active_pool::SudoMsg;
5 |
6 | pub struct ParamInfo {
7 | name: Option,
8 | owner: Option,
9 | }
10 |
11 | #[cfg_attr(not(feature = "library"), entry_point)]
12 | pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> Result {
13 | match msg {
14 | SudoMsg::UpdateParams { name, owner } => {
15 | sudo_update_params(deps, env, ParamInfo { name, owner })
16 | }
17 | }
18 | }
19 |
20 | /// Only governance can update contract params
21 | pub fn sudo_update_params(
22 | deps: DepsMut,
23 | _env: Env,
24 | param_info: ParamInfo,
25 | ) -> Result {
26 | let ParamInfo { name, owner } = param_info;
27 |
28 | let mut params = SUDO_PARAMS.load(deps.storage)?;
29 |
30 | params.name = name.unwrap_or(params.name);
31 | params.owner = owner.unwrap_or(params.owner);
32 |
33 | SUDO_PARAMS.save(deps.storage, ¶ms)?;
34 |
35 | Ok(Response::new().add_attribute("action", "update_params"))
36 | }
37 |
--------------------------------------------------------------------------------
/contracts/default-pool/.cargo/config:
--------------------------------------------------------------------------------
1 | [alias]
2 | wasm = "build --release --target wasm32-unknown-unknown"
3 | wasm-debug = "build --target wasm32-unknown-unknown"
4 | schema = "run --example schema"
5 |
--------------------------------------------------------------------------------
/contracts/default-pool/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "default-pool"
3 | version = "0.1.0"
4 | authors = ["Chinh D.Nguyen "]
5 | edition = "2021"
6 |
7 | description = "The Default Pool holds the JUNO and ULTRA debt (but not ULTRA tokens) from liquidations that have been redistributed to active troves but not yet applied, i.e. not yet recorded on a recipient active trove's struct."
8 | repository = "https://github.com/notional-labs/UltraStableJuno"
9 |
10 | [lib]
11 | crate-type = ["cdylib", "rlib"]
12 |
13 | [features]
14 | backtraces = ["cosmwasm-std/backtraces"]
15 | # use library feature to disable all instantiate/execute/query exports
16 | library = []
17 |
18 | [dependencies]
19 | cw2 = { version = "0.13.4" }
20 | cosmwasm-std = { version = "1.0.0" }
21 | cw-storage-plus = { version = "0.13.4" }
22 | schemars = "0.8.1"
23 | serde = { version = "1.0.103", default-features = false, features = ["derive"] }
24 | thiserror = { version = "1.0.23" }
25 | ultra-base = { path = "../../packages/ultra-base", default-features = false }
26 |
27 |
28 |
--------------------------------------------------------------------------------
/contracts/default-pool/README.md:
--------------------------------------------------------------------------------
1 | # Default Pool
2 | The Default Pool holds the JUNO and ULTRA debt (but not ULTRA tokens) from liquidations that have been redistributed to active troves but not yet "applied", i.e. not yet recorded on a recipient active trove's struct.
3 | When a trove makes an operation that applies its pending JUNO and ULTRA debt, its pending JUNO and ULTRA debt is moved from the Default Pool to the Active Pool.
--------------------------------------------------------------------------------
/contracts/default-pool/src/contract.rs:
--------------------------------------------------------------------------------
1 | use std::vec;
2 |
3 | #[cfg(not(feature = "library"))]
4 | use cosmwasm_std::entry_point;
5 | use cosmwasm_std::{
6 | coin, to_binary, Addr, BankMsg, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError,
7 | StdResult, Storage, Uint128,
8 | };
9 |
10 | use cw2::set_contract_version;
11 |
12 | use crate::error::ContractError;
13 | use crate::state::{
14 | AddressesSet, AssetsInPool, SudoParams, ADDRESSES_SET, ASSETS_IN_POOL, SUDO_PARAMS,
15 | };
16 | use ultra_base::default_pool::{ExecuteMsg, InstantiateMsg, ParamsResponse, QueryMsg};
17 |
18 | // version info for migration info
19 | const CONTRACT_NAME: &str = "crates.io:default-pool";
20 | const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
21 |
22 | pub const NATIVE_JUNO_DENOM: &str = "ujuno";
23 |
24 | #[cfg_attr(not(feature = "library"), entry_point)]
25 | pub fn instantiate(
26 | deps: DepsMut,
27 | _env: Env,
28 | _info: MessageInfo,
29 | msg: InstantiateMsg,
30 | ) -> Result {
31 | set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
32 |
33 | // store sudo params
34 | let data = SudoParams {
35 | name: msg.name,
36 | owner: deps.api.addr_validate(&msg.owner)?,
37 | };
38 |
39 | // initial assets in pool
40 | let assets_in_pool = AssetsInPool {
41 | juno: Uint128::zero(),
42 | ultra_debt: Uint128::zero(),
43 | };
44 |
45 | SUDO_PARAMS.save(deps.storage, &data)?;
46 | ASSETS_IN_POOL.save(deps.storage, &assets_in_pool)?;
47 |
48 | Ok(Response::default())
49 | }
50 |
51 | #[cfg_attr(not(feature = "library"), entry_point)]
52 | pub fn execute(
53 | deps: DepsMut,
54 | env: Env,
55 | info: MessageInfo,
56 | msg: ExecuteMsg,
57 | ) -> Result {
58 | match msg {
59 | ExecuteMsg::IncreaseULTRADebt { amount } => {
60 | execute_increase_ultra_debt(deps, env, info, amount)
61 | }
62 | ExecuteMsg::DecreaseULTRADebt { amount } => {
63 | execute_decrease_ultra_debt(deps, env, info, amount)
64 | }
65 | ExecuteMsg::SendJUNOToActivePool { amount } => {
66 | execute_send_juno_to_active_pool(deps, env, info, amount)
67 | }
68 | ExecuteMsg::SetAddresses {
69 | trove_manager_address,
70 | active_pool_address,
71 | } => execute_set_addresses(deps, env, info, trove_manager_address, active_pool_address),
72 | }
73 | }
74 |
75 | pub fn execute_increase_ultra_debt(
76 | deps: DepsMut,
77 | _env: Env,
78 | info: MessageInfo,
79 | amount: Uint128,
80 | ) -> Result {
81 | only_tm(deps.storage, &info)?;
82 |
83 | let mut assets_in_pool = ASSETS_IN_POOL.load(deps.storage)?;
84 | assets_in_pool.ultra_debt += amount;
85 | ASSETS_IN_POOL.save(deps.storage, &assets_in_pool)?;
86 | let res = Response::new()
87 | .add_attribute("action", "increase_ultra_debt")
88 | .add_attribute("amount", amount);
89 | Ok(res)
90 | }
91 |
92 | pub fn execute_decrease_ultra_debt(
93 | deps: DepsMut,
94 | _env: Env,
95 | info: MessageInfo,
96 | amount: Uint128,
97 | ) -> Result {
98 | only_tm(deps.storage, &info)?;
99 |
100 | let mut assets_in_pool = ASSETS_IN_POOL.load(deps.storage)?;
101 | assets_in_pool.ultra_debt = assets_in_pool
102 | .ultra_debt
103 | .checked_sub(amount)
104 | .map_err(StdError::overflow)?;
105 | ASSETS_IN_POOL.save(deps.storage, &assets_in_pool)?;
106 | let res = Response::new()
107 | .add_attribute("action", "decrease_ultra_debt")
108 | .add_attribute("amount", amount);
109 | Ok(res)
110 | }
111 |
112 | pub fn execute_send_juno_to_active_pool(
113 | deps: DepsMut,
114 | _env: Env,
115 | info: MessageInfo,
116 | amount: Uint128,
117 | ) -> Result {
118 | only_tm(deps.storage, &info)?;
119 |
120 | let mut assets_in_pool = ASSETS_IN_POOL.load(deps.storage)?;
121 | assets_in_pool.juno = assets_in_pool
122 | .juno
123 | .checked_sub(amount)
124 | .map_err(StdError::overflow)?;
125 | ASSETS_IN_POOL.save(deps.storage, &assets_in_pool)?;
126 |
127 | let addresses_set = ADDRESSES_SET.load(deps.storage)?;
128 | let active_pool_address = addresses_set.active_pool_address;
129 | let send_msg = BankMsg::Send {
130 | to_address: active_pool_address.to_string(),
131 | amount: vec![coin(amount.u128(), NATIVE_JUNO_DENOM.to_string())],
132 | };
133 | let res = Response::new()
134 | .add_message(send_msg)
135 | .add_attribute("action", "send_juno")
136 | .add_attribute("recipient", active_pool_address.to_string())
137 | .add_attribute("amount", amount);
138 | Ok(res)
139 | }
140 |
141 | pub fn execute_set_addresses(
142 | deps: DepsMut,
143 | _env: Env,
144 | info: MessageInfo,
145 | trove_manager_address: String,
146 | active_pool_address: String,
147 | ) -> Result {
148 | only_owner(deps.storage, &info)?;
149 |
150 | let new_addresses_set = AddressesSet {
151 | trove_manager_address: deps.api.addr_validate(&trove_manager_address)?,
152 | active_pool_address: deps.api.addr_validate(&active_pool_address)?,
153 | };
154 |
155 | ADDRESSES_SET.save(deps.storage, &new_addresses_set)?;
156 | let res = Response::new()
157 | .add_attribute("action", "set_addresses")
158 | .add_attribute("trove_manager_address", trove_manager_address)
159 | .add_attribute("active_pool_address", active_pool_address);
160 | Ok(res)
161 | }
162 |
163 | /// Checks to enfore only active pool can call
164 | fn only_ap(store: &dyn Storage, info: &MessageInfo) -> Result {
165 | let addresses_set = ADDRESSES_SET.load(store)?;
166 | if addresses_set.active_pool_address != info.sender.as_ref() {
167 | return Err(ContractError::CallerIsNotAP {});
168 | }
169 | Ok(info.sender.clone())
170 | }
171 | /// Checks to enfore only trove manager can call
172 | fn only_tm(store: &dyn Storage, info: &MessageInfo) -> Result {
173 | let addresses_set = ADDRESSES_SET.load(store)?;
174 | if addresses_set.trove_manager_address != info.sender.as_ref() {
175 | return Err(ContractError::CallerIsNotTM {});
176 | }
177 | Ok(info.sender.clone())
178 | }
179 | /// Checks to enfore only owner can call
180 | fn only_owner(store: &dyn Storage, info: &MessageInfo) -> Result {
181 | let params = SUDO_PARAMS.load(store)?;
182 | if params.owner != info.sender.as_ref() {
183 | return Err(ContractError::UnauthorizedOwner {});
184 | }
185 | Ok(info.sender.clone())
186 | }
187 |
188 | #[cfg_attr(not(feature = "library"), entry_point)]
189 | pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult {
190 | match msg {
191 | QueryMsg::GetParams {} => to_binary(&query_params(deps)?),
192 | QueryMsg::GetJUNO {} => to_binary(&query_juno_state(deps)?),
193 | QueryMsg::GetULTRADebt {} => to_binary(&query_ultra_debt_state(deps)?),
194 | QueryMsg::GetActivePoolAddress {} => to_binary(&query_active_pool_address(deps)?),
195 | QueryMsg::GetTroveManagerAddress {} => to_binary(&query_trove_manager_address(deps)?),
196 | }
197 | }
198 |
199 | pub fn query_juno_state(deps: Deps) -> StdResult {
200 | let info = ASSETS_IN_POOL.load(deps.storage)?;
201 | let res = info.juno;
202 | Ok(res)
203 | }
204 |
205 | pub fn query_ultra_debt_state(deps: Deps) -> StdResult {
206 | let info = ASSETS_IN_POOL.load(deps.storage)?;
207 | let res = info.ultra_debt;
208 | Ok(res)
209 | }
210 |
211 | pub fn query_params(deps: Deps) -> StdResult {
212 | let info = SUDO_PARAMS.load(deps.storage)?;
213 | let res = ParamsResponse {
214 | name: info.name,
215 | owner: info.owner,
216 | };
217 | Ok(res)
218 | }
219 |
220 | pub fn query_active_pool_address(deps: Deps) -> StdResult {
221 | let addresses_set = ADDRESSES_SET.load(deps.storage)?;
222 | let active_pool_address = addresses_set.active_pool_address;
223 | Ok(active_pool_address)
224 | }
225 |
226 | pub fn query_trove_manager_address(deps: Deps) -> StdResult {
227 | let addresses_set = ADDRESSES_SET.load(deps.storage)?;
228 | let trove_manager_address = addresses_set.trove_manager_address;
229 | Ok(trove_manager_address)
230 | }
231 |
--------------------------------------------------------------------------------
/contracts/default-pool/src/error.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::StdError;
2 | use thiserror::Error;
3 |
4 | #[derive(Error, Debug, PartialEq)]
5 | pub enum ContractError {
6 | #[error("{0}")]
7 | Std(#[from] StdError),
8 |
9 | #[error("UnauthorizedOwner")]
10 | UnauthorizedOwner {},
11 |
12 | #[error("DefaultPool: Caller is not the ActivePool")]
13 | CallerIsNotAP {},
14 |
15 | #[error("DefaultPool: Caller is not the TroveManager")]
16 | CallerIsNotTM {},
17 | }
18 |
--------------------------------------------------------------------------------
/contracts/default-pool/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod contract;
2 | mod error;
3 | pub mod state;
4 | pub mod sudo;
5 |
6 | pub use crate::error::ContractError;
7 |
--------------------------------------------------------------------------------
/contracts/default-pool/src/state.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::{Addr, Uint128};
2 | use cw_storage_plus::Item;
3 | use schemars::JsonSchema;
4 | use serde::{Deserialize, Serialize};
5 |
6 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
7 | pub struct AddressesSet {
8 | pub trove_manager_address: Addr,
9 | pub active_pool_address: Addr,
10 | }
11 |
12 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
13 | pub struct AssetsInPool {
14 | pub juno: Uint128,
15 | pub ultra_debt: Uint128,
16 | }
17 |
18 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
19 | pub struct SudoParams {
20 | pub name: String,
21 | pub owner: Addr,
22 | }
23 |
24 | pub const SUDO_PARAMS: Item = Item::new("sudo-params");
25 | pub const ADDRESSES_SET: Item = Item::new("addresses_set");
26 | pub const ASSETS_IN_POOL: Item = Item::new("assets_in_pool");
27 |
--------------------------------------------------------------------------------
/contracts/default-pool/src/sudo.rs:
--------------------------------------------------------------------------------
1 | use crate::error::ContractError;
2 | use crate::state::SUDO_PARAMS;
3 | use cosmwasm_std::{entry_point, Addr, DepsMut, Env, Response};
4 | use ultra_base::default_pool::SudoMsg;
5 |
6 | pub struct ParamInfo {
7 | name: Option,
8 | owner: Option,
9 | }
10 |
11 | #[cfg_attr(not(feature = "library"), entry_point)]
12 | pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> Result {
13 | match msg {
14 | SudoMsg::UpdateParams { name, owner } => {
15 | sudo_update_params(deps, env, ParamInfo { name, owner })
16 | }
17 | }
18 | }
19 |
20 | /// Only governance can update contract params
21 | pub fn sudo_update_params(
22 | deps: DepsMut,
23 | _env: Env,
24 | param_info: ParamInfo,
25 | ) -> Result {
26 | let ParamInfo { name, owner } = param_info;
27 |
28 | let mut params = SUDO_PARAMS.load(deps.storage)?;
29 |
30 | params.name = name.unwrap_or(params.name);
31 | params.owner = owner.unwrap_or(params.owner);
32 |
33 | SUDO_PARAMS.save(deps.storage, ¶ms)?;
34 |
35 | Ok(Response::new().add_attribute("action", "update_params"))
36 | }
37 |
--------------------------------------------------------------------------------
/contracts/junoswap-oracle/.cargo/config:
--------------------------------------------------------------------------------
1 | [alias]
2 | wasm = "build --release --target wasm32-unknown-unknown"
3 | wasm-debug = "build --target wasm32-unknown-unknown"
4 | schema = "run --example schema"
5 |
--------------------------------------------------------------------------------
/contracts/junoswap-oracle/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "junoswap-oracle"
3 | version = "0.1.0"
4 | authors = ["Chinh D.Nguyen "]
5 | edition = "2021"
6 |
7 | description = "JunoSwap Oracle"
8 | repository = "https://github.com/notional-labs/UltraStableJuno"
9 |
10 | [lib]
11 | crate-type = ["cdylib", "rlib"]
12 |
13 | [features]
14 | backtraces = ["cosmwasm-std/backtraces"]
15 | # use library feature to disable all instantiate/execute/query exports
16 | library = []
17 |
18 | [dependencies]
19 | cw2 = { version = "0.13.4" }
20 | cosmwasm-std = { version = "1.0.0" }
21 | cw-storage-plus = { version = "0.13.4" }
22 | schemars = "0.8.1"
23 | serde = { version = "1.0.103", default-features = false, features = ["derive"] }
24 | thiserror = { version = "1.0.23" }
25 | ultra-base = { path = "../../packages/ultra-base", default-features = false }
--------------------------------------------------------------------------------
/contracts/junoswap-oracle/README.md:
--------------------------------------------------------------------------------
1 | # JunoSwap Oracle
2 |
--------------------------------------------------------------------------------
/contracts/junoswap-oracle/src/contract.rs:
--------------------------------------------------------------------------------
1 | use crate::error::ContractError;
2 | use crate::state::{Config, PriceCumulativeLast, CONFIG, PRICE_LAST};
3 | use cosmwasm_std::{
4 | entry_point, to_binary, Binary, Decimal256, Deps, DepsMut, Env, MessageInfo, Response,
5 | StdError, StdResult, Uint128, Uint256,
6 | };
7 | use cw2::set_contract_version;
8 | use ultra_base::asset::{AssetInfo, PoolInfo};
9 | use ultra_base::oracle::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
10 | use ultra_base::querier::query_pool_info;
11 |
12 | const CONTRACT_NAME: &str = "junoswap-oracle";
13 | const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
14 |
15 | /// Time between two consecutive TWAP updates.
16 | pub const PERIOD: Uint128 = Uint128::new(1200u128);
17 |
18 | /// Decimal precision for TWAP results
19 | pub const TWAP_PRECISION: u8 = 6;
20 |
21 | #[cfg_attr(not(feature = "library"), entry_point)]
22 | pub fn instantiate(
23 | deps: DepsMut,
24 | env: Env,
25 | info: MessageInfo,
26 | msg: InstantiateMsg,
27 | ) -> Result {
28 | set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
29 |
30 | let pool_contract_address = deps.api.addr_validate(&msg.pool_contract_address)?;
31 | let pool_info: PoolInfo = query_pool_info(&deps.querier, pool_contract_address.clone())?;
32 |
33 | let pool_info_clone = pool_info.clone();
34 |
35 | let config = Config {
36 | owner: info.sender,
37 | pool_contract_addr: pool_contract_address,
38 | asset_infos: [pool_info_clone.token1_denom, pool_info_clone.token2_denom],
39 | pool: pool_info,
40 | };
41 | CONFIG.save(deps.storage, &config)?;
42 |
43 | let init_price = PriceCumulativeLast {
44 | price1_cumulative_last: Uint128::zero(),
45 | price2_cumulative_last: Uint128::zero(),
46 | price_1_average: Decimal256::zero(),
47 | price_2_average: Decimal256::zero(),
48 | block_timestamp_last: env.block.time.seconds(),
49 | };
50 | PRICE_LAST.save(deps.storage, &init_price)?;
51 | Ok(Response::default())
52 | }
53 |
54 | #[cfg_attr(not(feature = "library"), entry_point)]
55 | pub fn execute(
56 | deps: DepsMut,
57 | env: Env,
58 | _info: MessageInfo,
59 | msg: ExecuteMsg,
60 | ) -> Result {
61 | match msg {
62 | ExecuteMsg::Update {} => update(deps, env),
63 | }
64 | }
65 |
66 | pub fn update(deps: DepsMut, env: Env) -> Result {
67 | let config = CONFIG.load(deps.storage)?;
68 | let pool_info: PoolInfo = query_pool_info(&deps.querier, config.pool_contract_addr)?;
69 |
70 | let price_last = PRICE_LAST.load(deps.storage)?;
71 |
72 | let time_elapsed = Uint128::from(env.block.time.seconds() - price_last.block_timestamp_last);
73 | let price_precision = Uint128::from(10u128.pow(TWAP_PRECISION.into()));
74 |
75 | // Ensure that at least one full period has passed since the last update
76 | if time_elapsed < PERIOD {
77 | return Err(ContractError::WrongPeriod {});
78 | }
79 |
80 | let x = pool_info.token1_reserve;
81 | let y = pool_info.token2_reserve;
82 |
83 | let price1_cumulative_new = price_last.price1_cumulative_last.wrapping_add(
84 | time_elapsed
85 | .checked_mul(price_precision)
86 | .map_err(StdError::overflow)?
87 | .multiply_ratio(y, x),
88 | );
89 |
90 | let price2_cumulative_new = price_last.price2_cumulative_last.wrapping_add(
91 | time_elapsed
92 | .checked_mul(price_precision)
93 | .map_err(StdError::overflow)?
94 | .multiply_ratio(x, y),
95 | );
96 |
97 | let price_1_average = Decimal256::from_ratio(
98 | Uint256::from(price1_cumulative_new.wrapping_sub(price_last.price1_cumulative_last)),
99 | time_elapsed,
100 | );
101 |
102 | let price_2_average = Decimal256::from_ratio(
103 | Uint256::from(price2_cumulative_new.wrapping_sub(price_last.price2_cumulative_last)),
104 | time_elapsed,
105 | );
106 |
107 | let prices = PriceCumulativeLast {
108 | price1_cumulative_last: price1_cumulative_new,
109 | price2_cumulative_last: price2_cumulative_new,
110 | price_1_average,
111 | price_2_average,
112 | block_timestamp_last: env.block.time.seconds(),
113 | };
114 | PRICE_LAST.save(deps.storage, &prices)?;
115 | Ok(Response::default())
116 | }
117 |
118 | #[cfg_attr(not(feature = "library"), entry_point)]
119 | pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult {
120 | match msg {
121 | QueryMsg::Consult { token, amount } => to_binary(&consult(deps, token, amount)?),
122 | }
123 | }
124 |
125 | /// Multiplies a token amount by its latest TWAP value and returns the result as a [`Uint256`] if the operation was successful
126 | fn consult(deps: Deps, token: AssetInfo, amount: Uint128) -> Result {
127 | let config = CONFIG.load(deps.storage)?;
128 | let price_last = PRICE_LAST.load(deps.storage)?;
129 | let price_precision = Uint256::from(10_u128.pow(TWAP_PRECISION.into()));
130 |
131 | let price_average = if config.asset_infos[0].equal(&token) {
132 | price_last.price_1_average
133 | } else if config.asset_infos[1].equal(&token) {
134 | price_last.price_2_average
135 | } else {
136 | return Err(StdError::generic_err("Invalid Token"));
137 | };
138 |
139 | Ok(Uint256::from(amount) * price_average / price_precision)
140 | }
141 |
142 | #[cfg_attr(not(feature = "library"), entry_point)]
143 | pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult {
144 | Ok(Response::default())
145 | }
146 |
--------------------------------------------------------------------------------
/contracts/junoswap-oracle/src/error.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::StdError;
2 | use thiserror::Error;
3 |
4 | #[derive(Error, Debug, PartialEq)]
5 | pub enum ContractError {
6 | #[error("{0}")]
7 | Std(#[from] StdError),
8 |
9 | #[error("Unauthorized")]
10 | Unauthorized {},
11 |
12 | #[error("Period not elapsed")]
13 | WrongPeriod {},
14 | }
15 |
--------------------------------------------------------------------------------
/contracts/junoswap-oracle/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod contract;
2 | mod error;
3 | pub mod state;
4 |
5 | #[cfg(test)]
6 | mod overflow_tests;
7 |
8 | pub use crate::error::ContractError;
9 |
--------------------------------------------------------------------------------
/contracts/junoswap-oracle/src/overflow_tests.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::{Decimal256, Uint128, Uint256};
2 | use std::ops::Mul;
3 |
4 | #[test]
5 | fn decimal_overflow() {
6 | let price_cumulative_current = Uint128::from(100u128);
7 | let price_cumulative_last = Uint128::from(192738282u128);
8 | let time_elapsed: u64 = 86400;
9 | let amount = Uint128::from(1000u128);
10 | let price_average = Decimal256::from_ratio(
11 | Uint256::from(price_cumulative_current.wrapping_sub(price_cumulative_last)),
12 | time_elapsed,
13 | );
14 |
15 | println!("{}", price_average.to_string());
16 |
17 | let res: Uint128 = price_average.mul(Uint256::from(amount)).try_into().unwrap();
18 | println!("{}", res);
19 | }
20 |
--------------------------------------------------------------------------------
/contracts/junoswap-oracle/src/state.rs:
--------------------------------------------------------------------------------
1 | use schemars::JsonSchema;
2 | use serde::{Deserialize, Serialize};
3 |
4 | use cosmwasm_std::{Addr, Decimal256, Uint128};
5 | use cw_storage_plus::Item;
6 | use ultra_base::asset::{AssetInfo, PoolInfo};
7 |
8 | /// This structure stores the latest cumulative and average token prices for the target pool
9 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
10 | pub struct PriceCumulativeLast {
11 | pub price1_cumulative_last: Uint128,
12 | pub price2_cumulative_last: Uint128,
13 | pub price_1_average: Decimal256,
14 | pub price_2_average: Decimal256,
15 | pub block_timestamp_last: u64,
16 | }
17 |
18 | /// Global configuration for the contract
19 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
20 | pub struct Config {
21 | pub owner: Addr,
22 | pub pool_contract_addr: Addr,
23 | pub asset_infos: [AssetInfo; 2],
24 | pub pool: PoolInfo,
25 | }
26 |
27 | pub const CONFIG: Item = Item::new("config");
28 | pub const PRICE_LAST: Item = Item::new("price_last");
29 |
--------------------------------------------------------------------------------
/contracts/ultra-token/.cargo/config:
--------------------------------------------------------------------------------
1 | [alias]
2 | wasm = "build --release --target wasm32-unknown-unknown"
3 | unit-test = "test --lib"
4 | schema = "run --example schema"
5 |
--------------------------------------------------------------------------------
/contracts/ultra-token/.gitignore:
--------------------------------------------------------------------------------
1 | # Build results
2 | /target
3 |
4 | # Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327)
5 | .cargo-ok
6 |
7 | # Text file backups
8 | **/*.rs.bk
9 |
10 | # macOS
11 | .DS_Store
12 |
13 | # IDEs
14 | *.iml
15 | .idea
16 |
--------------------------------------------------------------------------------
/contracts/ultra-token/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "ultra-token"
3 | version = "0.1.0"
4 | authors = ["Chinh D.Nguyen "]
5 | edition = "2021"
6 |
7 | description = "Implementation ULTRA stablecoin as a CW20 token"
8 | repository = "https://github.com/notional-labs/UltraStableJuno"
9 |
10 | [lib]
11 | crate-type = ["cdylib", "rlib"]
12 |
13 | [features]
14 | backtraces = ["cosmwasm-std/backtraces"]
15 | # use library feature to disable all instantiate/execute/query exports
16 | library = []
17 |
18 | [dependencies]
19 | cw-utils = { version = "0.13.4" }
20 | cw2 = { version = "0.13.4" }
21 | cw20 = { version = "0.13.4" }
22 | cw-storage-plus = { version = "0.13.4" }
23 | cosmwasm-std = { version = "1.0.0" }
24 | schemars = "0.8.1"
25 | serde = { version = "1.0.103", default-features = false, features = ["derive"] }
26 | thiserror = { version = "1.0.23" }
27 |
28 | [dev-dependencies]
29 | cosmwasm-schema = { version = "1.0.0" }
30 |
--------------------------------------------------------------------------------
/contracts/ultra-token/README.md:
--------------------------------------------------------------------------------
1 | # ULTRA token contract
2 |
3 | This is the ULTRA stablecoin token contract, which implements the CW20 fungible token standard. The contract mints, burns and transfers ULTRA tokens.
--------------------------------------------------------------------------------
/contracts/ultra-token/examples/schema.rs:
--------------------------------------------------------------------------------
1 | use std::env::current_dir;
2 | use std::fs::create_dir_all;
3 |
4 | use cosmwasm_schema::{export_schema, remove_schemas, schema_for};
5 |
6 | use cw20::{
7 | AllAccountsResponse, AllAllowancesResponse, AllowanceResponse, BalanceResponse,
8 | TokenInfoResponse,
9 | };
10 | use ultra_token::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
11 |
12 | fn main() {
13 | let mut out_dir = current_dir().unwrap();
14 | out_dir.push("schema");
15 | create_dir_all(&out_dir).unwrap();
16 | remove_schemas(&out_dir).unwrap();
17 |
18 | export_schema(&schema_for!(InstantiateMsg), &out_dir);
19 | export_schema(&schema_for!(ExecuteMsg), &out_dir);
20 | export_schema(&schema_for!(QueryMsg), &out_dir);
21 | export_schema(&schema_for!(AllowanceResponse), &out_dir);
22 | export_schema(&schema_for!(BalanceResponse), &out_dir);
23 | export_schema(&schema_for!(TokenInfoResponse), &out_dir);
24 | export_schema(&schema_for!(AllAllowancesResponse), &out_dir);
25 | export_schema(&schema_for!(AllAccountsResponse), &out_dir);
26 | }
27 |
--------------------------------------------------------------------------------
/contracts/ultra-token/schema/all_accounts_response.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "title": "AllAccountsResponse",
4 | "type": "object",
5 | "required": [
6 | "accounts"
7 | ],
8 | "properties": {
9 | "accounts": {
10 | "type": "array",
11 | "items": {
12 | "type": "string"
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/contracts/ultra-token/schema/all_allowances_response.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "title": "AllAllowancesResponse",
4 | "type": "object",
5 | "required": [
6 | "allowances"
7 | ],
8 | "properties": {
9 | "allowances": {
10 | "type": "array",
11 | "items": {
12 | "$ref": "#/definitions/AllowanceInfo"
13 | }
14 | }
15 | },
16 | "definitions": {
17 | "AllowanceInfo": {
18 | "type": "object",
19 | "required": [
20 | "allowance",
21 | "expires",
22 | "spender"
23 | ],
24 | "properties": {
25 | "allowance": {
26 | "$ref": "#/definitions/Uint128"
27 | },
28 | "expires": {
29 | "$ref": "#/definitions/Expiration"
30 | },
31 | "spender": {
32 | "type": "string"
33 | }
34 | }
35 | },
36 | "Expiration": {
37 | "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)",
38 | "oneOf": [
39 | {
40 | "description": "AtHeight will expire when `env.block.height` >= height",
41 | "type": "object",
42 | "required": [
43 | "at_height"
44 | ],
45 | "properties": {
46 | "at_height": {
47 | "type": "integer",
48 | "format": "uint64",
49 | "minimum": 0.0
50 | }
51 | },
52 | "additionalProperties": false
53 | },
54 | {
55 | "description": "AtTime will expire when `env.block.time` >= time",
56 | "type": "object",
57 | "required": [
58 | "at_time"
59 | ],
60 | "properties": {
61 | "at_time": {
62 | "$ref": "#/definitions/Timestamp"
63 | }
64 | },
65 | "additionalProperties": false
66 | },
67 | {
68 | "description": "Never will never expire. Used to express the empty variant",
69 | "type": "object",
70 | "required": [
71 | "never"
72 | ],
73 | "properties": {
74 | "never": {
75 | "type": "object"
76 | }
77 | },
78 | "additionalProperties": false
79 | }
80 | ]
81 | },
82 | "Timestamp": {
83 | "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```",
84 | "allOf": [
85 | {
86 | "$ref": "#/definitions/Uint64"
87 | }
88 | ]
89 | },
90 | "Uint128": {
91 | "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
92 | "type": "string"
93 | },
94 | "Uint64": {
95 | "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```",
96 | "type": "string"
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/contracts/ultra-token/schema/allowance_response.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "title": "AllowanceResponse",
4 | "type": "object",
5 | "required": [
6 | "allowance",
7 | "expires"
8 | ],
9 | "properties": {
10 | "allowance": {
11 | "$ref": "#/definitions/Uint128"
12 | },
13 | "expires": {
14 | "$ref": "#/definitions/Expiration"
15 | }
16 | },
17 | "definitions": {
18 | "Expiration": {
19 | "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)",
20 | "oneOf": [
21 | {
22 | "description": "AtHeight will expire when `env.block.height` >= height",
23 | "type": "object",
24 | "required": [
25 | "at_height"
26 | ],
27 | "properties": {
28 | "at_height": {
29 | "type": "integer",
30 | "format": "uint64",
31 | "minimum": 0.0
32 | }
33 | },
34 | "additionalProperties": false
35 | },
36 | {
37 | "description": "AtTime will expire when `env.block.time` >= time",
38 | "type": "object",
39 | "required": [
40 | "at_time"
41 | ],
42 | "properties": {
43 | "at_time": {
44 | "$ref": "#/definitions/Timestamp"
45 | }
46 | },
47 | "additionalProperties": false
48 | },
49 | {
50 | "description": "Never will never expire. Used to express the empty variant",
51 | "type": "object",
52 | "required": [
53 | "never"
54 | ],
55 | "properties": {
56 | "never": {
57 | "type": "object"
58 | }
59 | },
60 | "additionalProperties": false
61 | }
62 | ]
63 | },
64 | "Timestamp": {
65 | "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```",
66 | "allOf": [
67 | {
68 | "$ref": "#/definitions/Uint64"
69 | }
70 | ]
71 | },
72 | "Uint128": {
73 | "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
74 | "type": "string"
75 | },
76 | "Uint64": {
77 | "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```",
78 | "type": "string"
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/contracts/ultra-token/schema/balance_response.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "title": "BalanceResponse",
4 | "type": "object",
5 | "required": [
6 | "balance"
7 | ],
8 | "properties": {
9 | "balance": {
10 | "$ref": "#/definitions/Uint128"
11 | }
12 | },
13 | "definitions": {
14 | "Uint128": {
15 | "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
16 | "type": "string"
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/contracts/ultra-token/schema/execute_msg.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "title": "ExecuteMsg",
4 | "oneOf": [
5 | {
6 | "description": "Transfer is a base message to move tokens to another account without triggering actions",
7 | "type": "object",
8 | "required": [
9 | "transfer"
10 | ],
11 | "properties": {
12 | "transfer": {
13 | "type": "object",
14 | "required": [
15 | "amount",
16 | "recipient"
17 | ],
18 | "properties": {
19 | "amount": {
20 | "$ref": "#/definitions/Uint128"
21 | },
22 | "recipient": {
23 | "type": "string"
24 | }
25 | }
26 | }
27 | },
28 | "additionalProperties": false
29 | },
30 | {
31 | "description": "Burn is a base message to destroy tokens forever",
32 | "type": "object",
33 | "required": [
34 | "burn"
35 | ],
36 | "properties": {
37 | "burn": {
38 | "type": "object",
39 | "required": [
40 | "amount"
41 | ],
42 | "properties": {
43 | "amount": {
44 | "$ref": "#/definitions/Uint128"
45 | }
46 | }
47 | }
48 | },
49 | "additionalProperties": false
50 | },
51 | {
52 | "description": "Send is a base message to transfer tokens to a contract and trigger an action on the receiving contract.",
53 | "type": "object",
54 | "required": [
55 | "send"
56 | ],
57 | "properties": {
58 | "send": {
59 | "type": "object",
60 | "required": [
61 | "amount",
62 | "contract",
63 | "msg"
64 | ],
65 | "properties": {
66 | "amount": {
67 | "$ref": "#/definitions/Uint128"
68 | },
69 | "contract": {
70 | "type": "string"
71 | },
72 | "msg": {
73 | "$ref": "#/definitions/Binary"
74 | }
75 | }
76 | }
77 | },
78 | "additionalProperties": false
79 | },
80 | {
81 | "description": "Only with \"approval\" extension. Allows spender to access an additional amount tokens from the owner's (env.sender) account. If expires is Some(), overwrites current allowance expiration with this one.",
82 | "type": "object",
83 | "required": [
84 | "increase_allowance"
85 | ],
86 | "properties": {
87 | "increase_allowance": {
88 | "type": "object",
89 | "required": [
90 | "amount",
91 | "spender"
92 | ],
93 | "properties": {
94 | "amount": {
95 | "$ref": "#/definitions/Uint128"
96 | },
97 | "expires": {
98 | "anyOf": [
99 | {
100 | "$ref": "#/definitions/Expiration"
101 | },
102 | {
103 | "type": "null"
104 | }
105 | ]
106 | },
107 | "spender": {
108 | "type": "string"
109 | }
110 | }
111 | }
112 | },
113 | "additionalProperties": false
114 | },
115 | {
116 | "description": "Only with \"approval\" extension. Lowers the spender's access of tokens from the owner's (env.sender) account by amount. If expires is Some(), overwrites current allowance expiration with this one.",
117 | "type": "object",
118 | "required": [
119 | "decrease_allowance"
120 | ],
121 | "properties": {
122 | "decrease_allowance": {
123 | "type": "object",
124 | "required": [
125 | "amount",
126 | "spender"
127 | ],
128 | "properties": {
129 | "amount": {
130 | "$ref": "#/definitions/Uint128"
131 | },
132 | "expires": {
133 | "anyOf": [
134 | {
135 | "$ref": "#/definitions/Expiration"
136 | },
137 | {
138 | "type": "null"
139 | }
140 | ]
141 | },
142 | "spender": {
143 | "type": "string"
144 | }
145 | }
146 | }
147 | },
148 | "additionalProperties": false
149 | },
150 | {
151 | "description": "Only with \"approval\" extension. Transfers amount tokens from owner -> recipient if `env.sender` has sufficient pre-approval.",
152 | "type": "object",
153 | "required": [
154 | "transfer_from"
155 | ],
156 | "properties": {
157 | "transfer_from": {
158 | "type": "object",
159 | "required": [
160 | "amount",
161 | "owner",
162 | "recipient"
163 | ],
164 | "properties": {
165 | "amount": {
166 | "$ref": "#/definitions/Uint128"
167 | },
168 | "owner": {
169 | "type": "string"
170 | },
171 | "recipient": {
172 | "type": "string"
173 | }
174 | }
175 | }
176 | },
177 | "additionalProperties": false
178 | },
179 | {
180 | "description": "Only with \"approval\" extension. Sends amount tokens from owner -> contract if `env.sender` has sufficient pre-approval.",
181 | "type": "object",
182 | "required": [
183 | "send_from"
184 | ],
185 | "properties": {
186 | "send_from": {
187 | "type": "object",
188 | "required": [
189 | "amount",
190 | "contract",
191 | "msg",
192 | "owner"
193 | ],
194 | "properties": {
195 | "amount": {
196 | "$ref": "#/definitions/Uint128"
197 | },
198 | "contract": {
199 | "type": "string"
200 | },
201 | "msg": {
202 | "$ref": "#/definitions/Binary"
203 | },
204 | "owner": {
205 | "type": "string"
206 | }
207 | }
208 | }
209 | },
210 | "additionalProperties": false
211 | },
212 | {
213 | "description": "Only with \"approval\" extension. TODO: This msg is just a template. This will be redefined after.",
214 | "type": "object",
215 | "required": [
216 | "send_to_pool"
217 | ],
218 | "properties": {
219 | "send_to_pool": {
220 | "type": "object",
221 | "required": [
222 | "amount",
223 | "msg",
224 | "owner",
225 | "pool_address"
226 | ],
227 | "properties": {
228 | "amount": {
229 | "$ref": "#/definitions/Uint128"
230 | },
231 | "msg": {
232 | "$ref": "#/definitions/Binary"
233 | },
234 | "owner": {
235 | "type": "string"
236 | },
237 | "pool_address": {
238 | "type": "string"
239 | }
240 | }
241 | }
242 | },
243 | "additionalProperties": false
244 | },
245 | {
246 | "description": "Only with \"approval\" extension. TODO: This msg is just a template. This will be redefined after.",
247 | "type": "object",
248 | "required": [
249 | "return_from_pool"
250 | ],
251 | "properties": {
252 | "return_from_pool": {
253 | "type": "object",
254 | "required": [
255 | "amount",
256 | "pool_address",
257 | "receiver"
258 | ],
259 | "properties": {
260 | "amount": {
261 | "$ref": "#/definitions/Uint128"
262 | },
263 | "pool_address": {
264 | "type": "string"
265 | },
266 | "receiver": {
267 | "type": "string"
268 | }
269 | }
270 | }
271 | },
272 | "additionalProperties": false
273 | },
274 | {
275 | "description": "Only with \"approval\" extension. Destroys tokens forever",
276 | "type": "object",
277 | "required": [
278 | "burn_from"
279 | ],
280 | "properties": {
281 | "burn_from": {
282 | "type": "object",
283 | "required": [
284 | "amount",
285 | "owner"
286 | ],
287 | "properties": {
288 | "amount": {
289 | "$ref": "#/definitions/Uint128"
290 | },
291 | "owner": {
292 | "type": "string"
293 | }
294 | }
295 | }
296 | },
297 | "additionalProperties": false
298 | },
299 | {
300 | "description": "Only with the \"mintable\" extension. If authorized, creates amount new tokens and adds to the recipient balance.",
301 | "type": "object",
302 | "required": [
303 | "mint"
304 | ],
305 | "properties": {
306 | "mint": {
307 | "type": "object",
308 | "required": [
309 | "amount",
310 | "recipient"
311 | ],
312 | "properties": {
313 | "amount": {
314 | "$ref": "#/definitions/Uint128"
315 | },
316 | "recipient": {
317 | "type": "string"
318 | }
319 | }
320 | }
321 | },
322 | "additionalProperties": false
323 | },
324 | {
325 | "description": "Only with the \"marketing\" extension. If authorized, updates marketing metadata. Setting None/null for any of these will leave it unchanged. Setting Some(\"\") will clear this field on the contract storage",
326 | "type": "object",
327 | "required": [
328 | "update_marketing"
329 | ],
330 | "properties": {
331 | "update_marketing": {
332 | "type": "object",
333 | "properties": {
334 | "description": {
335 | "description": "A longer description of the token and it's utility. Designed for tooltips or such",
336 | "type": [
337 | "string",
338 | "null"
339 | ]
340 | },
341 | "marketing": {
342 | "description": "The address (if any) who can update this data structure",
343 | "type": [
344 | "string",
345 | "null"
346 | ]
347 | },
348 | "project": {
349 | "description": "A URL pointing to the project behind this token.",
350 | "type": [
351 | "string",
352 | "null"
353 | ]
354 | }
355 | }
356 | }
357 | },
358 | "additionalProperties": false
359 | },
360 | {
361 | "description": "If set as the \"marketing\" role on the contract, upload a new URL, SVG, or PNG for the token",
362 | "type": "object",
363 | "required": [
364 | "upload_logo"
365 | ],
366 | "properties": {
367 | "upload_logo": {
368 | "$ref": "#/definitions/Logo"
369 | }
370 | },
371 | "additionalProperties": false
372 | }
373 | ],
374 | "definitions": {
375 | "Binary": {
376 | "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec",
377 | "type": "string"
378 | },
379 | "EmbeddedLogo": {
380 | "description": "This is used to store the logo on the blockchain in an accepted format. Enforce maximum size of 5KB on all variants.",
381 | "oneOf": [
382 | {
383 | "description": "Store the Logo as an SVG file. The content must conform to the spec at https://en.wikipedia.org/wiki/Scalable_Vector_Graphics (The contract should do some light-weight sanity-check validation)",
384 | "type": "object",
385 | "required": [
386 | "svg"
387 | ],
388 | "properties": {
389 | "svg": {
390 | "$ref": "#/definitions/Binary"
391 | }
392 | },
393 | "additionalProperties": false
394 | },
395 | {
396 | "description": "Store the Logo as a PNG file. This will likely only support up to 64x64 or so within the 5KB limit.",
397 | "type": "object",
398 | "required": [
399 | "png"
400 | ],
401 | "properties": {
402 | "png": {
403 | "$ref": "#/definitions/Binary"
404 | }
405 | },
406 | "additionalProperties": false
407 | }
408 | ]
409 | },
410 | "Expiration": {
411 | "description": "Expiration represents a point in time when some event happens. It can compare with a BlockInfo and will return is_expired() == true once the condition is hit (and for every block in the future)",
412 | "oneOf": [
413 | {
414 | "description": "AtHeight will expire when `env.block.height` >= height",
415 | "type": "object",
416 | "required": [
417 | "at_height"
418 | ],
419 | "properties": {
420 | "at_height": {
421 | "type": "integer",
422 | "format": "uint64",
423 | "minimum": 0.0
424 | }
425 | },
426 | "additionalProperties": false
427 | },
428 | {
429 | "description": "AtTime will expire when `env.block.time` >= time",
430 | "type": "object",
431 | "required": [
432 | "at_time"
433 | ],
434 | "properties": {
435 | "at_time": {
436 | "$ref": "#/definitions/Timestamp"
437 | }
438 | },
439 | "additionalProperties": false
440 | },
441 | {
442 | "description": "Never will never expire. Used to express the empty variant",
443 | "type": "object",
444 | "required": [
445 | "never"
446 | ],
447 | "properties": {
448 | "never": {
449 | "type": "object"
450 | }
451 | },
452 | "additionalProperties": false
453 | }
454 | ]
455 | },
456 | "Logo": {
457 | "description": "This is used for uploading logo data, or setting it in InstantiateData",
458 | "oneOf": [
459 | {
460 | "description": "A reference to an externally hosted logo. Must be a valid HTTP or HTTPS URL.",
461 | "type": "object",
462 | "required": [
463 | "url"
464 | ],
465 | "properties": {
466 | "url": {
467 | "type": "string"
468 | }
469 | },
470 | "additionalProperties": false
471 | },
472 | {
473 | "description": "Logo content stored on the blockchain. Enforce maximum size of 5KB on all variants",
474 | "type": "object",
475 | "required": [
476 | "embedded"
477 | ],
478 | "properties": {
479 | "embedded": {
480 | "$ref": "#/definitions/EmbeddedLogo"
481 | }
482 | },
483 | "additionalProperties": false
484 | }
485 | ]
486 | },
487 | "Timestamp": {
488 | "description": "A point in time in nanosecond precision.\n\nThis type can represent times from 1970-01-01T00:00:00Z to 2554-07-21T23:34:33Z.\n\n## Examples\n\n``` # use cosmwasm_std::Timestamp; let ts = Timestamp::from_nanos(1_000_000_202); assert_eq!(ts.nanos(), 1_000_000_202); assert_eq!(ts.seconds(), 1); assert_eq!(ts.subsec_nanos(), 202);\n\nlet ts = ts.plus_seconds(2); assert_eq!(ts.nanos(), 3_000_000_202); assert_eq!(ts.seconds(), 3); assert_eq!(ts.subsec_nanos(), 202); ```",
489 | "allOf": [
490 | {
491 | "$ref": "#/definitions/Uint64"
492 | }
493 | ]
494 | },
495 | "Uint128": {
496 | "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
497 | "type": "string"
498 | },
499 | "Uint64": {
500 | "description": "A thin wrapper around u64 that is using strings for JSON encoding/decoding, such that the full u64 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u64` to get the value out:\n\n``` # use cosmwasm_std::Uint64; let a = Uint64::from(42u64); assert_eq!(a.u64(), 42);\n\nlet b = Uint64::from(70u32); assert_eq!(b.u64(), 70); ```",
501 | "type": "string"
502 | }
503 | }
504 | }
505 |
--------------------------------------------------------------------------------
/contracts/ultra-token/schema/instantiate_msg.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "title": "InstantiateMsg",
4 | "type": "object",
5 | "required": [
6 | "decimals",
7 | "initial_balances",
8 | "name",
9 | "symbol"
10 | ],
11 | "properties": {
12 | "decimals": {
13 | "type": "integer",
14 | "format": "uint8",
15 | "minimum": 0.0
16 | },
17 | "initial_balances": {
18 | "type": "array",
19 | "items": {
20 | "$ref": "#/definitions/Cw20Coin"
21 | }
22 | },
23 | "marketing": {
24 | "anyOf": [
25 | {
26 | "$ref": "#/definitions/InstantiateMarketingInfo"
27 | },
28 | {
29 | "type": "null"
30 | }
31 | ]
32 | },
33 | "mint": {
34 | "anyOf": [
35 | {
36 | "$ref": "#/definitions/MinterResponse"
37 | },
38 | {
39 | "type": "null"
40 | }
41 | ]
42 | },
43 | "name": {
44 | "type": "string"
45 | },
46 | "symbol": {
47 | "type": "string"
48 | }
49 | },
50 | "definitions": {
51 | "Binary": {
52 | "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec",
53 | "type": "string"
54 | },
55 | "Cw20Coin": {
56 | "type": "object",
57 | "required": [
58 | "address",
59 | "amount"
60 | ],
61 | "properties": {
62 | "address": {
63 | "type": "string"
64 | },
65 | "amount": {
66 | "$ref": "#/definitions/Uint128"
67 | }
68 | }
69 | },
70 | "EmbeddedLogo": {
71 | "description": "This is used to store the logo on the blockchain in an accepted format. Enforce maximum size of 5KB on all variants.",
72 | "oneOf": [
73 | {
74 | "description": "Store the Logo as an SVG file. The content must conform to the spec at https://en.wikipedia.org/wiki/Scalable_Vector_Graphics (The contract should do some light-weight sanity-check validation)",
75 | "type": "object",
76 | "required": [
77 | "svg"
78 | ],
79 | "properties": {
80 | "svg": {
81 | "$ref": "#/definitions/Binary"
82 | }
83 | },
84 | "additionalProperties": false
85 | },
86 | {
87 | "description": "Store the Logo as a PNG file. This will likely only support up to 64x64 or so within the 5KB limit.",
88 | "type": "object",
89 | "required": [
90 | "png"
91 | ],
92 | "properties": {
93 | "png": {
94 | "$ref": "#/definitions/Binary"
95 | }
96 | },
97 | "additionalProperties": false
98 | }
99 | ]
100 | },
101 | "InstantiateMarketingInfo": {
102 | "type": "object",
103 | "properties": {
104 | "description": {
105 | "type": [
106 | "string",
107 | "null"
108 | ]
109 | },
110 | "logo": {
111 | "anyOf": [
112 | {
113 | "$ref": "#/definitions/Logo"
114 | },
115 | {
116 | "type": "null"
117 | }
118 | ]
119 | },
120 | "marketing": {
121 | "type": [
122 | "string",
123 | "null"
124 | ]
125 | },
126 | "project": {
127 | "type": [
128 | "string",
129 | "null"
130 | ]
131 | }
132 | }
133 | },
134 | "Logo": {
135 | "description": "This is used for uploading logo data, or setting it in InstantiateData",
136 | "oneOf": [
137 | {
138 | "description": "A reference to an externally hosted logo. Must be a valid HTTP or HTTPS URL.",
139 | "type": "object",
140 | "required": [
141 | "url"
142 | ],
143 | "properties": {
144 | "url": {
145 | "type": "string"
146 | }
147 | },
148 | "additionalProperties": false
149 | },
150 | {
151 | "description": "Logo content stored on the blockchain. Enforce maximum size of 5KB on all variants",
152 | "type": "object",
153 | "required": [
154 | "embedded"
155 | ],
156 | "properties": {
157 | "embedded": {
158 | "$ref": "#/definitions/EmbeddedLogo"
159 | }
160 | },
161 | "additionalProperties": false
162 | }
163 | ]
164 | },
165 | "MinterResponse": {
166 | "type": "object",
167 | "required": [
168 | "minter"
169 | ],
170 | "properties": {
171 | "cap": {
172 | "description": "cap is a hard cap on total supply that can be achieved by minting. Note that this refers to total_supply. If None, there is unlimited cap.",
173 | "anyOf": [
174 | {
175 | "$ref": "#/definitions/Uint128"
176 | },
177 | {
178 | "type": "null"
179 | }
180 | ]
181 | },
182 | "minter": {
183 | "type": "string"
184 | }
185 | }
186 | },
187 | "Uint128": {
188 | "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
189 | "type": "string"
190 | }
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/contracts/ultra-token/schema/query_msg.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "title": "QueryMsg",
4 | "oneOf": [
5 | {
6 | "description": "Returns the current balance of the given address, 0 if unset. Return type: BalanceResponse.",
7 | "type": "object",
8 | "required": [
9 | "balance"
10 | ],
11 | "properties": {
12 | "balance": {
13 | "type": "object",
14 | "required": [
15 | "address"
16 | ],
17 | "properties": {
18 | "address": {
19 | "type": "string"
20 | }
21 | }
22 | }
23 | },
24 | "additionalProperties": false
25 | },
26 | {
27 | "description": "Returns metadata on the contract - name, decimals, supply, etc. Return type: TokenInfoResponse.",
28 | "type": "object",
29 | "required": [
30 | "token_info"
31 | ],
32 | "properties": {
33 | "token_info": {
34 | "type": "object"
35 | }
36 | },
37 | "additionalProperties": false
38 | },
39 | {
40 | "description": "Only with \"mintable\" extension. Returns who can mint and the hard cap on maximum tokens after minting. Return type: MinterResponse.",
41 | "type": "object",
42 | "required": [
43 | "minter"
44 | ],
45 | "properties": {
46 | "minter": {
47 | "type": "object"
48 | }
49 | },
50 | "additionalProperties": false
51 | },
52 | {
53 | "description": "Only with \"allowance\" extension. Returns how much spender can use from owner account, 0 if unset. Return type: AllowanceResponse.",
54 | "type": "object",
55 | "required": [
56 | "allowance"
57 | ],
58 | "properties": {
59 | "allowance": {
60 | "type": "object",
61 | "required": [
62 | "owner",
63 | "spender"
64 | ],
65 | "properties": {
66 | "owner": {
67 | "type": "string"
68 | },
69 | "spender": {
70 | "type": "string"
71 | }
72 | }
73 | }
74 | },
75 | "additionalProperties": false
76 | },
77 | {
78 | "description": "Only with \"enumerable\" extension (and \"allowances\") Returns all allowances this owner has approved. Supports pagination. Return type: AllAllowancesResponse.",
79 | "type": "object",
80 | "required": [
81 | "all_allowances"
82 | ],
83 | "properties": {
84 | "all_allowances": {
85 | "type": "object",
86 | "required": [
87 | "owner"
88 | ],
89 | "properties": {
90 | "limit": {
91 | "type": [
92 | "integer",
93 | "null"
94 | ],
95 | "format": "uint32",
96 | "minimum": 0.0
97 | },
98 | "owner": {
99 | "type": "string"
100 | },
101 | "start_after": {
102 | "type": [
103 | "string",
104 | "null"
105 | ]
106 | }
107 | }
108 | }
109 | },
110 | "additionalProperties": false
111 | },
112 | {
113 | "description": "Only with \"enumerable\" extension Returns all accounts that have balances. Supports pagination. Return type: AllAccountsResponse.",
114 | "type": "object",
115 | "required": [
116 | "all_accounts"
117 | ],
118 | "properties": {
119 | "all_accounts": {
120 | "type": "object",
121 | "properties": {
122 | "limit": {
123 | "type": [
124 | "integer",
125 | "null"
126 | ],
127 | "format": "uint32",
128 | "minimum": 0.0
129 | },
130 | "start_after": {
131 | "type": [
132 | "string",
133 | "null"
134 | ]
135 | }
136 | }
137 | }
138 | },
139 | "additionalProperties": false
140 | },
141 | {
142 | "description": "Only with \"marketing\" extension Returns more metadata on the contract to display in the client: - description, logo, project url, etc. Return type: MarketingInfoResponse",
143 | "type": "object",
144 | "required": [
145 | "marketing_info"
146 | ],
147 | "properties": {
148 | "marketing_info": {
149 | "type": "object"
150 | }
151 | },
152 | "additionalProperties": false
153 | },
154 | {
155 | "description": "Only with \"marketing\" extension Downloads the embedded logo data (if stored on chain). Errors if no logo data is stored for this contract. Return type: DownloadLogoResponse.",
156 | "type": "object",
157 | "required": [
158 | "download_logo"
159 | ],
160 | "properties": {
161 | "download_logo": {
162 | "type": "object"
163 | }
164 | },
165 | "additionalProperties": false
166 | }
167 | ]
168 | }
169 |
--------------------------------------------------------------------------------
/contracts/ultra-token/schema/token_info_response.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "http://json-schema.org/draft-07/schema#",
3 | "title": "TokenInfoResponse",
4 | "type": "object",
5 | "required": [
6 | "decimals",
7 | "name",
8 | "symbol",
9 | "total_supply"
10 | ],
11 | "properties": {
12 | "decimals": {
13 | "type": "integer",
14 | "format": "uint8",
15 | "minimum": 0.0
16 | },
17 | "name": {
18 | "type": "string"
19 | },
20 | "symbol": {
21 | "type": "string"
22 | },
23 | "total_supply": {
24 | "$ref": "#/definitions/Uint128"
25 | }
26 | },
27 | "definitions": {
28 | "Uint128": {
29 | "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
30 | "type": "string"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/contracts/ultra-token/src/enumerable.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::{Deps, Order, StdResult};
2 | use cw20::{AllAccountsResponse, AllAllowancesResponse, AllowanceInfo};
3 |
4 | use crate::state::{ALLOWANCES, BALANCES};
5 | use cw_storage_plus::Bound;
6 |
7 | // settings for pagination
8 | const MAX_LIMIT: u32 = 30;
9 | const DEFAULT_LIMIT: u32 = 10;
10 |
11 | pub fn query_all_allowances(
12 | deps: Deps,
13 | owner: String,
14 | start_after: Option,
15 | limit: Option,
16 | ) -> StdResult {
17 | let owner_addr = deps.api.addr_validate(&owner)?;
18 | let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
19 | let start = start_after.map(|s| Bound::ExclusiveRaw(s.into_bytes()));
20 |
21 | let allowances = ALLOWANCES
22 | .prefix(&owner_addr)
23 | .range(deps.storage, start, None, Order::Ascending)
24 | .take(limit)
25 | .map(|item| {
26 | item.map(|(addr, allow)| AllowanceInfo {
27 | spender: addr.into(),
28 | allowance: allow.allowance,
29 | expires: allow.expires,
30 | })
31 | })
32 | .collect::>()?;
33 | Ok(AllAllowancesResponse { allowances })
34 | }
35 |
36 | pub fn query_all_accounts(
37 | deps: Deps,
38 | start_after: Option,
39 | limit: Option,
40 | ) -> StdResult {
41 | let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
42 | let start = start_after.map(|s| Bound::ExclusiveRaw(s.into()));
43 |
44 | let accounts = BALANCES
45 | .keys(deps.storage, start, None, Order::Ascending)
46 | .take(limit)
47 | .map(|item| item.map(Into::into))
48 | .collect::>()?;
49 |
50 | Ok(AllAccountsResponse { accounts })
51 | }
52 |
53 | #[cfg(test)]
54 | mod tests {
55 | use super::*;
56 |
57 | use cosmwasm_std::testing::{mock_dependencies_with_balance, mock_env, mock_info};
58 | use cosmwasm_std::{coins, DepsMut, Uint128};
59 | use cw20::{Cw20Coin, Expiration, TokenInfoResponse};
60 |
61 | use crate::contract::{execute, instantiate, query_token_info};
62 | use crate::msg::{ExecuteMsg, InstantiateMsg};
63 |
64 | // this will set up the instantiation for other tests
65 | fn do_instantiate(mut deps: DepsMut, addr: &str, amount: Uint128) -> TokenInfoResponse {
66 | let instantiate_msg = InstantiateMsg {
67 | name: "Auto Gen".to_string(),
68 | symbol: "AUTO".to_string(),
69 | decimals: 3,
70 | initial_balances: vec![Cw20Coin {
71 | address: addr.into(),
72 | amount,
73 | }],
74 | mint: None,
75 | marketing: None,
76 | };
77 | let info = mock_info("creator", &[]);
78 | let env = mock_env();
79 | instantiate(deps.branch(), env, info, instantiate_msg).unwrap();
80 | query_token_info(deps.as_ref()).unwrap()
81 | }
82 |
83 | #[test]
84 | fn query_all_allowances_works() {
85 | let mut deps = mock_dependencies_with_balance(&coins(2, "token"));
86 |
87 | let owner = String::from("owner");
88 | // these are in alphabetical order same than insert order
89 | let spender1 = String::from("earlier");
90 | let spender2 = String::from("later");
91 |
92 | let info = mock_info(owner.as_ref(), &[]);
93 | let env = mock_env();
94 | do_instantiate(deps.as_mut(), &owner, Uint128::new(12340000));
95 |
96 | // no allowance to start
97 | let allowances = query_all_allowances(deps.as_ref(), owner.clone(), None, None).unwrap();
98 | assert_eq!(allowances.allowances, vec![]);
99 |
100 | // set allowance with height expiration
101 | let allow1 = Uint128::new(7777);
102 | let expires = Expiration::AtHeight(5432);
103 | let msg = ExecuteMsg::IncreaseAllowance {
104 | spender: spender1.clone(),
105 | amount: allow1,
106 | expires: Some(expires),
107 | };
108 | execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap();
109 |
110 | // set allowance with no expiration
111 | let allow2 = Uint128::new(54321);
112 | let msg = ExecuteMsg::IncreaseAllowance {
113 | spender: spender2.clone(),
114 | amount: allow2,
115 | expires: None,
116 | };
117 | execute(deps.as_mut(), env, info, msg).unwrap();
118 |
119 | // query list gets 2
120 | let allowances = query_all_allowances(deps.as_ref(), owner.clone(), None, None).unwrap();
121 | assert_eq!(allowances.allowances.len(), 2);
122 |
123 | // first one is spender1 (order of CanonicalAddr uncorrelated with String)
124 | let allowances = query_all_allowances(deps.as_ref(), owner.clone(), None, Some(1)).unwrap();
125 | assert_eq!(allowances.allowances.len(), 1);
126 | let allow = &allowances.allowances[0];
127 | assert_eq!(&allow.spender, &spender1);
128 | assert_eq!(&allow.expires, &expires);
129 | assert_eq!(&allow.allowance, &allow1);
130 |
131 | // next one is spender2
132 | let allowances = query_all_allowances(
133 | deps.as_ref(),
134 | owner,
135 | Some(allow.spender.clone()),
136 | Some(10000),
137 | )
138 | .unwrap();
139 | assert_eq!(allowances.allowances.len(), 1);
140 | let allow = &allowances.allowances[0];
141 | assert_eq!(&allow.spender, &spender2);
142 | assert_eq!(&allow.expires, &Expiration::Never {});
143 | assert_eq!(&allow.allowance, &allow2);
144 | }
145 |
146 | #[test]
147 | fn query_all_accounts_works() {
148 | let mut deps = mock_dependencies_with_balance(&coins(2, "token"));
149 |
150 | // insert order and lexicographical order are different
151 | let acct1 = String::from("acct01");
152 | let acct2 = String::from("zebra");
153 | let acct3 = String::from("nice");
154 | let acct4 = String::from("aaaardvark");
155 | let expected_order = [acct4.clone(), acct1.clone(), acct3.clone(), acct2.clone()];
156 |
157 | do_instantiate(deps.as_mut(), &acct1, Uint128::new(12340000));
158 |
159 | // put money everywhere (to create balanaces)
160 | let info = mock_info(acct1.as_ref(), &[]);
161 | let env = mock_env();
162 | execute(
163 | deps.as_mut(),
164 | env.clone(),
165 | info.clone(),
166 | ExecuteMsg::Transfer {
167 | recipient: acct2,
168 | amount: Uint128::new(222222),
169 | },
170 | )
171 | .unwrap();
172 | execute(
173 | deps.as_mut(),
174 | env.clone(),
175 | info.clone(),
176 | ExecuteMsg::Transfer {
177 | recipient: acct3,
178 | amount: Uint128::new(333333),
179 | },
180 | )
181 | .unwrap();
182 | execute(
183 | deps.as_mut(),
184 | env,
185 | info,
186 | ExecuteMsg::Transfer {
187 | recipient: acct4,
188 | amount: Uint128::new(444444),
189 | },
190 | )
191 | .unwrap();
192 |
193 | // make sure we get the proper results
194 | let accounts = query_all_accounts(deps.as_ref(), None, None).unwrap();
195 | assert_eq!(accounts.accounts, expected_order);
196 |
197 | // let's do pagination
198 | let accounts = query_all_accounts(deps.as_ref(), None, Some(2)).unwrap();
199 | assert_eq!(accounts.accounts, expected_order[0..2].to_vec());
200 |
201 | let accounts =
202 | query_all_accounts(deps.as_ref(), Some(accounts.accounts[1].clone()), Some(1)).unwrap();
203 | assert_eq!(accounts.accounts, expected_order[2..3].to_vec());
204 |
205 | let accounts =
206 | query_all_accounts(deps.as_ref(), Some(accounts.accounts[0].clone()), Some(777))
207 | .unwrap();
208 | assert_eq!(accounts.accounts, expected_order[3..].to_vec());
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/contracts/ultra-token/src/error.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::StdError;
2 | use thiserror::Error;
3 |
4 | #[derive(Error, Debug, PartialEq)]
5 | pub enum ContractError {
6 | #[error("{0}")]
7 | Std(#[from] StdError),
8 |
9 | #[error("Unauthorized")]
10 | Unauthorized {},
11 |
12 | #[error("Cannot set to own account")]
13 | CannotSetOwnAccount {},
14 |
15 | #[error("Invalid zero amount")]
16 | InvalidZeroAmount {},
17 |
18 | #[error("Allowance is expired")]
19 | Expired {},
20 |
21 | #[error("No allowance for this account")]
22 | NoAllowance {},
23 |
24 | #[error("Minting cannot exceed the cap")]
25 | CannotExceedCap {},
26 |
27 | #[error("Logo binary data exceeds 5KB limit")]
28 | LogoTooBig {},
29 |
30 | #[error("Invalid xml preamble for SVG")]
31 | InvalidXmlPreamble {},
32 |
33 | #[error("Invalid png header")]
34 | InvalidPngHeader {},
35 |
36 | #[error("Duplicate initial balance addresses")]
37 | DuplicateInitialBalanceAddresses {},
38 | }
39 |
--------------------------------------------------------------------------------
/contracts/ultra-token/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod allowances;
2 | pub mod contract;
3 | pub mod enumerable;
4 | mod error;
5 | pub mod msg;
6 | pub mod state;
7 |
8 | pub use crate::error::ContractError;
9 |
--------------------------------------------------------------------------------
/contracts/ultra-token/src/msg.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::{Binary, StdError, StdResult, Uint128};
2 | use cw20::{Cw20Coin, Logo, MinterResponse};
3 | use cw_utils::Expiration;
4 | use schemars::JsonSchema;
5 | use serde::{Deserialize, Serialize};
6 |
7 | #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)]
8 | pub struct InstantiateMarketingInfo {
9 | pub project: Option,
10 | pub description: Option,
11 | pub marketing: Option,
12 | pub logo: Option,
13 | }
14 |
15 | #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)]
16 | pub struct InstantiateMsg {
17 | pub name: String,
18 | pub symbol: String,
19 | pub decimals: u8,
20 | pub initial_balances: Vec,
21 | pub mint: Option,
22 | pub marketing: Option,
23 | }
24 |
25 | impl InstantiateMsg {
26 | pub fn get_cap(&self) -> Option {
27 | self.mint.as_ref().and_then(|v| v.cap)
28 | }
29 |
30 | pub fn validate(&self) -> StdResult<()> {
31 | // Check name, symbol, decimals
32 | if !is_valid_name(&self.name) {
33 | return Err(StdError::generic_err(
34 | "Name is not in the expected format (3-50 UTF-8 bytes)",
35 | ));
36 | }
37 | if !is_valid_symbol(&self.symbol) {
38 | return Err(StdError::generic_err(
39 | "Ticker symbol is not in expected format [a-zA-Z\\-]{3,12}",
40 | ));
41 | }
42 | if self.decimals > 18 {
43 | return Err(StdError::generic_err("Decimals must not exceed 18"));
44 | }
45 | Ok(())
46 | }
47 | }
48 |
49 | fn is_valid_name(name: &str) -> bool {
50 | let bytes = name.as_bytes();
51 | if bytes.len() < 3 || bytes.len() > 50 {
52 | return false;
53 | }
54 | true
55 | }
56 |
57 | fn is_valid_symbol(symbol: &str) -> bool {
58 | let bytes = symbol.as_bytes();
59 | if bytes.len() < 3 || bytes.len() > 12 {
60 | return false;
61 | }
62 | for byte in bytes.iter() {
63 | if (*byte != 45) && (*byte < 65 || *byte > 90) && (*byte < 97 || *byte > 122) {
64 | return false;
65 | }
66 | }
67 | true
68 | }
69 |
70 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
71 | #[serde(rename_all = "snake_case")]
72 | pub enum ExecuteMsg {
73 | /// Transfer is a base message to move tokens to another account without triggering actions
74 | Transfer { recipient: String, amount: Uint128 },
75 | /// Burn is a base message to destroy tokens forever
76 | Burn { amount: Uint128 },
77 | /// Send is a base message to transfer tokens to a contract and trigger an action
78 | /// on the receiving contract.
79 | Send {
80 | contract: String,
81 | amount: Uint128,
82 | msg: Binary,
83 | },
84 | /// Only with "approval" extension. Allows spender to access an additional amount tokens
85 | /// from the owner's (env.sender) account. If expires is Some(), overwrites current allowance
86 | /// expiration with this one.
87 | IncreaseAllowance {
88 | spender: String,
89 | amount: Uint128,
90 | expires: Option,
91 | },
92 | /// Only with "approval" extension. Lowers the spender's access of tokens
93 | /// from the owner's (env.sender) account by amount. If expires is Some(), overwrites current
94 | /// allowance expiration with this one.
95 | DecreaseAllowance {
96 | spender: String,
97 | amount: Uint128,
98 | expires: Option,
99 | },
100 | /// Only with "approval" extension. Transfers amount tokens from owner -> recipient
101 | /// if `env.sender` has sufficient pre-approval.
102 | TransferFrom {
103 | owner: String,
104 | recipient: String,
105 | amount: Uint128,
106 | },
107 | /// Only with "approval" extension. Sends amount tokens from owner -> contract
108 | /// if `env.sender` has sufficient pre-approval.
109 | SendFrom {
110 | owner: String,
111 | contract: String,
112 | amount: Uint128,
113 | msg: Binary,
114 | },
115 | /// Only with "approval" extension.
116 | /// TODO: This msg is just a template. It will be redefined after.
117 | SendToPool {
118 | owner: String,
119 | pool_address: String,
120 | amount: Uint128,
121 | msg: Binary,
122 | },
123 | /// Only with "approval" extension.
124 | /// TODO: This msg is just a template. It will be redefined after.
125 | ReturnFromPool {
126 | pool_address: String,
127 | receiver: String,
128 | amount: Uint128,
129 | },
130 | /// Only with the "mintable" extension. If authorized, creates amount new tokens
131 | /// and adds to the recipient balance.
132 | Mint { recipient: String, amount: Uint128 },
133 | /// Only with the "marketing" extension. If authorized, updates marketing metadata.
134 | /// Setting None/null for any of these will leave it unchanged.
135 | /// Setting Some("") will clear this field on the contract storage
136 | UpdateMarketing {
137 | /// A URL pointing to the project behind this token.
138 | project: Option,
139 | /// A longer description of the token and it's utility. Designed for tooltips or such
140 | description: Option,
141 | /// The address (if any) who can update this data structure
142 | marketing: Option,
143 | },
144 | /// If set as the "marketing" role on the contract, upload a new URL, SVG, or PNG for the token
145 | UploadLogo(Logo),
146 | }
147 |
148 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
149 | #[serde(rename_all = "snake_case")]
150 | pub enum QueryMsg {
151 | /// Returns the current balance of the given address, 0 if unset.
152 | /// Return type: BalanceResponse.
153 | Balance { address: String },
154 | /// Returns metadata on the contract - name, decimals, supply, etc.
155 | /// Return type: TokenInfoResponse.
156 | TokenInfo {},
157 | /// Only with "mintable" extension.
158 | /// Returns who can mint and the hard cap on maximum tokens after minting.
159 | /// Return type: MinterResponse.
160 | Minter {},
161 | /// Only with "allowance" extension.
162 | /// Returns how much spender can use from owner account, 0 if unset.
163 | /// Return type: AllowanceResponse.
164 | Allowance { owner: String, spender: String },
165 | /// Only with "enumerable" extension (and "allowances")
166 | /// Returns all allowances this owner has approved. Supports pagination.
167 | /// Return type: AllAllowancesResponse.
168 | AllAllowances {
169 | owner: String,
170 | start_after: Option,
171 | limit: Option,
172 | },
173 | /// Only with "enumerable" extension
174 | /// Returns all accounts that have balances. Supports pagination.
175 | /// Return type: AllAccountsResponse.
176 | AllAccounts {
177 | start_after: Option,
178 | limit: Option,
179 | },
180 | /// Only with "marketing" extension
181 | /// Returns more metadata on the contract to display in the client:
182 | /// - description, logo, project url, etc.
183 | /// Return type: MarketingInfoResponse
184 | MarketingInfo {},
185 | /// Only with "marketing" extension
186 | /// Downloads the embedded logo data (if stored on chain). Errors if no logo data is stored for this
187 | /// contract.
188 | /// Return type: DownloadLogoResponse.
189 | DownloadLogo {},
190 | }
191 |
--------------------------------------------------------------------------------
/contracts/ultra-token/src/state.rs:
--------------------------------------------------------------------------------
1 | use schemars::JsonSchema;
2 | use serde::{Deserialize, Serialize};
3 |
4 | use cosmwasm_std::{Addr, Uint128};
5 | use cw_storage_plus::{Item, Map};
6 |
7 | use cw20::{AllowanceResponse, Logo, MarketingInfoResponse};
8 |
9 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
10 | #[serde(rename_all = "snake_case")]
11 | pub struct TokenInfo {
12 | pub name: String,
13 | pub symbol: String,
14 | pub decimals: u8,
15 | pub total_supply: Uint128,
16 | pub mint: Option,
17 | }
18 |
19 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
20 | pub struct MinterData {
21 | pub minter: Addr,
22 | /// cap is how many more tokens can be issued by the minter
23 | pub cap: Option,
24 | }
25 |
26 | impl TokenInfo {
27 | pub fn get_cap(&self) -> Option {
28 | self.mint.as_ref().and_then(|v| v.cap)
29 | }
30 | }
31 |
32 | pub const TOKEN_INFO: Item = Item::new("token_info");
33 | pub const MARKETING_INFO: Item = Item::new("marketing_info");
34 | pub const LOGO: Item = Item::new("logo");
35 | pub const BALANCES: Map<&Addr, Uint128> = Map::new("balance");
36 | pub const ALLOWANCES: Map<(&Addr, &Addr), AllowanceResponse> = Map::new("allowance");
37 |
--------------------------------------------------------------------------------
/packages/ultra-base/.cargo/config:
--------------------------------------------------------------------------------
1 | [alias]
2 | wasm = "build --release --target wasm32-unknown-unknown"
3 | wasm-debug = "build --target wasm32-unknown-unknown"
4 | unit-test = "test --lib"
5 |
--------------------------------------------------------------------------------
/packages/ultra-base/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "ultra-base"
3 | version = "0.1.0"
4 | authors = ["Chinh D.Nguyen "]
5 | edition = "2021"
6 | description = "Commom types, queriers and other utils"
7 | license = "Apache-2.0"
8 | repository = "https://github.com/notional-labs/UltraStableJuno"
9 |
10 | [lib]
11 | crate-type = ["cdylib", "rlib"]
12 |
13 | [features]
14 | backtraces = ["cosmwasm-std/backtraces"]
15 | # use library feature to disable all instantiate/execute/query exports
16 | library = []
17 |
18 | [dependencies]
19 | cw20 = { version = "0.10.0" }
20 | cosmwasm-std = { version = "1.0", features = ["iterator"] }
21 | schemars = "0.8"
22 | serde = { version = "1.0.103", default-features = false, features = ["derive"] }
23 | uint = "0.9"
24 | cw-storage-plus = {version = "0.13.4", features = ['iterator']}
25 | wasmswap = { git = "https://github.com/wasmswap/wasmswap-contracts.git", branch="main" }
26 |
27 | [dev-dependencies]
28 | cosmwasm-schema = "1.0"
--------------------------------------------------------------------------------
/packages/ultra-base/src/active_pool.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::{Addr, Uint128};
2 | use schemars::JsonSchema;
3 | use serde::{Deserialize, Serialize};
4 |
5 | #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)]
6 | pub struct InstantiateMsg {
7 | pub name: String,
8 | pub owner: String,
9 | }
10 |
11 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
12 | #[serde(rename_all = "snake_case")]
13 | pub enum ExecuteMsg {
14 | DecreaseULTRADebt {
15 | amount: Uint128,
16 | },
17 | IncreaseULTRADebt {
18 | amount: Uint128,
19 | },
20 | SendJUNO {
21 | recipient: Addr,
22 | amount: Uint128,
23 | },
24 | SetAddresses {
25 | borrower_operations_address: String,
26 | trove_manager_address: String,
27 | stability_pool_address: String,
28 | default_pool_address: String,
29 | },
30 | }
31 |
32 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
33 | #[serde(rename_all = "snake_case")]
34 | pub enum QueryMsg {
35 | GetParams {},
36 | GetJUNO {},
37 | GetULTRADebt {},
38 | GetBorrowerOperationsAddress {},
39 | GetStabilityPoolAddress {},
40 | GetDefaultPoolAddress {},
41 | GetTroveManagerAddress {},
42 | }
43 |
44 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
45 | #[serde(rename_all = "snake_case")]
46 | pub enum SudoMsg {
47 | /// Update the contract parameters
48 | /// Can only be called by governance
49 | UpdateParams {
50 | name: Option,
51 | owner: Option,
52 | },
53 | }
54 |
55 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
56 | pub struct ParamsResponse {
57 | pub name: String,
58 | pub owner: Addr,
59 | }
60 |
--------------------------------------------------------------------------------
/packages/ultra-base/src/asset.rs:
--------------------------------------------------------------------------------
1 | use schemars::JsonSchema;
2 | use serde::{Deserialize, Serialize};
3 | use std::fmt;
4 |
5 | use crate::querier::{query_balance, query_token_balance};
6 | use cosmwasm_std::{
7 | coin, to_binary, Addr, BankMsg, CosmosMsg, MessageInfo, QuerierWrapper, StdError, StdResult,
8 | Uint128, WasmMsg,
9 | };
10 | use cw20::Cw20ExecuteMsg;
11 |
12 | /// JUNO token denomination
13 | pub const UJUNO_DENOM: &str = "ujuno";
14 |
15 | /// This enum describes an asset (native or CW20 token
16 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
17 | pub struct Asset {
18 | pub info: AssetInfo,
19 | pub amount: Uint128,
20 | }
21 |
22 | impl fmt::Display for Asset {
23 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
24 | write!(f, "{}{}", self.amount, self.info)
25 | }
26 | }
27 |
28 | impl Asset {
29 | pub fn is_native_token(&self) -> bool {
30 | self.info.is_native_token()
31 | }
32 |
33 | pub fn into_msg(
34 | self,
35 | _querier: &QuerierWrapper,
36 | recipient: impl Into,
37 | ) -> StdResult {
38 | let recipient = recipient.into();
39 | match &self.info {
40 | AssetInfo::Cw20Token { contract_addr } => Ok(CosmosMsg::Wasm(WasmMsg::Execute {
41 | contract_addr: contract_addr.to_string(),
42 | msg: to_binary(&Cw20ExecuteMsg::Transfer {
43 | recipient,
44 | amount: self.amount,
45 | })?,
46 | funds: vec![],
47 | })),
48 | AssetInfo::NativeToken { denom } => Ok(CosmosMsg::Bank(BankMsg::Send {
49 | to_address: recipient,
50 | amount: vec![coin(self.amount.u128(), denom)],
51 | })),
52 | }
53 | }
54 |
55 | pub fn assert_sent_native_token_balance(&self, message_info: &MessageInfo) -> StdResult<()> {
56 | if let AssetInfo::NativeToken { denom } = &self.info {
57 | match message_info.funds.iter().find(|x| x.denom == *denom) {
58 | Some(coin) => {
59 | if self.amount == coin.amount {
60 | Ok(())
61 | } else {
62 | Err(StdError::generic_err("Native token balance mismatch between the argument and the transferred"))
63 | }
64 | }
65 | None => {
66 | if self.amount.is_zero() {
67 | Ok(())
68 | } else {
69 | Err(StdError::generic_err("Native token balance mismatch between the argument and the transferred"))
70 | }
71 | }
72 | }
73 | } else {
74 | Ok(())
75 | }
76 | }
77 | }
78 |
79 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
80 | #[serde(rename_all = "snake_case")]
81 | pub enum AssetInfo {
82 | Cw20Token { contract_addr: Addr },
83 | NativeToken { denom: String },
84 | }
85 |
86 | impl fmt::Display for AssetInfo {
87 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
88 | match self {
89 | AssetInfo::NativeToken { denom } => write!(f, "{}", denom),
90 | AssetInfo::Cw20Token { contract_addr } => write!(f, "{}", contract_addr),
91 | }
92 | }
93 | }
94 |
95 | impl AssetInfo {
96 | pub fn is_native_token(&self) -> bool {
97 | match self {
98 | AssetInfo::NativeToken { .. } => true,
99 | AssetInfo::Cw20Token { .. } => false,
100 | }
101 | }
102 |
103 | /// Returns the balance of token in a pool.
104 | pub fn query_pool(
105 | &self,
106 | querier: &QuerierWrapper,
107 | pool_addr: impl Into,
108 | ) -> StdResult {
109 | match self {
110 | AssetInfo::Cw20Token { contract_addr, .. } => {
111 | query_token_balance(querier, contract_addr, pool_addr)
112 | }
113 | AssetInfo::NativeToken { denom } => query_balance(querier, pool_addr, denom),
114 | }
115 | }
116 |
117 | /// Returns **true** if the calling token is the same as the token specified in the input parameters.
118 | pub fn equal(&self, asset: &AssetInfo) -> bool {
119 | match (self, asset) {
120 | (AssetInfo::NativeToken { denom }, AssetInfo::NativeToken { denom: other_denom }) => {
121 | denom == other_denom
122 | }
123 | (
124 | AssetInfo::Cw20Token { contract_addr },
125 | AssetInfo::Cw20Token {
126 | contract_addr: other_contract_addr,
127 | },
128 | ) => contract_addr == other_contract_addr,
129 | _ => false,
130 | }
131 | }
132 |
133 | /// If the caller object is a native token of type ['AssetInfo`] then his `denom` field converts to a byte string.
134 | /// If the caller object is a token of type ['AssetInfo`] then his `contract_addr` field converts to a byte string.
135 | /// ## Params
136 | /// * **self** is the type of the caller object.
137 | pub fn as_bytes(&self) -> &[u8] {
138 | match self {
139 | AssetInfo::NativeToken { denom } => denom.as_bytes(),
140 | AssetInfo::Cw20Token { contract_addr } => contract_addr.as_bytes(),
141 | }
142 | }
143 | }
144 |
145 | pub fn native_asset(denom: String, amount: Uint128) -> Asset {
146 | Asset {
147 | info: AssetInfo::NativeToken { denom },
148 | amount,
149 | }
150 | }
151 |
152 | pub fn token_asset(contract_addr: Addr, amount: Uint128) -> Asset {
153 | Asset {
154 | info: AssetInfo::Cw20Token { contract_addr },
155 | amount,
156 | }
157 | }
158 |
159 | pub fn native_asset_info(denom: String) -> AssetInfo {
160 | AssetInfo::NativeToken { denom }
161 | }
162 |
163 | pub fn token_asset_info(contract_addr: Addr) -> AssetInfo {
164 | AssetInfo::Cw20Token { contract_addr }
165 | }
166 |
167 | /// This structure stores the main parameters for an JunoSwap pool
168 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
169 | pub struct PoolInfo {
170 | pub token1_reserve: Uint128,
171 | pub token1_denom: AssetInfo,
172 | pub token2_reserve: Uint128,
173 | pub token2_denom: AssetInfo,
174 | pub lp_token_address: String,
175 | pub lp_token_supply: Uint128,
176 | }
177 |
178 | impl PoolInfo {
179 | /// Returns the balance for each asset in the pool.
180 | pub fn query_pools(
181 | &self,
182 | querier: &QuerierWrapper,
183 | contract_addr: impl Into,
184 | ) -> StdResult<[Asset; 2]> {
185 | let contract_addr = contract_addr.into();
186 | Ok([
187 | Asset {
188 | amount: self.token1_denom.query_pool(querier, &contract_addr)?,
189 | info: self.token1_denom.clone(),
190 | },
191 | Asset {
192 | amount: self.token2_denom.query_pool(querier, contract_addr)?,
193 | info: self.token2_denom.clone(),
194 | },
195 | ])
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/packages/ultra-base/src/borrower_operations.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::{Addr, Decimal256, Uint128};
2 | use schemars::JsonSchema;
3 | use serde::{Deserialize, Serialize};
4 |
5 | #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)]
6 | pub struct InstantiateMsg {
7 | pub name: String,
8 | pub owner: String,
9 | }
10 |
11 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
12 | #[serde(rename_all = "snake_case")]
13 | pub enum ExecuteMsg {
14 | /// Send JUNO as collateral to a trove
15 | AddColl {},
16 | /// Alongside a debt change, this function can perform either a collateral top-up or a collateral withdrawal.
17 | AdjustTrove {
18 | borrower: Addr,
19 | coll_withdrawal: Uint128,
20 | ultra_change: Uint128,
21 | is_debt_increase: bool,
22 | max_fee_percentage: Decimal256,
23 | },
24 | /// Claim remaining collateral from a redemption or from a liquidation with ICR > MCR in Recovery Mode
25 | ClaimCollateral {},
26 | CloseTrove {},
27 | /// Send JUNO as collateral to a trove. Called by only the Stability Pool.
28 | MoveJUNOGainToTrove {
29 | borrower: Addr,
30 | },
31 | OpenTrove {
32 | max_fee_percentage: Decimal256,
33 | ultra_amount: Uint128,
34 | },
35 | /// Burn the specified amount of ULTRA from `account` and decreases the total active debt
36 | RepayULTRA {
37 | active_pool_addr: Addr,
38 | ultra_token_addr: Addr,
39 | account: Addr,
40 | ultra_amount: Uint128,
41 | },
42 | SetAddresses {
43 | trove_manager_address: String,
44 | active_pool_address: String,
45 | default_pool_address: String,
46 | stability_pool_address: String,
47 | coll_surplus_pool_address: String,
48 | price_feed_contract_address: String,
49 | ultra_token_address: String,
50 | reward_pool_address: String,
51 | },
52 | /// Withdraw JUNO collateral from a trove
53 | WithdrawColl {
54 | coll_amount: Uint128,
55 | },
56 | /// Withdraw ULTRA tokens from a trove: mint new ULTRA tokens to the owner, and increase the trove's debt accordingly
57 | WithdrawULTRA {
58 | max_fee_percentage: Uint128,
59 | ultra_amount: Uint128,
60 | },
61 | }
62 |
63 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
64 | #[serde(rename_all = "snake_case")]
65 | pub enum QueryMsg {
66 | GetParams {},
67 | GetCompositeDebt { debt: Uint128 },
68 | GetEntireSystemColl {},
69 | GetEntireSystemDebt {},
70 | GetActivePoolAddress {},
71 | GetDefaultPoolAddress {},
72 | GetTroveManagerAddress {},
73 | GetULTRATokenContractAddress {},
74 | GetPriceFeedContractAddress {},
75 | GetRewardPoolAddress {},
76 | }
77 |
78 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
79 | #[serde(rename_all = "snake_case")]
80 | pub enum SudoMsg {
81 | /// Update the contract parameters
82 | /// Can only be called by governance
83 | UpdateParams {
84 | name: Option,
85 | owner: Option,
86 | },
87 | }
88 |
89 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
90 | pub struct ParamsResponse {
91 | pub name: String,
92 | pub owner: Addr,
93 | }
94 |
--------------------------------------------------------------------------------
/packages/ultra-base/src/coll_surplus_pool.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::{Addr, Uint128};
2 | use schemars::JsonSchema;
3 | use serde::{Deserialize, Serialize};
4 |
5 | #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)]
6 | pub struct InstantiateMsg {
7 | pub name: String,
8 | pub owner: String,
9 | }
10 |
11 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
12 | #[serde(rename_all = "snake_case")]
13 | pub enum ExecuteMsg {
14 | AccountSurplus {
15 | account: Addr,
16 | amount: Uint128,
17 | },
18 | ClaimColl {
19 | account: Addr,
20 | },
21 | SetAddresses {
22 | borrower_operations_address: String,
23 | trove_manager_address: String,
24 | active_pool_address: String,
25 | },
26 | }
27 |
28 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
29 | #[serde(rename_all = "snake_case")]
30 | pub enum QueryMsg {
31 | GetParams {},
32 | GetJUNO {},
33 | GetCollateral { account: Addr },
34 | GetBorrowerOperationsAddress {},
35 | GetActivePoolAddress {},
36 | GetTroveManagerAddress {},
37 | }
38 |
39 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
40 | #[serde(rename_all = "snake_case")]
41 | pub enum SudoMsg {
42 | /// Update the contract parameters
43 | /// Can only be called by governance
44 | UpdateParams {
45 | name: Option,
46 | owner: Option,
47 | },
48 | }
49 |
50 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
51 | pub struct ParamsResponse {
52 | pub name: String,
53 | pub owner: Addr,
54 | }
55 |
--------------------------------------------------------------------------------
/packages/ultra-base/src/default_pool.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::{Addr, Uint128};
2 | use schemars::JsonSchema;
3 | use serde::{Deserialize, Serialize};
4 |
5 | #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)]
6 | pub struct InstantiateMsg {
7 | pub name: String,
8 | pub owner: String,
9 | }
10 |
11 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
12 | #[serde(rename_all = "snake_case")]
13 | pub enum ExecuteMsg {
14 | DecreaseULTRADebt {
15 | amount: Uint128,
16 | },
17 | IncreaseULTRADebt {
18 | amount: Uint128,
19 | },
20 | SendJUNOToActivePool {
21 | amount: Uint128,
22 | },
23 | SetAddresses {
24 | trove_manager_address: String,
25 | active_pool_address: String,
26 | },
27 | }
28 |
29 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
30 | #[serde(rename_all = "snake_case")]
31 | pub enum QueryMsg {
32 | GetParams {},
33 | GetJUNO {},
34 | GetULTRADebt {},
35 | GetActivePoolAddress {},
36 | GetTroveManagerAddress {},
37 | }
38 |
39 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
40 | #[serde(rename_all = "snake_case")]
41 | pub enum SudoMsg {
42 | /// Update the contract parameters
43 | /// Can only be called by governance
44 | UpdateParams {
45 | name: Option,
46 | owner: Option,
47 | },
48 | }
49 |
50 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
51 | pub struct ParamsResponse {
52 | pub name: String,
53 | pub owner: Addr,
54 | }
55 |
--------------------------------------------------------------------------------
/packages/ultra-base/src/hint_helpers.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::{Addr, Uint128};
2 | use schemars::JsonSchema;
3 | use serde::{Deserialize, Serialize};
4 |
5 | #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)]
6 | pub struct InstantiateMsg {
7 | pub name: String,
8 | pub owner: String,
9 | }
10 |
11 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
12 | #[serde(rename_all = "snake_case")]
13 | pub enum ExecuteMsg {
14 | ComputeNominalCR {
15 | coll: Uint128,
16 | debt: Uint128,
17 | },
18 | ComputeCR {
19 | coll: Uint128,
20 | debt: Uint128,
21 | price: Uint128,
22 | },
23 | SetAddresses {
24 | sorted_troves_address: String,
25 | trove_manager_address: String,
26 | },
27 | }
28 |
29 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
30 | #[serde(rename_all = "snake_case")]
31 | pub enum QueryMsg {
32 | GetParams {},
33 | GetRedemptionHints {
34 | ultra_amount: Uint128,
35 | price: Uint128,
36 | max_iterations: Uint128,
37 | },
38 | GetApproxHint {cr: Uint128, num_trials: Uint128, input_random_seed: Uint128},
39 | GetSortedTrovesAddress {},
40 | GetTroveManagerAddress {},
41 | }
42 |
43 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
44 | #[serde(rename_all = "snake_case")]
45 | pub enum SudoMsg {
46 | /// Update the contract parameters
47 | /// Can only be called by governance
48 | UpdateParams {
49 | name: Option,
50 | owner: Option,
51 | },
52 | }
53 |
54 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
55 | pub struct ParamsResponse {
56 | pub name: String,
57 | pub owner: Addr,
58 | }
59 |
--------------------------------------------------------------------------------
/packages/ultra-base/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod active_pool;
2 | pub mod asset;
3 | pub mod borrower_operations;
4 | pub mod coll_surplus_pool;
5 | pub mod default_pool;
6 | pub mod hint_helpers;
7 | pub mod oracle;
8 | pub mod querier;
9 | pub mod sorted_troves;
10 | pub mod stability_pool;
11 | pub mod trove_manager;
12 | pub mod ultra_math;
13 |
--------------------------------------------------------------------------------
/packages/ultra-base/src/oracle.rs:
--------------------------------------------------------------------------------
1 | use crate::asset::AssetInfo;
2 | use cosmwasm_std::Uint128;
3 | use schemars::JsonSchema;
4 | use serde::{Deserialize, Serialize};
5 |
6 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
7 | pub struct InstantiateMsg {
8 | pub pool_contract_address: String,
9 | }
10 |
11 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
12 | #[serde(rename_all = "snake_case")]
13 | pub enum ExecuteMsg {
14 | /// Update/accumulate prices
15 | Update {},
16 | }
17 |
18 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
19 | #[serde(rename_all = "snake_case")]
20 | pub enum QueryMsg {
21 | /// Calculates a new TWAP with updated precision
22 | Consult {
23 | /// The asset for which to compute a new TWAP value
24 | token: AssetInfo,
25 | /// The amount of tokens for which to compute the token price
26 | amount: Uint128,
27 | },
28 | }
29 |
30 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
31 | pub struct MigrateMsg {}
32 |
--------------------------------------------------------------------------------
/packages/ultra-base/src/querier.rs:
--------------------------------------------------------------------------------
1 | use crate::active_pool::QueryMsg as ActivePoolQueryMsg;
2 | use crate::asset::{AssetInfo, PoolInfo};
3 | use crate::default_pool::QueryMsg as DefaultPoolQueryMsg;
4 | use crate::ultra_math;
5 |
6 | use cosmwasm_std::{
7 | Addr, AllBalanceResponse, BankQuery, Coin, Decimal256, QuerierWrapper, QueryRequest, StdError,
8 | StdResult, Uint128, Uint256,
9 | };
10 | use wasmswap::msg::{InfoResponse, QueryMsg as WasmSwapMsg};
11 |
12 | use cw20::{BalanceResponse as Cw20BalanceResponse, Cw20QueryMsg, Denom, TokenInfoResponse};
13 |
14 | const NATIVE_TOKEN_PRECISION: u8 = 6;
15 |
16 | // Minimum collateral ratio for individual troves
17 | pub const MCR: Decimal256 = Decimal256::new(Uint256::from_u128(1_100_000_000_000_000_000u128));
18 |
19 | // Critical system collateral ratio. If the system's total collateral ratio (TCR) falls below the CCR, Recovery Mode is triggered.
20 | pub const CCR: Decimal256 = Decimal256::new(Uint256::from_u128(1_500_000_000_000_000_000u128));
21 |
22 | // Minimum amount of net ULTRA debt a trove must have
23 | pub const MIN_NET_DEBT: Uint128 = Uint128::new(2000u128);
24 |
25 | pub const BORROWING_FEE_FLOOR: Decimal256 =
26 | Decimal256::new(Uint256::from_u128(5_000_000_000_000_000u128)); // 0.5%
27 |
28 | /// Returns a native token's balance for a specific account.
29 | pub fn query_balance(
30 | querier: &QuerierWrapper,
31 | account_addr: impl Into,
32 | denom: impl Into,
33 | ) -> StdResult {
34 | querier
35 | .query_balance(account_addr, denom)
36 | .map(|coin| coin.amount)
37 | }
38 |
39 | /// Returns the total balances for all coins at a specified account address.
40 | pub fn query_all_balances(querier: &QuerierWrapper, account_addr: Addr) -> StdResult> {
41 | let all_balances: AllBalanceResponse =
42 | querier.query(&QueryRequest::Bank(BankQuery::AllBalances {
43 | address: String::from(account_addr),
44 | }))?;
45 | Ok(all_balances.amount)
46 | }
47 |
48 | /// Returns a cw20 token balance for an account.
49 | pub fn query_token_balance(
50 | querier: &QuerierWrapper,
51 | contract_addr: impl Into,
52 | account_addr: impl Into,
53 | ) -> StdResult {
54 | let resp: Cw20BalanceResponse = querier
55 | .query_wasm_smart(
56 | contract_addr,
57 | &Cw20QueryMsg::Balance {
58 | address: account_addr.into(),
59 | },
60 | )
61 | .unwrap_or_else(|_| Cw20BalanceResponse {
62 | balance: Uint128::zero(),
63 | });
64 |
65 | Ok(resp.balance)
66 | }
67 |
68 | /// Returns a cw20 token's symbol.
69 | pub fn query_token_symbol(
70 | querier: &QuerierWrapper,
71 | contract_addr: impl Into,
72 | ) -> StdResult {
73 | let res: TokenInfoResponse =
74 | querier.query_wasm_smart(contract_addr, &Cw20QueryMsg::TokenInfo {})?;
75 |
76 | Ok(res.symbol)
77 | }
78 |
79 | /// Returns the total supply of a specific cw20 token.
80 | pub fn query_supply(
81 | querier: &QuerierWrapper,
82 | contract_addr: impl Into,
83 | ) -> StdResult {
84 | let res: TokenInfoResponse =
85 | querier.query_wasm_smart(contract_addr, &Cw20QueryMsg::TokenInfo {})?;
86 |
87 | Ok(res.total_supply)
88 | }
89 |
90 | /// Returns the number of decimals that a token (native or cw20 token) has.
91 | pub fn query_token_precision(querier: &QuerierWrapper, asset_info: &AssetInfo) -> StdResult {
92 | let decimals = match asset_info {
93 | AssetInfo::NativeToken { .. } => NATIVE_TOKEN_PRECISION,
94 | AssetInfo::Cw20Token { contract_addr } => {
95 | let res: TokenInfoResponse =
96 | querier.query_wasm_smart(contract_addr, &Cw20QueryMsg::TokenInfo {})?;
97 |
98 | res.decimals
99 | }
100 | };
101 |
102 | Ok(decimals)
103 | }
104 |
105 | /// Returns JunoSwap pool information.
106 | pub fn query_pool_info(querier: &QuerierWrapper, pool_contract_addr: Addr) -> StdResult {
107 | let pool_info: InfoResponse =
108 | querier.query_wasm_smart(pool_contract_addr, &WasmSwapMsg::Info {})?;
109 |
110 | let token1_denom: AssetInfo = match pool_info.token1_denom {
111 | Denom::Native(denom) => AssetInfo::NativeToken { denom },
112 | Denom::Cw20(contract_addr) => AssetInfo::Cw20Token { contract_addr },
113 | };
114 |
115 | let token2_denom: AssetInfo = match pool_info.token2_denom {
116 | Denom::Native(denom) => AssetInfo::NativeToken { denom },
117 | Denom::Cw20(contract_addr) => AssetInfo::Cw20Token { contract_addr },
118 | };
119 |
120 | let res = PoolInfo {
121 | token1_reserve: pool_info.token1_reserve,
122 | token1_denom,
123 | token2_reserve: pool_info.token2_reserve,
124 | token2_denom,
125 | lp_token_address: pool_info.lp_token_address,
126 | lp_token_supply: pool_info.lp_token_supply,
127 | };
128 | Ok(res)
129 | }
130 |
131 | pub fn query_entire_system_coll(
132 | querier: &QuerierWrapper,
133 | active_pool_addr: Addr,
134 | default_pool_addr: Addr,
135 | ) -> StdResult {
136 | let active_coll: Uint128 =
137 | querier.query_wasm_smart(active_pool_addr, &ActivePoolQueryMsg::GetJUNO {})?;
138 | let liquidated_coll: Uint128 =
139 | querier.query_wasm_smart(default_pool_addr, &DefaultPoolQueryMsg::GetJUNO {})?;
140 | let total = active_coll
141 | .checked_add(liquidated_coll)
142 | .map_err(StdError::overflow)?;
143 |
144 | Ok(total)
145 | }
146 |
147 | pub fn query_entire_system_debt(
148 | querier: &QuerierWrapper,
149 | active_pool_addr: Addr,
150 | default_pool_addr: Addr,
151 | ) -> StdResult {
152 | let active_debt: Uint128 =
153 | querier.query_wasm_smart(active_pool_addr, &ActivePoolQueryMsg::GetULTRADebt {})?;
154 | let liquidated_debt: Uint128 =
155 | querier.query_wasm_smart(default_pool_addr, &DefaultPoolQueryMsg::GetULTRADebt {})?;
156 | let total = active_debt
157 | .checked_add(liquidated_debt)
158 | .map_err(StdError::overflow)?;
159 |
160 | Ok(total)
161 | }
162 |
163 | pub fn get_tcr(
164 | querier: &QuerierWrapper,
165 | price: Decimal256,
166 | active_pool_addr: Addr,
167 | default_pool_addr: Addr,
168 | ) -> StdResult {
169 | let entire_system_coll =
170 | query_entire_system_debt(querier, active_pool_addr.clone(), default_pool_addr.clone())
171 | .unwrap();
172 | let entire_system_debt =
173 | query_entire_system_coll(querier, active_pool_addr, default_pool_addr).unwrap();
174 | let tcr = ultra_math::compute_cr(entire_system_coll, entire_system_debt, price).unwrap();
175 | Ok(tcr)
176 | }
177 |
178 | pub fn check_recovery_mode(
179 | querier: &QuerierWrapper,
180 | price: Decimal256,
181 | active_pool_addr: Addr,
182 | default_pool_addr: Addr,
183 | ) -> bool {
184 | let tcr = get_tcr(querier, price, active_pool_addr, default_pool_addr).unwrap();
185 | tcr < CCR
186 | }
187 |
--------------------------------------------------------------------------------
/packages/ultra-base/src/sorted_troves.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::{Addr, Uint256};
2 | use schemars::JsonSchema;
3 | use serde::{Deserialize, Serialize};
4 |
5 | #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)]
6 | pub struct InstantiateMsg {
7 | pub name: String,
8 | pub owner: String,
9 | }
10 |
11 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
12 | #[serde(rename_all = "snake_case")]
13 | pub enum ExecuteMsg {
14 | Insert {
15 | id: String,
16 | nicr: Uint256,
17 | prev_id: String,
18 | next_id: String,
19 | },
20 | ReInsert {
21 | id: String,
22 | new_nicr: Uint256,
23 | prev_id: String,
24 | next_id: String,
25 | },
26 | Remove {
27 | id: String,
28 | },
29 | SetParams {
30 | size: Uint256,
31 | borrower_operation_address: String,
32 | trove_manager_address: String,
33 | },
34 | }
35 |
36 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
37 | pub enum QueryMsg {
38 | GetParams {},
39 | GetData {},
40 | GetSize {},
41 | GetMaxSize {},
42 | GetFirst {},
43 | GetLast {},
44 | GetNext {
45 | id: String,
46 | },
47 | GetPrev {
48 | id: String,
49 | },
50 | FindInsertPosition {
51 | nicr: Uint256,
52 | prev_id: String,
53 | next_id: String,
54 | },
55 | ValidInsertPosition {
56 | nicr: Uint256,
57 | prev_id: String,
58 | next_id: String,
59 | },
60 | IsEmpty {},
61 | IsFull {},
62 | GetBorrowerOperationAddress {},
63 | GetTroveManagerAddress {},
64 | }
65 |
66 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
67 | #[serde(rename_all = "snake_case")]
68 | pub enum SudoMsg {
69 | /// Update the contract parameters
70 | /// Can only be called by governance
71 | UpdateParams {
72 | name: Option,
73 | owner: Option,
74 | },
75 | }
76 |
77 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
78 | pub struct ParamsResponse {
79 | pub name: String,
80 | pub owner: Addr,
81 | }
82 |
--------------------------------------------------------------------------------
/packages/ultra-base/src/stability_pool.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::Addr;
2 | use schemars::JsonSchema;
3 | use serde::{Deserialize, Serialize};
4 |
5 | #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)]
6 | pub struct InstantiateMsg {
7 | pub name: String,
8 | pub owner: String,
9 | }
10 |
11 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
12 | #[serde(rename_all = "snake_case")]
13 | pub enum ExecuteMsg {
14 | ProvideToSP {},
15 | WithdrawFromSP {},
16 | WithdrawJUNOGainToTrove {},
17 | RegisterFrontEnd {},
18 | Offset {},
19 | SetAddresses {
20 | borrower_operations_address: String,
21 | trove_manager_address: String,
22 | active_pool_address: String,
23 | ultra_token_address: String,
24 | sorted_troves_address: String,
25 | price_feed_address: String,
26 | },
27 | }
28 |
29 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
30 | #[serde(rename_all = "snake_case")]
31 | pub enum QueryMsg {
32 | GetParams {},
33 | GetCurrentEpoch {},
34 | GetCurrentScale {},
35 | GetDeposits { input: String },
36 | GetDepositSnapshot { input: String },
37 | GetFrontEnds { input: String },
38 | GetFrontEndStakes { input: String },
39 | GetFrontEndSnapshots { input: String },
40 | GetFrontEndRewardGain { frontend: String },
41 | GetDepositorJUNOGain { depositor: String },
42 | GetDepositorRewardGain { depositor: String },
43 | GetLastJUNOErrorOffset {},
44 | GetLastRewardError {},
45 | GetLastUltraLossErrorOffset {},
46 | GetJUNO {},
47 | GetTotalUltraDeposits {},
48 | GetCompoundedFrontEndStake {},
49 | GetCompoundedUltraDeposit {},
50 | GetBorrowerOperationsAddress {},
51 | GetTroveManagerAddress {},
52 | GetActivePoolAddress {},
53 | GetUltraTokenAddress {},
54 | GetSortedTrovesAddress {},
55 | GetPriceFeedAddress {},
56 | }
57 |
58 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
59 | #[serde(rename_all = "snake_case")]
60 | pub enum SudoMsg {
61 | /// Update the contract parameters
62 | /// Can only be called by governance
63 | UpdateParams {
64 | name: Option,
65 | owner: Option,
66 | },
67 | }
68 |
69 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
70 | pub struct ParamsResponse {
71 | pub name: String,
72 | pub owner: Addr,
73 | }
74 |
--------------------------------------------------------------------------------
/packages/ultra-base/src/trove_manager.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::{Addr, Uint128};
2 | use schemars::JsonSchema;
3 | use serde::{Deserialize, Serialize};
4 |
5 | #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)]
6 | pub struct InstantiateMsg {
7 | pub name: String,
8 | pub owner: String,
9 | }
10 |
11 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
12 | #[serde(rename_all = "snake_case")]
13 | pub enum ExecuteMsg {
14 | ///--- Trove Liquidation functions ---
15 | // Single liquidation function. Closes the trove if its ICR is lower than the minimum collateral ratio.
16 | Liquidate {
17 | borrower: String,
18 | },
19 | // Liquidate a sequence of troves. Closes a maximum number of n under-collateralized Troves,
20 | // starting from the one with the lowest collateral ratio in the system, and moving upwards
21 | LiquidateTroves {
22 | n: Uint128,
23 | },
24 | // Attempt to liquidate a custom list of troves provided by the caller.
25 | BatchLiquidateTroves {},
26 | // Send ultra_amount $ULTRA to the system and redeem the corresponding amount of collateral from as many Troves
27 | // as are needed to fill the redemption request.
28 | RedeemCollateral {
29 | ultra_amount: Uint128,
30 | first_redemption_hint: String,
31 | upper_partial_redemption_hint: String,
32 | lower_partial_redemption_hint: String,
33 | max_iterations: Uint128,
34 | max_fee_percentage: Uint128,
35 | },
36 | // Add the borrowers's coll and debt rewards earned from redistributions, to their Trove
37 | ApplyPendingRewards {
38 | borrower: String,
39 | },
40 | // Update borrower's snapshots of L_JUNO and L_ULTRADebt to reflect the current values
41 | UpdateTroveRewardSnapshots {
42 | borrower: String,
43 | },
44 | // Remove borrower's stake from the totalStakes sum, and set their stake to 0
45 | RemoveStake {
46 | borrower: String,
47 | },
48 | // Update borrower's stake based on their latest collateral value
49 | UpdateStakeAndTotalStakes {
50 | borrower: String,
51 | },
52 | // Close a Trove
53 | CloseTrove {
54 | borrower: String,
55 | },
56 | // Push the owner's address to the Trove owners list, and record the corresponding array index on the Trove struct
57 | AddTroveOwnerToArray {
58 | borrower: String,
59 | },
60 |
61 | /// --- Borrowing fee functions ---
62 | DecayBaseRateFromBorrowing {},
63 |
64 | /// --- Trove property setters, called by BorrowerOperations ---
65 | SetTroveStatus {
66 | borrower: String,
67 | num: Uint128,
68 | },
69 | IncreaseTroveColl {
70 | borrower: String,
71 | coll_increase: Uint128,
72 | },
73 | DecreaseTroveColl {
74 | borrower: String,
75 | coll_decrease: Uint128,
76 | },
77 | IncreaseTroveDebt {
78 | borrower: String,
79 | debt_increase: Uint128,
80 | },
81 | DecreaseTroveDebt {
82 | borrower: String,
83 | debt_decrease: Uint128,
84 | },
85 |
86 | SetAddresses {
87 | borrower_operations_address: String,
88 | active_pool_address: String,
89 | default_pool_address: String,
90 | stability_pool_address: String,
91 | coll_surplus_pool_address: String,
92 | ultra_token_address: String,
93 | sorted_troves_address: String,
94 | price_feed_address: String,
95 | },
96 | }
97 |
98 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
99 | #[serde(rename_all = "snake_case")]
100 | pub enum QueryMsg {
101 | GetParams {},
102 | GetTroveFromTroveOwnersArray { index: Uint128 },
103 | GetTroveOwnersCount {},
104 | GetNominalICR { borrower: String },
105 | GetCurrentICR { borrower: String, price: Uint128 },
106 | GetPendingJUNOReward {},
107 | GetPendingULTRADebtReward {},
108 | GetEntireDebtAndColl { borrower: String },
109 | GetTCR {},
110 | GetBorrowingFee { ultra_debt: Uint128 },
111 | GetBorrowingFeeWithDecay { ultra_debt: Uint128 },
112 | GetBorrowingRate {},
113 | GetBorrowingRateWithDecay {},
114 | GetRedemptionRate {},
115 | GetRedemptionRateWithDecay {},
116 | GetRedemptionFeeWithDecay { juno_drawn: Uint128 },
117 | GetTroveStatus {},
118 | GetTroveStake {},
119 | GetTroveDebt {},
120 | GetTroveColl {},
121 | GetBorrowerOperationsAddress {},
122 | GetTroveManagerAddress {},
123 | GetActivePoolAddress {},
124 | GetULTRATokenAddress {},
125 | GetSortedTrovesAddress {},
126 | GetPriceFeedAddress {},
127 | }
128 |
129 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
130 | #[serde(rename_all = "snake_case")]
131 | pub enum SudoMsg {
132 | /// Update the contract parameters
133 | /// Can only be called by governance
134 | UpdateParams {
135 | name: Option,
136 | owner: Option,
137 | },
138 | }
139 |
140 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
141 | pub struct ParamsResponse {
142 | pub name: String,
143 | pub owner: Addr,
144 | }
145 |
--------------------------------------------------------------------------------
/packages/ultra-base/src/ultra_math.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::{Decimal256, StdError, StdResult, Uint128};
2 |
3 | pub fn compute_cr(coll: Uint128, debt: Uint128, price: Decimal256) -> StdResult {
4 | if debt != Uint128::zero() {
5 | let new_coll_ratio: Decimal256 = Decimal256::from_ratio(coll, debt)
6 | .checked_mul(price)
7 | .map_err(StdError::overflow)?;
8 | Ok(new_coll_ratio)
9 | } else {
10 | Ok(Decimal256::MAX)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test-contracts/price-feed-test/.cargo/config:
--------------------------------------------------------------------------------
1 | [alias]
2 | wasm = "build --release --target wasm32-unknown-unknown"
3 | wasm-debug = "build --target wasm32-unknown-unknown"
4 | schema = "run --example schema"
5 |
--------------------------------------------------------------------------------
/test-contracts/price-feed-test/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "price-feed-test"
3 | version = "0.1.0"
4 | authors = ["Chinh D.Nguyen "]
5 | edition = "2021"
6 |
7 | description = "PriceFeed for testnet and development"
8 | repository = "https://github.com/notional-labs/UltraStableJuno"
9 |
10 | [lib]
11 | crate-type = ["cdylib", "rlib"]
12 |
13 | [features]
14 | backtraces = ["cosmwasm-std/backtraces"]
15 | # use library feature to disable all instantiate/execute/query exports
16 | library = []
17 |
18 | [dependencies]
19 | cw-storage-plus = { version = "0.13.4" }
20 | cosmwasm-std = { version = "1.0.0" }
21 | cw2 = { version = "0.13.4" }
22 | schemars = "0.8.1"
23 | serde = { version = "1.0.103", default-features = false, features = ["derive"] }
24 | thiserror = { version = "1.0.23" }
25 |
26 | [dev-dependencies]
27 | cosmwasm-schema = { version = "1.0.0" }
28 |
--------------------------------------------------------------------------------
/test-contracts/price-feed-test/README.md:
--------------------------------------------------------------------------------
1 | # PriceFeed testnet
2 | PriceFeed placeholder for testnet and development. The price is simply set manually and saved in a state
3 | variable. The contract does not connect to a live JunoSwap oracle.
--------------------------------------------------------------------------------
/test-contracts/price-feed-test/src/contract.rs:
--------------------------------------------------------------------------------
1 | #[cfg(not(feature = "library"))]
2 | use cosmwasm_std::entry_point;
3 | use cosmwasm_std::{
4 | to_binary, Binary, Decimal256, Deps, DepsMut, Env, MessageInfo, Response, StdResult,
5 | };
6 | use cw2::set_contract_version;
7 |
8 | use crate::error::ContractError;
9 | use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
10 | use crate::state::{Price, PRICE};
11 |
12 | // version info for migration info
13 | const CONTRACT_NAME: &str = "crates.io:price-feed-testnet";
14 | const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
15 |
16 | #[cfg_attr(not(feature = "library"), entry_point)]
17 | pub fn instantiate(
18 | deps: DepsMut,
19 | _env: Env,
20 | _info: MessageInfo,
21 | _msg: InstantiateMsg,
22 | ) -> Result {
23 | set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
24 |
25 | // initial price
26 | let price = Price {
27 | price: Decimal256::zero(),
28 | };
29 |
30 | PRICE.save(deps.storage, &price)?;
31 |
32 | Ok(Response::default())
33 | }
34 |
35 | #[cfg_attr(not(feature = "library"), entry_point)]
36 | pub fn execute(
37 | deps: DepsMut,
38 | env: Env,
39 | info: MessageInfo,
40 | msg: ExecuteMsg,
41 | ) -> Result {
42 | match msg {
43 | ExecuteMsg::SetJunoPrice { price } => execute_set_juno_price(deps, env, info, price),
44 | }
45 | }
46 |
47 | pub fn execute_set_juno_price(
48 | deps: DepsMut,
49 | _env: Env,
50 | _info: MessageInfo,
51 | price: Decimal256,
52 | ) -> Result {
53 | let new_price = Price { price };
54 | PRICE.save(deps.storage, &new_price)?;
55 | let res = Response::new().add_attribute("action", "set_price");
56 | Ok(res)
57 | }
58 |
59 | #[cfg_attr(not(feature = "library"), entry_point)]
60 | pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult {
61 | match msg {
62 | QueryMsg::GetJunoPrice {} => to_binary(&query_juno_price(deps)?),
63 | }
64 | }
65 |
66 | pub fn query_juno_price(deps: Deps) -> StdResult {
67 | let info = PRICE.load(deps.storage)?;
68 | let res = info.price;
69 | Ok(res)
70 | }
71 |
--------------------------------------------------------------------------------
/test-contracts/price-feed-test/src/error.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::StdError;
2 | use thiserror::Error;
3 |
4 | #[derive(Error, Debug, PartialEq)]
5 | pub enum ContractError {
6 | #[error("{0}")]
7 | Std(#[from] StdError),
8 | }
9 |
--------------------------------------------------------------------------------
/test-contracts/price-feed-test/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod contract;
2 | mod error;
3 | pub mod msg;
4 | pub mod state;
5 |
6 | pub use crate::error::ContractError;
7 |
--------------------------------------------------------------------------------
/test-contracts/price-feed-test/src/msg.rs:
--------------------------------------------------------------------------------
1 | use cosmwasm_std::Decimal256;
2 | use schemars::JsonSchema;
3 | use serde::{Deserialize, Serialize};
4 |
5 | #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)]
6 | pub struct InstantiateMsg {}
7 |
8 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
9 | #[serde(rename_all = "snake_case")]
10 | pub enum ExecuteMsg {
11 | SetJunoPrice { price: Decimal256 },
12 | }
13 |
14 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
15 | #[serde(rename_all = "snake_case")]
16 | pub enum QueryMsg {
17 | GetJunoPrice {},
18 | }
19 |
--------------------------------------------------------------------------------
/test-contracts/price-feed-test/src/state.rs:
--------------------------------------------------------------------------------
1 | use schemars::JsonSchema;
2 | use serde::{Deserialize, Serialize};
3 |
4 | use cosmwasm_std::Decimal256;
5 | use cw_storage_plus::Item;
6 |
7 | #[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
8 | #[serde(rename_all = "snake_case")]
9 | pub struct Price {
10 | pub price: Decimal256,
11 | }
12 |
13 | pub const PRICE: Item = Item::new("price");
14 |
--------------------------------------------------------------------------------