mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-08-25 06:01:11 +00:00
feat(amm): add configAccount read (decode the singleton config)
Expose the AMM config account as a read op, so a future config/admin view can show the authority and the token/twap program ids the AMM chains into.
This commit is contained in:
@@ -182,6 +182,11 @@ QVariantMap AmmUiBackend::resolvePoolAccount(QString defAHex, QString defBHex)
|
||||
return m_logos->amm_module.resolvePoolAccount(defAHex, defBHex);
|
||||
}
|
||||
|
||||
QVariantMap AmmUiBackend::configAccount()
|
||||
{
|
||||
return m_logos->amm_module.configAccount();
|
||||
}
|
||||
|
||||
QString AmmUiBackend::swapExactInput(QString defAHex, QString defBHex, QString userInputHoldingHex,
|
||||
QString userOutputHoldingHex, QString amountInDecimal,
|
||||
QString minOutDecimal, QString deadlineDecimal)
|
||||
|
||||
@@ -55,6 +55,7 @@ public slots:
|
||||
|
||||
// AMM — all forwarded to the amm_module core module.
|
||||
QVariantMap resolvePoolAccount(QString defAHex, QString defBHex) override;
|
||||
QVariantMap configAccount() override;
|
||||
QString swapExactInput(QString defAHex, QString defBHex, QString userInputHoldingHex,
|
||||
QString userOutputHoldingHex, QString amountInDecimal,
|
||||
QString minOutDecimal, QString deadlineDecimal) override;
|
||||
|
||||
@@ -49,6 +49,10 @@ class AmmUiBackend
|
||||
// `{ status:"error", error:<code> }` — `no_pool` for the ordinary "no pool /
|
||||
// no liquidity yet" state, else no_program_bin / amm_not_initialized / bad_config.
|
||||
SLOT(QVariantMap resolvePoolAccount(QString defAHex, QString defBHex))
|
||||
// Decodes the singleton AMM config account: `{ status:"ok", error:"", configId,
|
||||
// ammProgramId, authority, tokenProgramId, twapOracleProgramId }` (ids base58), or
|
||||
// `{ status:"error", error:"config_missing"|"config_unavailable"|"backend_error" }`.
|
||||
SLOT(QVariantMap configAccount())
|
||||
// Submits a real on-chain SwapExactInput transaction against the pool for
|
||||
// (defAHex, defBHex). amountInDecimal/minOutDecimal are decimal-string
|
||||
// u128 amounts in base units; deadlineDecimal is a decimal-string u64 unix
|
||||
|
||||
@@ -16,6 +16,8 @@ extern "C" {
|
||||
|
||||
char *amm_config_id(const char *request_json);
|
||||
|
||||
char *amm_config_account(const char *request_json);
|
||||
|
||||
char *amm_pair_ids(const char *request_json);
|
||||
|
||||
char *amm_resolve_tokens(const char *request_json);
|
||||
|
||||
@@ -60,6 +60,10 @@ pub(crate) fn program_id_hex(program_id: ProgramId) -> String {
|
||||
hex::encode(bytes)
|
||||
}
|
||||
|
||||
pub(crate) fn program_id_base58(program_id: ProgramId) -> String {
|
||||
AccountId::new(program_id_bytes(program_id)).to_string()
|
||||
}
|
||||
|
||||
pub(crate) fn program_id_bytes(program_id: ProgramId) -> [u8; 32] {
|
||||
let mut bytes = [0_u8; 32];
|
||||
for (chunk, word) in bytes.chunks_exact_mut(4).zip(program_id) {
|
||||
|
||||
@@ -2,8 +2,10 @@ use amm_core::{compute_config_pda, AmmConfig};
|
||||
use nssa_core::{account::Account, program::ProgramId};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use super::ConfigIdRequest;
|
||||
use crate::account::{account_id_hex, decode_account, parse_program_id, AccountRead};
|
||||
use super::{ConfigAccountRequest, ConfigIdRequest};
|
||||
use crate::account::{
|
||||
account_id_hex, decode_account, parse_program_id, program_id_base58, AccountRead,
|
||||
};
|
||||
|
||||
pub(super) fn config_id(request: ConfigIdRequest) -> Result<Value, String> {
|
||||
let amm_program = parse_program_id(&request.amm_program_id)?;
|
||||
@@ -13,6 +15,26 @@ pub(super) fn config_id(request: ConfigIdRequest) -> Result<Value, String> {
|
||||
}))
|
||||
}
|
||||
|
||||
/// Decodes the singleton config account: authority + the token/twap program ids the AMM chains
|
||||
/// into. Ids are base58 (app-facing). `config_unavailable` when the config PDA isn't on-chain
|
||||
/// yet / undecodable; `configId` / `ammProgramId` are still derivable from `amm_program_id` via
|
||||
/// `config_id` for address derivation.
|
||||
pub(super) fn config_account(request: ConfigAccountRequest) -> Result<Value, String> {
|
||||
let amm_program = parse_program_id(&request.amm_program_id)?;
|
||||
let Ok(config) = load_config(amm_program, &request.config) else {
|
||||
return Ok(json!({ "status": "error", "error": "config_unavailable" }));
|
||||
};
|
||||
Ok(json!({
|
||||
"status": "ok",
|
||||
"error": "",
|
||||
"configId": compute_config_pda(amm_program).to_string(),
|
||||
"ammProgramId": program_id_base58(amm_program),
|
||||
"authority": config.authority.to_string(),
|
||||
"tokenProgramId": program_id_base58(config.token_program_id),
|
||||
"twapOracleProgramId": program_id_base58(config.twap_oracle_program_id),
|
||||
}))
|
||||
}
|
||||
|
||||
pub(super) fn load_config(amm_program: ProgramId, read: &AccountRead) -> Result<AmmConfig, String> {
|
||||
let (id, account) = decode_account(read)?;
|
||||
if id != compute_config_pda(amm_program)
|
||||
|
||||
@@ -17,9 +17,9 @@ mod tests;
|
||||
use std::{error::Error, fmt};
|
||||
|
||||
pub use request::{
|
||||
AddLiquidityPlanRequest, AddLiquidityQuoteRequest, ConfigIdRequest, CreatePoolPlanRequest,
|
||||
CreatePoolQuoteRequest, FeeTiersRequest, PairIdsRequest, PoolIdRequest, ProgramIdRequest,
|
||||
RemoveLiquidityPlanRequest, RemoveLiquidityQuoteRequest, ResolvePoolRequest,
|
||||
AddLiquidityPlanRequest, AddLiquidityQuoteRequest, ConfigAccountRequest, ConfigIdRequest,
|
||||
CreatePoolPlanRequest, CreatePoolQuoteRequest, FeeTiersRequest, PairIdsRequest, PoolIdRequest,
|
||||
ProgramIdRequest, RemoveLiquidityPlanRequest, RemoveLiquidityQuoteRequest, ResolvePoolRequest,
|
||||
ResolveTokensRequest, SwapExactInPlanRequest, SwapExactInQuoteRequest, SwapExactOutPlanRequest,
|
||||
SwapExactOutQuoteRequest, SwapPairRequest, SyncReservesPlanRequest, TokenHoldingsRequest,
|
||||
};
|
||||
@@ -66,6 +66,11 @@ pub fn config_id(request: ConfigIdRequest) -> AmmResult {
|
||||
config::config_id(request).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Decodes the singleton config account (authority + token/twap program ids).
|
||||
pub fn config_account(request: ConfigAccountRequest) -> AmmResult {
|
||||
config::config_account(request).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Derives canonical accounts for one token pair.
|
||||
pub fn pair_ids(request: PairIdsRequest) -> AmmResult {
|
||||
pair::pair_ids(request).map_err(Into::into)
|
||||
|
||||
@@ -8,6 +8,16 @@ pub struct ConfigIdRequest {
|
||||
pub amm_program_id: String,
|
||||
}
|
||||
|
||||
/// Decodes the singleton AMM config account. `config` is the read of the config PDA the module
|
||||
/// derives from `amm_program_id`; the op returns the authority + program ids (or
|
||||
/// `{ status:"error", error:"config_unavailable" }` when the config isn't on-chain yet).
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ConfigAccountRequest {
|
||||
pub amm_program_id: String,
|
||||
pub config: AccountRead,
|
||||
}
|
||||
|
||||
/// Resolves an app-provided set of token ids into selector rows. `token_ids` are hex — the
|
||||
/// module normalizes base58→hex and reads each definition into `token_definitions` (keyed by
|
||||
/// hex id) plus the wallet accounts; the FFI is stateless and reads nothing itself.
|
||||
|
||||
@@ -14,15 +14,17 @@ use token_core::{TokenDefinition, TokenHolding};
|
||||
use twap_oracle_core::compute_current_tick_account_pda;
|
||||
|
||||
use super::{
|
||||
config::config_account as decode_config_account,
|
||||
context::resolve_tokens,
|
||||
holding::{select_holding, SelectedHolding},
|
||||
pair::{is_canonical_pair, pair_ids, PairIds},
|
||||
quote::{div_ceil_u256, minimum_opening_pair, Q64},
|
||||
swap::{swap_exact_in_plan, swap_exact_out_plan},
|
||||
PairIdsRequest, ResolveTokensRequest, SwapExactInPlanRequest, SwapExactOutPlanRequest,
|
||||
ConfigAccountRequest, PairIdsRequest, ResolveTokensRequest, SwapExactInPlanRequest,
|
||||
SwapExactOutPlanRequest,
|
||||
};
|
||||
use crate::{
|
||||
account::{account_id_hex, account_read, decode_account, program_id_bytes},
|
||||
account::{account_id_hex, account_read, decode_account, program_id_base58, program_id_bytes},
|
||||
AccountRead,
|
||||
};
|
||||
|
||||
@@ -238,6 +240,39 @@ fn resolve_tokens_returns_lean_rows_held_first_and_omits_unresolvable() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_account_decodes_authority_and_program_ids() {
|
||||
let config_id = compute_config_pda(AMM_PROGRAM);
|
||||
let value = decode_config_account(ConfigAccountRequest {
|
||||
amm_program_id: amm_program_id(),
|
||||
config: account_read(config_id, &config_account()),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(value["status"], "ok");
|
||||
assert_eq!(value["configId"], config_id.to_string());
|
||||
assert_eq!(value["ammProgramId"], program_id_base58(AMM_PROGRAM));
|
||||
assert_eq!(value["authority"], AccountId::new([7; 32]).to_string());
|
||||
assert_eq!(value["tokenProgramId"], program_id_base58(TOKEN_PROGRAM));
|
||||
assert_eq!(
|
||||
value["twapOracleProgramId"],
|
||||
program_id_base58(TWAP_PROGRAM)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_account_is_unavailable_when_not_on_chain() {
|
||||
let config_id = compute_config_pda(AMM_PROGRAM);
|
||||
let value = decode_config_account(ConfigAccountRequest {
|
||||
amm_program_id: amm_program_id(),
|
||||
config: default_read(config_id),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(value["status"], "error");
|
||||
assert_eq!(value["error"], "config_unavailable");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_pool_snapshot_defaults_remain_real_accounts() {
|
||||
let id = AccountId::new([5; 32]);
|
||||
|
||||
@@ -7,8 +7,8 @@ use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
use crate::api::{
|
||||
self, AddLiquidityPlanRequest, AddLiquidityQuoteRequest, AmmApiError, AmmResult,
|
||||
ConfigIdRequest, CreatePoolPlanRequest, CreatePoolQuoteRequest, FeeTiersRequest,
|
||||
PairIdsRequest, PoolIdRequest, ProgramIdRequest, RemoveLiquidityPlanRequest,
|
||||
ConfigAccountRequest, ConfigIdRequest, CreatePoolPlanRequest, CreatePoolQuoteRequest,
|
||||
FeeTiersRequest, PairIdsRequest, PoolIdRequest, ProgramIdRequest, RemoveLiquidityPlanRequest,
|
||||
RemoveLiquidityQuoteRequest, ResolvePoolRequest, ResolveTokensRequest, SwapExactInPlanRequest,
|
||||
SwapExactInQuoteRequest, SwapExactOutPlanRequest, SwapExactOutQuoteRequest, SwapPairRequest,
|
||||
SyncReservesPlanRequest, TokenHoldingsRequest,
|
||||
@@ -85,6 +85,11 @@ pub extern "C" fn amm_config_id(request_json: *const c_char) -> *mut c_char {
|
||||
call::<ConfigIdRequest>(request_json, api::config_id)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn amm_config_account(request_json: *const c_char) -> *mut c_char {
|
||||
call::<ConfigAccountRequest>(request_json, api::config_account)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn amm_pair_ids(request_json: *const c_char) -> *mut c_char {
|
||||
call::<PairIdsRequest>(request_json, api::pair_ids)
|
||||
|
||||
@@ -6,11 +6,11 @@ mod ffi;
|
||||
pub mod api;
|
||||
|
||||
pub use api::{
|
||||
config_id, create_pool_plan, create_pool_quote, fee_tiers, pair_ids, pool_id, program_id,
|
||||
resolve_pool, resolve_tokens, swap_exact_in_plan, swap_exact_in_quote, swap_exact_out_plan,
|
||||
swap_exact_out_quote, swap_pair, AccountRead, AmmApiError, AmmResponse, AmmResult,
|
||||
ConfigIdRequest, CreatePoolPlanRequest, CreatePoolQuoteRequest, FeeTiersRequest,
|
||||
PairIdsRequest, PoolIdRequest, ProgramIdRequest, ResolvePoolRequest, ResolveTokensRequest,
|
||||
SwapExactInPlanRequest, SwapExactInQuoteRequest, SwapExactOutPlanRequest,
|
||||
SwapExactOutQuoteRequest, SwapPairRequest, WalletAccount,
|
||||
config_account, config_id, create_pool_plan, create_pool_quote, fee_tiers, pair_ids, pool_id,
|
||||
program_id, resolve_pool, resolve_tokens, swap_exact_in_plan, swap_exact_in_quote,
|
||||
swap_exact_out_plan, swap_exact_out_quote, swap_pair, AccountRead, AmmApiError, AmmResponse,
|
||||
AmmResult, ConfigAccountRequest, ConfigIdRequest, CreatePoolPlanRequest,
|
||||
CreatePoolQuoteRequest, FeeTiersRequest, PairIdsRequest, PoolIdRequest, ProgramIdRequest,
|
||||
ResolvePoolRequest, ResolveTokensRequest, SwapExactInPlanRequest, SwapExactInQuoteRequest,
|
||||
SwapExactOutPlanRequest, SwapExactOutQuoteRequest, SwapPairRequest, WalletAccount,
|
||||
};
|
||||
|
||||
@@ -401,6 +401,25 @@ LogosMap AmmModuleImpl::resolvePoolAccount(const std::string& def_a_hex,
|
||||
return resolved;
|
||||
}
|
||||
|
||||
LogosMap AmmModuleImpl::configAccount() {
|
||||
const std::string amm_program_id = ammProgramId();
|
||||
if (amm_program_id.empty())
|
||||
return LogosMap{{"status", "error"}, {"error", "config_missing"}};
|
||||
|
||||
const json config = readConfig(amm_program_id);
|
||||
if (config.is_null())
|
||||
return LogosMap{{"status", "error"}, {"error", "backend_error"}};
|
||||
|
||||
const FfiResult result = call(amm_config_account, json{
|
||||
{"ammProgramId", amm_program_id},
|
||||
{"config", config},
|
||||
});
|
||||
if (!result.ok)
|
||||
return LogosMap{{"status", "error"},
|
||||
{"error", result.error.empty() ? "backend_error" : result.error}};
|
||||
return result.value;
|
||||
}
|
||||
|
||||
LogosMap AmmModuleImpl::swapExactInQuote(const std::string& token_in_hex,
|
||||
const std::string& token_out_hex,
|
||||
const nlohmann::json& amount_in,
|
||||
|
||||
@@ -40,6 +40,13 @@ public:
|
||||
/// ordinary "no pool / no liquidity yet" state (still carries `poolId`).
|
||||
LogosMap resolvePoolAccount(const std::string& def_a_hex, const std::string& def_b_hex);
|
||||
|
||||
/// Decodes the singleton AMM config account. On success: `{ status:"ok", error:"",
|
||||
/// configId, ammProgramId, authority, tokenProgramId, twapOracleProgramId }` (ids
|
||||
/// base58). `{ status:"error", error:"config_missing" }` when AMM_PROGRAM_BIN is
|
||||
/// unset/unreadable; `config_unavailable` when the config isn't on-chain yet; or
|
||||
/// `backend_error` when the backend FFI call fails.
|
||||
LogosMap configAccount();
|
||||
|
||||
/// Prices a `SwapExactInput` for the (token_in_hex, token_out_hex) pair:
|
||||
/// reads the pool and returns `{ status:"ok", error:"", expectedOutRaw,
|
||||
/// minReceivedRaw, priceImpactBps }`, oriented and computed server-side via
|
||||
|
||||
Reference in New Issue
Block a user