Merge branch 'dev'
This commit is contained in:
commit
a911a861f9
|
@ -22,7 +22,7 @@ on:
|
||||||
env:
|
env:
|
||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
SOLANA_VERSION: '1.14.9'
|
SOLANA_VERSION: '1.14.9'
|
||||||
RUST_TOOLCHAIN: '1.60.0'
|
RUST_TOOLCHAIN: '1.65.0'
|
||||||
LOG_PROGRAM: 'm43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD'
|
LOG_PROGRAM: 'm43thNJ58XCjL798ZSq6JGAG1BnWskhdq5or6kcnfsD'
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
|
@ -59,7 +59,8 @@ jobs:
|
||||||
run: cargo fmt -- --check
|
run: cargo fmt -- --check
|
||||||
|
|
||||||
- name: Run clippy
|
- name: Run clippy
|
||||||
run: cargo clippy -- --deny=warnings --allow=clippy::style --allow=clippy::complexity
|
# The --allow args are due to clippy scanning anchor
|
||||||
|
run: cargo clippy --no-deps -- --deny=warnings --allow=clippy::style --allow=clippy::complexity --allow=clippy::manual-retain --allow=clippy::crate-in-macro-def --allow=clippy::result-large-err
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
name: Run tests
|
name: Run tests
|
||||||
|
|
|
@ -428,6 +428,12 @@ dependencies = [
|
||||||
"event-listener",
|
"event-listener",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-once-cell"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f61305cacf1d0c5c9d3ee283d22f8f1f8c743a18ceb44a1b102bd53476c141de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-stream"
|
name = "async-stream"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
@ -1118,6 +1124,7 @@ dependencies = [
|
||||||
"anchor-lang",
|
"anchor-lang",
|
||||||
"anchor-spl",
|
"anchor-spl",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-once-cell",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64 0.13.1",
|
"base64 0.13.1",
|
||||||
"bincode",
|
"bincode",
|
||||||
|
@ -1445,6 +1452,17 @@ version = "2.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
|
checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "default-env"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f753eb82d29277e79efc625e84aecacfd4851ee50e05a8573a4740239a77bfd3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2 0.4.30",
|
||||||
|
"quote 0.6.13",
|
||||||
|
"syn 0.15.44",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "der"
|
name = "der"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
@ -3114,6 +3132,7 @@ dependencies = [
|
||||||
"borsh",
|
"borsh",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"checked_math",
|
"checked_math",
|
||||||
|
"default-env",
|
||||||
"derivative",
|
"derivative",
|
||||||
"env_logger 0.9.3",
|
"env_logger 0.9.3",
|
||||||
"fixed",
|
"fixed",
|
||||||
|
@ -3132,6 +3151,7 @@ dependencies = [
|
||||||
"solana-program",
|
"solana-program",
|
||||||
"solana-program-test",
|
"solana-program-test",
|
||||||
"solana-sdk",
|
"solana-sdk",
|
||||||
|
"solana-security-txt",
|
||||||
"spl-associated-token-account",
|
"spl-associated-token-account",
|
||||||
"spl-token",
|
"spl-token",
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
|
@ -6207,6 +6227,12 @@ dependencies = [
|
||||||
"syn 1.0.105",
|
"syn 1.0.105",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "solana-security-txt"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e0461f3afb29d8591300b3dd09b5472b3772d65688a2826ad960b8c0d5fa605"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "solana-send-transaction-service"
|
name = "solana-send-transaction-service"
|
||||||
version = "1.14.10"
|
version = "1.14.10"
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Important Notice
|
||||||
|
Please **DO NOT** create a GitHub issue to report a security problem. Instead, please send an email to hello@blockworks.foundation with a detailed description of the attack vector and security risk you have identified.
|
||||||
|
|
||||||
|
# Bug Bounty Overview
|
||||||
|
Mango Markets offers bug bounties for Mango Markets' on-chain program code; UI only bugs are omitted.
|
||||||
|
|
||||||
|
|Severity|Description|Bug Bounty|
|
||||||
|
|-----------|--------------|-------------|
|
||||||
|
|Critical|Bugs that freeze user funds or drain the contract's holdings or involve theft of funds without user signatures|10% of the value of the hack up to $1,000,000|
|
||||||
|
|High|Bugs that could temporarily freeze user funds or incorrectly assign value to user funds|$10,000 to $50,000 per bug, assessed on a case by case basis|
|
||||||
|
|Medium/Low|Bugs that don't threaten user funds|$1,000 to $5,000 per bug, assessed on a case by case basis|
|
||||||
|
|
||||||
|
The severity guidelines are based on [Immunefi's classification system](https://immunefi.com/severity-updated/).
|
||||||
|
Note that these are simply guidelines for the severity of the bugs. Each bug bounty submission will be evaluated on a case-by-case basis.
|
||||||
|
|
||||||
|
## Submission
|
||||||
|
Please email hello@blockworks.foundation with a detailed description of the attack vector. For critical and moderate bugs, we require a proof of concept done on a privately deployed mainnet contract. We will reach out in 1 business day with additional questions or next steps on the bug bounty.
|
||||||
|
|
||||||
|
## Bug Bounty Payment
|
||||||
|
Bug bounties will be paid in USDC or locked MNGO, after a DAO vote. The Mango DAO has never refused a valid bug bounty so far.
|
||||||
|
|
||||||
|
## Invalid Bug Bounties
|
||||||
|
The following are out of scope for the bug bounty:
|
||||||
|
1. Attacks that the reporter has already exploited themselves, leading to damage.
|
||||||
|
2. Attacks requiring access to leaked keys/credentials.
|
||||||
|
3. Attacks requiring access to privileged addresses (governance, admin).
|
||||||
|
4. Incorrect data supplied by third party oracles (this does not exclude oracle manipulation/flash loan attacks).
|
||||||
|
5. Lack of liquidity.
|
||||||
|
6. Third party, off-chain bot errors (for instance bugs with an arbitrage bot running on the smart contracts).
|
||||||
|
7. Best practice critiques.
|
||||||
|
8. Sybil attacks.
|
|
@ -11,6 +11,7 @@ anchor-client = { path = "../anchor/client" }
|
||||||
anchor-lang = { path = "../anchor/lang" }
|
anchor-lang = { path = "../anchor/lang" }
|
||||||
anchor-spl = { path = "../anchor/spl" }
|
anchor-spl = { path = "../anchor/spl" }
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
async-once-cell = { version = "0.4.2", features = ["unpin"] }
|
||||||
async-trait = "0.1.52"
|
async-trait = "0.1.52"
|
||||||
fixed = { version = "=1.11.0", features = ["serde", "borsh"] }
|
fixed = { version = "=1.11.0", features = ["serde", "borsh"] }
|
||||||
fixed-macro = "^1.1.1"
|
fixed-macro = "^1.1.1"
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
use async_once_cell::unpin::Lazy;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
|
|
||||||
use anchor_client::ClientError;
|
use anchor_client::ClientError;
|
||||||
|
@ -12,7 +15,7 @@ use solana_sdk::pubkey::Pubkey;
|
||||||
|
|
||||||
use mango_v4::state::MangoAccountValue;
|
use mango_v4::state::MangoAccountValue;
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait]
|
||||||
pub trait AccountFetcher: Sync + Send {
|
pub trait AccountFetcher: Sync + Send {
|
||||||
async fn fetch_raw_account(&self, address: &Pubkey) -> anyhow::Result<AccountSharedData>;
|
async fn fetch_raw_account(&self, address: &Pubkey) -> anyhow::Result<AccountSharedData>;
|
||||||
async fn fetch_raw_account_lookup_table(
|
async fn fetch_raw_account_lookup_table(
|
||||||
|
@ -54,7 +57,7 @@ pub struct RpcAccountFetcher {
|
||||||
pub rpc: RpcClientAsync,
|
pub rpc: RpcClientAsync,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait]
|
||||||
impl AccountFetcher for RpcAccountFetcher {
|
impl AccountFetcher for RpcAccountFetcher {
|
||||||
async fn fetch_raw_account(&self, address: &Pubkey) -> anyhow::Result<AccountSharedData> {
|
async fn fetch_raw_account(&self, address: &Pubkey) -> anyhow::Result<AccountSharedData> {
|
||||||
self.rpc
|
self.rpc
|
||||||
|
@ -99,9 +102,44 @@ impl AccountFetcher for RpcAccountFetcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CoalescedAsyncJob<Key, Output> {
|
||||||
|
jobs: HashMap<Key, Arc<Lazy<Output>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Key, Output> Default for CoalescedAsyncJob<Key, Output> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
jobs: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Key: std::cmp::Eq + std::hash::Hash, Output: 'static> CoalescedAsyncJob<Key, Output> {
|
||||||
|
/// Either returns the job for `key` or registers a new job for it
|
||||||
|
fn run_coalesced<F: std::future::Future<Output = Output> + Send + 'static>(
|
||||||
|
&mut self,
|
||||||
|
key: Key,
|
||||||
|
fut: F,
|
||||||
|
) -> Arc<Lazy<Output>> {
|
||||||
|
self.jobs
|
||||||
|
.entry(key)
|
||||||
|
.or_insert_with(|| Arc::new(Lazy::new(Box::pin(fut))))
|
||||||
|
.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&mut self, key: &Key) {
|
||||||
|
self.jobs.remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
struct AccountCache {
|
struct AccountCache {
|
||||||
accounts: HashMap<Pubkey, AccountSharedData>,
|
accounts: HashMap<Pubkey, AccountSharedData>,
|
||||||
keys_for_program_and_discriminator: HashMap<(Pubkey, [u8; 8]), Vec<Pubkey>>,
|
keys_for_program_and_discriminator: HashMap<(Pubkey, [u8; 8]), Vec<Pubkey>>,
|
||||||
|
|
||||||
|
account_jobs: CoalescedAsyncJob<Pubkey, anyhow::Result<AccountSharedData>>,
|
||||||
|
program_accounts_jobs:
|
||||||
|
CoalescedAsyncJob<(Pubkey, [u8; 8]), anyhow::Result<Vec<(Pubkey, AccountSharedData)>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountCache {
|
impl AccountCache {
|
||||||
|
@ -112,18 +150,24 @@ impl AccountCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CachedAccountFetcher<T: AccountFetcher> {
|
pub struct CachedAccountFetcher<T: AccountFetcher> {
|
||||||
fetcher: T,
|
fetcher: Arc<T>,
|
||||||
cache: Mutex<AccountCache>,
|
cache: Arc<Mutex<AccountCache>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AccountFetcher> Clone for CachedAccountFetcher<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
fetcher: self.fetcher.clone(),
|
||||||
|
cache: self.cache.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: AccountFetcher> CachedAccountFetcher<T> {
|
impl<T: AccountFetcher> CachedAccountFetcher<T> {
|
||||||
pub fn new(fetcher: T) -> Self {
|
pub fn new(fetcher: Arc<T>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
fetcher,
|
fetcher,
|
||||||
cache: Mutex::new(AccountCache {
|
cache: Arc::new(Mutex::new(AccountCache::default())),
|
||||||
accounts: HashMap::new(),
|
|
||||||
keys_for_program_and_discriminator: HashMap::new(),
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,16 +177,41 @@ impl<T: AccountFetcher> CachedAccountFetcher<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait]
|
||||||
impl<T: AccountFetcher> AccountFetcher for CachedAccountFetcher<T> {
|
impl<T: AccountFetcher + 'static> AccountFetcher for CachedAccountFetcher<T> {
|
||||||
async fn fetch_raw_account(&self, address: &Pubkey) -> anyhow::Result<AccountSharedData> {
|
async fn fetch_raw_account(&self, address: &Pubkey) -> anyhow::Result<AccountSharedData> {
|
||||||
|
let fetch_job = {
|
||||||
let mut cache = self.cache.lock().unwrap();
|
let mut cache = self.cache.lock().unwrap();
|
||||||
if let Some(account) = cache.accounts.get(address) {
|
if let Some(acc) = cache.accounts.get(address) {
|
||||||
return Ok(account.clone());
|
return Ok(acc.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start or fetch a reference to the fetch + cache update job
|
||||||
|
let self_copy = self.clone();
|
||||||
|
let address_copy = address.clone();
|
||||||
|
cache.account_jobs.run_coalesced(*address, async move {
|
||||||
|
let result = self_copy.fetcher.fetch_raw_account(&address_copy).await;
|
||||||
|
let mut cache = self_copy.cache.lock().unwrap();
|
||||||
|
|
||||||
|
// remove the job from the job list, so it can be redone if it errored
|
||||||
|
cache.account_jobs.remove(&address_copy);
|
||||||
|
|
||||||
|
// store a successful fetch
|
||||||
|
if let Ok(account) = result.as_ref() {
|
||||||
|
cache.accounts.insert(address_copy, account.clone());
|
||||||
|
}
|
||||||
|
result
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
match fetch_job.get().await {
|
||||||
|
Ok(v) => Ok(v.clone()),
|
||||||
|
// Can't clone the stored error, so need to stringize it
|
||||||
|
Err(err) => Err(anyhow::format_err!(
|
||||||
|
"fetch error in CachedAccountFetcher: {:?}",
|
||||||
|
err
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
let account = self.fetcher.fetch_raw_account(address).await?;
|
|
||||||
cache.accounts.insert(*address, account.clone());
|
|
||||||
Ok(account)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_program_accounts(
|
async fn fetch_program_accounts(
|
||||||
|
@ -151,6 +220,7 @@ impl<T: AccountFetcher> AccountFetcher for CachedAccountFetcher<T> {
|
||||||
discriminator: [u8; 8],
|
discriminator: [u8; 8],
|
||||||
) -> anyhow::Result<Vec<(Pubkey, AccountSharedData)>> {
|
) -> anyhow::Result<Vec<(Pubkey, AccountSharedData)>> {
|
||||||
let cache_key = (*program, discriminator);
|
let cache_key = (*program, discriminator);
|
||||||
|
let fetch_job = {
|
||||||
let mut cache = self.cache.lock().unwrap();
|
let mut cache = self.cache.lock().unwrap();
|
||||||
if let Some(accounts) = cache.keys_for_program_and_discriminator.get(&cache_key) {
|
if let Some(accounts) = cache.keys_for_program_and_discriminator.get(&cache_key) {
|
||||||
return Ok(accounts
|
return Ok(accounts
|
||||||
|
@ -158,16 +228,37 @@ impl<T: AccountFetcher> AccountFetcher for CachedAccountFetcher<T> {
|
||||||
.map(|pk| (*pk, cache.accounts.get(&pk).unwrap().clone()))
|
.map(|pk| (*pk, cache.accounts.get(&pk).unwrap().clone()))
|
||||||
.collect::<Vec<_>>());
|
.collect::<Vec<_>>());
|
||||||
}
|
}
|
||||||
let accounts = self
|
|
||||||
|
let self_copy = self.clone();
|
||||||
|
let program_copy = program.clone();
|
||||||
|
cache
|
||||||
|
.program_accounts_jobs
|
||||||
|
.run_coalesced(cache_key.clone(), async move {
|
||||||
|
let result = self_copy
|
||||||
.fetcher
|
.fetcher
|
||||||
.fetch_program_accounts(program, discriminator)
|
.fetch_program_accounts(&program_copy, discriminator)
|
||||||
.await?;
|
.await;
|
||||||
|
let mut cache = self_copy.cache.lock().unwrap();
|
||||||
|
cache.program_accounts_jobs.remove(&cache_key);
|
||||||
|
if let Ok(accounts) = result.as_ref() {
|
||||||
cache
|
cache
|
||||||
.keys_for_program_and_discriminator
|
.keys_for_program_and_discriminator
|
||||||
.insert(cache_key, accounts.iter().map(|(pk, _)| *pk).collect());
|
.insert(cache_key, accounts.iter().map(|(pk, _)| *pk).collect());
|
||||||
for (pk, acc) in accounts.iter() {
|
for (pk, acc) in accounts.iter() {
|
||||||
cache.accounts.insert(*pk, acc.clone());
|
cache.accounts.insert(*pk, acc.clone());
|
||||||
}
|
}
|
||||||
Ok(accounts)
|
}
|
||||||
|
result
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
match fetch_job.get().await {
|
||||||
|
Ok(v) => Ok(v.clone()),
|
||||||
|
// Can't clone the stored error, so need to stringize it
|
||||||
|
Err(err) => Err(anyhow::format_err!(
|
||||||
|
"fetch error in CachedAccountFetcher: {:?}",
|
||||||
|
err
|
||||||
|
)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@ impl AccountFetcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait(?Send)]
|
#[async_trait::async_trait]
|
||||||
impl crate::AccountFetcher for AccountFetcher {
|
impl crate::AccountFetcher for AccountFetcher {
|
||||||
async fn fetch_raw_account(
|
async fn fetch_raw_account(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -220,7 +220,9 @@ impl MangoClient {
|
||||||
owner: Keypair,
|
owner: Keypair,
|
||||||
) -> anyhow::Result<Self> {
|
) -> anyhow::Result<Self> {
|
||||||
let rpc = client.rpc_async();
|
let rpc = client.rpc_async();
|
||||||
let account_fetcher = Arc::new(CachedAccountFetcher::new(RpcAccountFetcher { rpc }));
|
let account_fetcher = Arc::new(CachedAccountFetcher::new(Arc::new(RpcAccountFetcher {
|
||||||
|
rpc,
|
||||||
|
})));
|
||||||
let mango_account =
|
let mango_account =
|
||||||
account_fetcher_fetch_mango_account(&*account_fetcher, &account).await?;
|
account_fetcher_fetch_mango_account(&*account_fetcher, &account).await?;
|
||||||
let group = mango_account.fixed.group;
|
let group = mango_account.fixed.group;
|
||||||
|
|
|
@ -28,6 +28,7 @@ bincode = "1.3.3"
|
||||||
borsh = { version = "0.9.3", features = ["const-generics"] }
|
borsh = { version = "0.9.3", features = ["const-generics"] }
|
||||||
bytemuck = { version = "^1.7.2", features = ["min_const_generics"] }
|
bytemuck = { version = "^1.7.2", features = ["min_const_generics"] }
|
||||||
checked_math = { path = "../../lib/checked_math" }
|
checked_math = { path = "../../lib/checked_math" }
|
||||||
|
default-env = "0.1.1"
|
||||||
derivative = "2.2.0"
|
derivative = "2.2.0"
|
||||||
fixed = { version = "=1.11.0", features = ["serde", "borsh"] } # todo: higher versions don't work
|
fixed = { version = "=1.11.0", features = ["serde", "borsh"] } # todo: higher versions don't work
|
||||||
fixed-macro = "^1.1.1"
|
fixed-macro = "^1.1.1"
|
||||||
|
@ -38,6 +39,7 @@ serum_dex = { version = "0.5.6", git = "https://github.com/blockworks-foundation
|
||||||
solana-address-lookup-table-program = "~1.14.9"
|
solana-address-lookup-table-program = "~1.14.9"
|
||||||
solana-program = "~1.14.9"
|
solana-program = "~1.14.9"
|
||||||
solana-sdk = { version = "~1.14.9", default-features = false, optional = true }
|
solana-sdk = { version = "~1.14.9", default-features = false, optional = true }
|
||||||
|
solana-security-txt = "1.1.0"
|
||||||
static_assertions = "1.1"
|
static_assertions = "1.1"
|
||||||
switchboard-program = ">=0.2.0"
|
switchboard-program = ">=0.2.0"
|
||||||
switchboard-v2 = "0.1.17"
|
switchboard-v2 = "0.1.17"
|
||||||
|
|
|
@ -84,7 +84,7 @@ pub fn token_register_trustless(
|
||||||
oracle: ctx.accounts.oracle.key(),
|
oracle: ctx.accounts.oracle.key(),
|
||||||
oracle_config: OracleConfig {
|
oracle_config: OracleConfig {
|
||||||
conf_filter: I80F48::from_num(0.10),
|
conf_filter: I80F48::from_num(0.10),
|
||||||
max_staleness_slots: -1,
|
max_staleness_slots: 600,
|
||||||
reserved: [0; 72],
|
reserved: [0; 72],
|
||||||
},
|
},
|
||||||
stable_price_model: StablePriceModel::default(),
|
stable_price_model: StablePriceModel::default(),
|
||||||
|
|
|
@ -760,3 +760,17 @@ impl anchor_lang::Id for Mango {
|
||||||
ID
|
ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "no-entrypoint"))]
|
||||||
|
use {default_env::default_env, solana_security_txt::security_txt};
|
||||||
|
#[cfg(not(feature = "no-entrypoint"))]
|
||||||
|
security_txt! {
|
||||||
|
name: "Mango v4",
|
||||||
|
project_url: "https://mango.markets",
|
||||||
|
contacts: "email:hello@blockworks.foundation,link:https://docs.mango.markets/mango-markets/bug-bounty,discord:https://discord.gg/mangomarkets",
|
||||||
|
policy: "https://github.com/blockworks-foundation/mango-v4/blob/main/SECURITY.md",
|
||||||
|
preferred_languages: "en",
|
||||||
|
source_code: "https://github.com/blockworks-foundation/mango-v4",
|
||||||
|
source_revision: default_env!("GITHUB_SHA", "Unknown source revision"),
|
||||||
|
source_release: default_env!("GITHUB_REF_NAME", "Unknown source release")
|
||||||
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ const MAINNET_MINTS = new Map([
|
||||||
['SOL', 'So11111111111111111111111111111111111111112'], // 4 Wrapped SOL
|
['SOL', 'So11111111111111111111111111111111111111112'], // 4 Wrapped SOL
|
||||||
['MSOL', 'mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So'], // 5
|
['MSOL', 'mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So'], // 5
|
||||||
['MNGO', 'MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac'], // 6
|
['MNGO', 'MangoCzJ36AjZyKwVj3VnYU4GTonjfVEnJmvvWaxLac'], // 6
|
||||||
|
['BONK', 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263'], // 7
|
||||||
]);
|
]);
|
||||||
const MAINNET_ORACLES = new Map([
|
const MAINNET_ORACLES = new Map([
|
||||||
// USDC - stub oracle
|
// USDC - stub oracle
|
||||||
|
@ -46,6 +47,7 @@ const MAINNET_ORACLES = new Map([
|
||||||
['MSOL', 'E4v1BBgoso9s64TQvmyownAVJbhbEPGyzA3qn4n46qj9'],
|
['MSOL', 'E4v1BBgoso9s64TQvmyownAVJbhbEPGyzA3qn4n46qj9'],
|
||||||
['MNGO', '79wm3jjcPr6RaNQ4DGvP5KxG1mNd3gEBsg6FsNVFezK4'],
|
['MNGO', '79wm3jjcPr6RaNQ4DGvP5KxG1mNd3gEBsg6FsNVFezK4'],
|
||||||
['BTC', 'GVXRSBjFk6e6J3NbVPXohDJetcTjaeeuykUpbQF8UoMU'],
|
['BTC', 'GVXRSBjFk6e6J3NbVPXohDJetcTjaeeuykUpbQF8UoMU'],
|
||||||
|
['BONK', '4SZ1qb4MtSUrZcoeaeQ3BDzVCyqxw3VwSFpPiMTmn4GE'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// External markets are matched with those in https://github.com/openbook-dex/openbook-ts/blob/master/packages/serum/src/markets.json
|
// External markets are matched with those in https://github.com/openbook-dex/openbook-ts/blob/master/packages/serum/src/markets.json
|
||||||
|
@ -322,6 +324,17 @@ async function registerTokens() {
|
||||||
'MNGO',
|
'MNGO',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log(`Registering BONK...`);
|
||||||
|
const bonkMainnetMint = new PublicKey(MAINNET_MINTS.get('BONK')!);
|
||||||
|
const bonkMainnetOracle = new PublicKey(MAINNET_ORACLES.get('BONK')!);
|
||||||
|
await client.tokenRegisterTrustless(
|
||||||
|
group,
|
||||||
|
bonkMainnetMint,
|
||||||
|
bonkMainnetOracle,
|
||||||
|
7,
|
||||||
|
'BONK',
|
||||||
|
);
|
||||||
|
|
||||||
// log tokens/banks
|
// log tokens/banks
|
||||||
await group.reloadAll(client);
|
await group.reloadAll(client);
|
||||||
for (const bank of await Array.from(group.banksMapByMint.values())
|
for (const bank of await Array.from(group.banksMapByMint.values())
|
||||||
|
|
Loading…
Reference in New Issue