Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
eb2948f
feat: use feature flags to add "no yield" option
Oighty May 28, 2025
362b554
chore: rename program
Oighty May 28, 2025
38e8c1f
fix: state serialization
Oighty May 29, 2025
fe25de0
chore: makefile and test fixes
Oighty May 29, 2025
43454c3
chore: remove unused imports and cleanup compile warnings
Oighty May 29, 2025
5f79a5f
chore: update CI
Oighty May 29, 2025
b238990
chore: remove unnecessary idl-build flag
Oighty May 29, 2025
855e3d1
fix: compiler warning on no-yield variant
Oighty May 29, 2025
ac44e44
chore: comment clean-up
Oighty May 29, 2025
0ab5828
fix: no yield multiplier return value
Oighty May 29, 2025
0207359
test: no yield ext tests
Oighty May 29, 2025
7118905
chore: move test runner to makefile
Oighty May 29, 2025
b106d64
chore: add default feature to play nicer with anchor plugin
Oighty May 29, 2025
dd56955
test: new test harness and initial test conversion to new format
Oighty May 30, 2025
6d67f3d
feat: check that ext mint has freeze authority on initialization
Oighty May 30, 2025
585872b
refactor: cfg_if imports
Oighty May 30, 2025
678114c
fix: remove unused signer from sync
Oighty Jun 2, 2025
763d7af
test: finish refactored admin tests
Oighty Jun 2, 2025
2f1f58b
test: wrap/unwrap test refactor
Oighty Jun 2, 2025
75eb1ca
test: refactor sync unit tests
Oighty Jun 2, 2025
a4948d7
test: remove old test files and update makefile
Oighty Jun 2, 2025
1478743
test: add test case for ext mint not having freeze authority
Oighty Jun 2, 2025
d9e5c96
test: refactor multiplier calculation and add tests
Oighty Jun 3, 2025
5094a71
tests: update multiplier checks to account for floating point precisi…
Oighty Jun 3, 2025
3965c93
chore: clarify comment on test
Oighty Jun 4, 2025
de68b3b
chore: update solana-m dep to `develop` branch
Oighty Jun 9, 2025
8d3ba81
feat: use last claim index for scaledui syncs instead of m global
Oighty Jun 9, 2025
62ebb68
test: update existing tests for index change
Oighty Jun 9, 2025
2ff3f0c
test: add'l ext tests with solvency checks
Oighty Jun 10, 2025
43a4827
chore: update README
Oighty Jun 10, 2025
e6e0f45
Update README.md
Oighty Jun 10, 2025
f71a84e
chore: kurtis review clean-up
Oighty Jun 10, 2025
89ab8f6
fix
SC4RECOIN Jun 11, 2025
edc0759
test: fix tests to accompany fix
Oighty Jun 11, 2025
5487dd7
Merge pull request #15 from m0-foundation/kurtis/unwrap-exploit
Oighty Jun 11, 2025
abaf31c
PROTO-179: swap router (#12)
SC4RECOIN Jun 11, 2025
8a98536
fix: make "no-yield" the default option to fix CPI build
Oighty Jun 11, 2025
1de0096
chore: rename program_authority -> wrap_authority
Oighty Jun 11, 2025
07ee511
test: fix existing tests with tokenAuthority/wrapAuthority split
Oighty Jun 11, 2025
d6faa3d
test: wrap/unwrap with wrap authority tests
Oighty Jun 11, 2025
6e8c7bb
feat: dynamically sized wrap auth list (#17)
Oighty Jun 12, 2025
4a94237
Allow wrap authority co-signing on swaps (#16)
SC4RECOIN Jun 12, 2025
b7754ad
test: fix tests after merge
Oighty Jun 12, 2025
34ec02c
chore: readme updates
Oighty Jun 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions .github/setup/action.yaml
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
name: setup
description: 'Installing tooling and dependencies for running tests'
description: "Installing tooling and dependencies for running tests"
inputs:
node-version:
description: 'Node.js version'
description: "Node.js version"
required: false
default: '22'
default: "22"
solana-version:
description: 'Solana version'
description: "Solana version"
required: false
default: '2.1.0'
default: "2.1.0"
anchor-version:
description: 'Anchor CLI version'
description: "Anchor CLI version"
required: false
default: '0.31.1'
default: "0.31.1"
runs:
using: 'composite'
using: "composite"
steps:
- name: Setup Node
uses: actions/setup-node@v4
Expand Down Expand Up @@ -61,5 +61,5 @@ runs:
shell: bash

- name: Build programs
run: anchor build
shell: bash
run: make build-programs
shell: bash
8 changes: 4 additions & 4 deletions .github/workflows/anchor.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ concurrency:
cancel-in-progress: true

jobs:
scaled-ui-ext-tests:
runs-on: macos-latest
program-tests:
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand All @@ -25,5 +25,5 @@ jobs:
uses: ./.github/setup

- name: Run tests
run: yarn run jest --preset ts-jest --verbose tests/unit/scaled_ui_ext.test.ts
shell: bash
run: yarn test
shell: bash
2 changes: 1 addition & 1 deletion Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ seeds = false
skip-lint = false

[programs.localnet]
scaled_ui_ext = "3C865D264L4NkAm78zfnDzQJJvXuU3fMjRUvRxyPi5da"
m_ext = "3C865D264L4NkAm78zfnDzQJJvXuU3fMjRUvRxyPi5da"

[registry]
url = "https://api.apr.dev"
Expand Down
25 changes: 12 additions & 13 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ codegen-units = 1
anchor-lang = "0.31.1"
anchor-spl = "0.31.1"
spl-token-2022 = { version = "7.0.0", features = ["no-entrypoint"] }
solana-program = "=2.1.0"
solana-security-txt = "1.1.1"
cfg-if = "1.0"
earn = { git = "https://github.com/m0-foundation/solana-m", branch = "solana-2.1.0", features = ["no-entrypoint"] }
Expand Down
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
build-programs:
anchor build -p m_ext -- --features scaled-ui --no-default-features
@mv target/deploy/m_ext.so target/deploy/scaled_ui.so
@mv target/idl/m_ext.json target/idl/scaled_ui.json
@mv target/types/m_ext.ts target/types/scaled_ui.ts
anchor build -p m_ext -- --features no-yield --no-default-features
@mv target/deploy/m_ext.so target/deploy/no_yield.so
@mv target/idl/m_ext.json target/idl/no_yield.json
@mv target/types/m_ext.ts target/types/no_yield.ts

test-programs:
@yarn run jest --preset ts-jest --verbose
54 changes: 27 additions & 27 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
{
"scripts": {
"lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
"lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check",
"test": "yarn run jest --preset ts-jest --verbose",
"build": "anchor build"
},
"dependencies": {
"@coral-xyz/anchor": "^0.31.1",
"@m0-foundation/solana-m-sdk": "https://gitpkg.vercel.app/m0-foundation/solana-m/sdk?0991eb829a52f6ba5969fd47d3100ef6871f0a6d&scripts.postinstall=yarn%20build",
"@solana-developers/helpers": "^2.7.0",
"@solana/spl-token": "^0.4.13",
"@solana/web3.js": "^1.98",
"anchor-litesvm": "0.1.2",
"bn.js": "^5.2.1",
"litesvm": "0.2.0"
},
"devDependencies": {
"@types/bn.js": "^5.1.0",
"@types/jest": "^29.0.3",
"@types/node": "^22.13.13",
"jest": "^29.0.3",
"nock": "^14.0.2",
"prettier": "^2.6.2",
"ts-jest": "^29.0.2",
"ts-node": "^10.9.2",
"typescript": "5"
}
"scripts": {
"lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
"lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check",
"test": "make test-programs",
"build": "make build-programs"
},
"dependencies": {
"@coral-xyz/anchor": "^0.31.1",
"@m0-foundation/solana-m-sdk": "https://gitpkg.vercel.app/m0-foundation/solana-m/sdk?0991eb829a52f6ba5969fd47d3100ef6871f0a6d&scripts.postinstall=yarn%20build",
"@solana-developers/helpers": "^2.7.0",
"@solana/spl-token": "^0.4.13",
"@solana/web3.js": "^1.98",
"anchor-litesvm": "0.1.2",
"bn.js": "^5.2.1",
"litesvm": "0.2.0"
},
"devDependencies": {
"@types/bn.js": "^5.1.0",
"@types/jest": "^29.0.3",
"@types/node": "^22.13.13",
"jest": "^29.0.3",
"nock": "^14.0.2",
"prettier": "^2.6.2",
"ts-jest": "^29.0.2",
"ts-node": "^10.9.2",
"typescript": "5"
}
}
13 changes: 8 additions & 5 deletions programs/scaled_ui_ext/Cargo.toml → programs/m_ext/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,26 +1,29 @@
[package]
name = "scaled_ui_ext"
name = "m_ext"
version = "0.1.0"
description = "Created with Anchor"
description = "M extension program with various yield distribution options chosen at compile time"
edition = "2021"

[lib]
crate-type = ["cdylib", "lib"]
name = "scaled_ui_ext"
name = "m_ext"

[features]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
cpi = ["no-entrypoint"]
default = []
default = ["scaled-ui"]
idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]

# yield features
scaled-ui = []
no-yield = []

[dependencies]
anchor-lang.workspace = true
anchor-spl.workspace = true
spl-token-2022.workspace = true
cfg-if.workspace = true
solana-security-txt.workspace = true
solana-program.workspace = true
earn.workspace = true
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,22 +1,29 @@
// external dependencies
use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey,
token_interface::{Mint, Token2022, TokenAccount},
};
use anchor_spl::token_interface::{Mint, Token2022, TokenAccount};
use cfg_if::cfg_if;

#[cfg(feature = "scaled-ui")]
use anchor_spl::token_2022_extensions::spl_pod::optional_keys::OptionalNonZeroPubkey;
#[cfg(feature = "scaled-ui")]
use spl_token_2022::extension::{
scaled_ui_amount::ScaledUiAmountConfig, BaseStateWithExtensions, ExtensionType,
StateWithExtensions,
};

// local dependencies
#[cfg(feature = "scaled-ui")]
use crate::{
constants::{ANCHOR_DISCRIMINATOR_SIZE, INDEX_SCALE_U64, ONE_HUNDRED_PERCENT_U64},
errors::ExtError,
state::{ExtGlobal, EXT_GLOBAL_SEED, MINT_AUTHORITY_SEED, M_VAULT_SEED},
constants::{INDEX_SCALE_U64, ONE_HUNDRED_PERCENT_U64},
utils::conversion::sync_multiplier,
};

use crate::{
constants::ANCHOR_DISCRIMINATOR_SIZE,
errors::ExtError,
state::{ExtGlobal, YieldConfig, EXT_GLOBAL_SEED, MINT_AUTHORITY_SEED, M_VAULT_SEED},
};

use earn::{
state::{Global as EarnGlobal, GLOBAL_SEED as EARN_GLOBAL_SEED},
ID as EARN_PROGRAM,
Expand Down Expand Up @@ -82,8 +89,6 @@ pub struct Initialize<'info> {

pub ext_token_program: Program<'info, Token2022>,

pub associated_token_program: Program<'info, AssociatedToken>,

pub system_program: Program<'info, System>,
}

Expand All @@ -94,44 +99,47 @@ impl Initialize<'_> {
// The ext_mint must have a supply of 0 to start.
// The wrap authorities are validated and stored in the global account.
// The fee_bps is validated to be within the allowed range.

fn validate(&self, wrap_authorities: &[Pubkey], fee_bps: u64) -> Result<()> {
fn validate(&self, wrap_authorities: &[Pubkey], _fee_bps: u64) -> Result<()> {
// Validate the ext_mint_authority PDA is the mint authority for the ext mint
let ext_mint_authority = self.ext_mint_authority.key();
if self.ext_mint.mint_authority.unwrap_or_default() != ext_mint_authority {
return err!(ExtError::InvalidMint);
}

// Validate that the ext mint has the ScaledUiAmount extension and
// that the ext mint authority is the extension authority
{
// explicit scope to drop the borrow at the end of the code block
let ext_account_info = &self.ext_mint.to_account_info();
let ext_data = ext_account_info.try_borrow_data()?;
let ext_mint_data =
StateWithExtensions::<spl_token_2022::state::Mint>::unpack(&ext_data)?;
let extensions = ext_mint_data.get_extension_types()?;

if !extensions.contains(&ExtensionType::ScaledUiAmount) {
return err!(ExtError::InvalidMint);
}

let scaled_ui_config = ext_mint_data.get_extension::<ScaledUiAmountConfig>()?;
if scaled_ui_config.authority != OptionalNonZeroPubkey(ext_mint_authority) {
return err!(ExtError::InvalidMint);
}
}

// Validate the fee_bps is within the allowed range
if fee_bps > ONE_HUNDRED_PERCENT_U64 {
return err!(ExtError::InvalidParam);
}

// Validate and create the wrap authorities array
if wrap_authorities.len() > 10 {
return err!(ExtError::InvalidParam);
}

cfg_if! {
if #[cfg(feature = "scaled-ui")] {
// Validate that the ext mint has the ScaledUiAmount extension and
// that the ext mint authority is the extension authority
{
// explicit scope to drop the borrow at the end of the code block
let ext_account_info = &self.ext_mint.to_account_info();
let ext_data = ext_account_info.try_borrow_data()?;
let ext_mint_data =
StateWithExtensions::<spl_token_2022::state::Mint>::unpack(&ext_data)?;
let extensions = ext_mint_data.get_extension_types()?;

if !extensions.contains(&ExtensionType::ScaledUiAmount) {
return err!(ExtError::InvalidMint);
}

let scaled_ui_config = ext_mint_data.get_extension::<ScaledUiAmountConfig>()?;
if scaled_ui_config.authority != OptionalNonZeroPubkey(ext_mint_authority) {
return err!(ExtError::InvalidMint);
}
}

// Validate the fee_bps is within the allowed range
if _fee_bps > ONE_HUNDRED_PERCENT_U64 {
return err!(ExtError::InvalidParam);
}
}
}

Ok(())
}

Expand All @@ -149,25 +157,37 @@ impl Initialize<'_> {
wrap_authorities_array[i] = *authority;
}

