├── .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 | --------------------------------------------------------------------------------