let yield_config: YieldConfig;
cfg_if! {
if #[cfg(feature = "scaled-ui")] {
yield_config = YieldConfig {
fee_bps,
last_m_index: ctx.accounts.m_earn_global_account.index,
last_ext_index: INDEX_SCALE_U64, // we set the extension index to 1.0 initially
};
} else {
yield_config = YieldConfig {};
}
}

// Initialize the ExtGlobal account
ctx.accounts.global_account.set_inner(ExtGlobal {
admin: ctx.accounts.admin.key(),
ext_mint: ctx.accounts.ext_mint.key(),
m_mint: ctx.accounts.m_mint.key(),
m_earn_global_account: ctx.accounts.m_earn_global_account.key(),
fee_bps,
last_m_index: ctx.accounts.m_earn_global_account.index,
last_ext_index: INDEX_SCALE_U64, // we set the extension index to 1.0 initially
bump: ctx.bumps.global_account,
m_vault_bump: ctx.bumps.m_vault,
ext_mint_authority_bump: ctx.bumps.ext_mint_authority,
wrap_authorities: wrap_authorities_array,
yield_config,
});

// Set the ScaledUi multiplier to 1.0
// We can do this by calling the sync_multiplier function
// when the last_m_index equals the index on the m_earn_global_account
// and having last_ext_index set to 1e12
#[cfg(feature = "scaled-ui")]
sync_multiplier(
&mut ctx.accounts.ext_mint,
&mut ctx.accounts.global_account,
Expand Down
Loading
Loading