Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit f914672

Browse files
committed
token-2022: Add Pausable extension
#### Problem Users want a more Ethereum-style token experience by being able to "pause" their token, similar to the "Pausable" interface. When a mint is paused, tokens cannot be minted, burned, or transferred. #### Summary of changes Add the extension and some tests. It covers the following interactions: * mint / mint-checked * burn / burn-checked * transfer / transfer-checked / transfer-with-fee * confidential transfer / confidential transfer with fee * confidential mint / confidential burn Unfortunately, the confidential mint / burn extension doesn't have testing, so I couldn't get a full end-to-end test for it. Also note that it's still possible to: * move withheld tokens * initialize token accounts * close token accounts * set authority * freeze / thaw * approve / revoke * almost every other bit of extension management
1 parent 96a1575 commit f914672

File tree

13 files changed

+961
-4
lines changed

13 files changed

+961
-4
lines changed

token/client/src/token.rs

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ use {
4343
ConfidentialTransferFeeConfig,
4444
},
4545
cpi_guard, default_account_state, group_member_pointer, group_pointer,
46-
interest_bearing_mint, memo_transfer, metadata_pointer, scaled_ui_amount, transfer_fee,
47-
transfer_hook, BaseStateWithExtensions, Extension, ExtensionType,
46+
interest_bearing_mint, memo_transfer, metadata_pointer, pausable, scaled_ui_amount,
47+
transfer_fee, transfer_hook, BaseStateWithExtensions, Extension, ExtensionType,
4848
StateWithExtensionsOwned,
4949
},
5050
instruction, offchain,
@@ -193,6 +193,9 @@ pub enum ExtensionInitializationParams {
193193
authority: Option<Pubkey>,
194194
multiplier: f64,
195195
},
196+
PausableConfig {
197+
authority: Pubkey,
198+
},
196199
}
197200
impl ExtensionInitializationParams {
198201
/// Get the extension type associated with the init params
@@ -213,6 +216,7 @@ impl ExtensionInitializationParams {
213216
Self::GroupPointer { .. } => ExtensionType::GroupPointer,
214217
Self::GroupMemberPointer { .. } => ExtensionType::GroupMemberPointer,
215218
Self::ScaledUiAmountConfig { .. } => ExtensionType::ScaledUiAmount,
219+
Self::PausableConfig { .. } => ExtensionType::Pausable,
216220
}
217221
}
218222
/// Generate an appropriate initialization instruction for the given mint
@@ -331,6 +335,9 @@ impl ExtensionInitializationParams {
331335
authority,
332336
multiplier,
333337
),
338+
Self::PausableConfig { authority } => {
339+
pausable::instruction::initialize(token_program_id, mint, &authority)
340+
}
334341
}
335342
}
336343
}
@@ -1753,6 +1760,48 @@ where
17531760
.await
17541761
}
17551762

1763+
/// Pause transferring, minting, and burning on the mint
1764+
pub async fn pause<S: Signers>(
1765+
&self,
1766+
authority: &Pubkey,
1767+
signing_keypairs: &S,
1768+
) -> TokenResult<T::Output> {
1769+
let signing_pubkeys = signing_keypairs.pubkeys();
1770+
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1771+
1772+
self.process_ixs(
1773+
&[pausable::instruction::pause(
1774+
&self.program_id,
1775+
self.get_address(),
1776+
authority,
1777+
&multisig_signers,
1778+
)?],
1779+
signing_keypairs,
1780+
)
1781+
.await
1782+
}
1783+
1784+
/// Resume transferring, minting, and burning on the mint
1785+
pub async fn resume<S: Signers>(
1786+
&self,
1787+
authority: &Pubkey,
1788+
signing_keypairs: &S,
1789+
) -> TokenResult<T::Output> {
1790+
let signing_pubkeys = signing_keypairs.pubkeys();
1791+
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
1792+
1793+
self.process_ixs(
1794+
&[pausable::instruction::resume(
1795+
&self.program_id,
1796+
self.get_address(),
1797+
authority,
1798+
&multisig_signers,
1799+
)?],
1800+
signing_keypairs,
1801+
)
1802+
.await
1803+
}
1804+
17561805
/// Prevent unsafe usage of token account through CPI
17571806
pub async fn enable_cpi_guard<S: Signers>(
17581807
&self,

token/program-2022-test/tests/confidential_transfer.rs

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1617,6 +1617,86 @@ async fn confidential_transfer_transfer() {
16171617
.await;
16181618
}
16191619

1620+
#[cfg(feature = "zk-ops")]
1621+
#[tokio::test]
1622+
async fn pause_confidential_transfer() {
1623+
let authority = Keypair::new();
1624+
let pausable_authority = Keypair::new();
1625+
let auto_approve_new_accounts = true;
1626+
let auditor_elgamal_keypair = ElGamalKeypair::new_rand();
1627+
let auditor_elgamal_pubkey = (*auditor_elgamal_keypair.pubkey()).into();
1628+
1629+
let mut context = TestContext::new().await;
1630+
context
1631+
.init_token_with_mint(vec![
1632+
ExtensionInitializationParams::ConfidentialTransferMint {
1633+
authority: Some(authority.pubkey()),
1634+
auto_approve_new_accounts,
1635+
auditor_elgamal_pubkey: Some(auditor_elgamal_pubkey),
1636+
},
1637+
ExtensionInitializationParams::PausableConfig {
1638+
authority: pausable_authority.pubkey(),
1639+
},
1640+
])
1641+
.await
1642+
.unwrap();
1643+
1644+
let TokenContext {
1645+
token,
1646+
alice,
1647+
bob,
1648+
mint_authority,
1649+
decimals,
1650+
..
1651+
} = context.token_context.unwrap();
1652+
1653+
let alice_meta = ConfidentialTokenAccountMeta::new_with_tokens(
1654+
&token,
1655+
&alice,
1656+
None,
1657+
false,
1658+
false,
1659+
&mint_authority,
1660+
42,
1661+
decimals,
1662+
)
1663+
.await;
1664+
1665+
let bob_meta = ConfidentialTokenAccountMeta::new(&token, &bob, Some(2), false, false).await;
1666+
1667+
// pause it
1668+
token
1669+
.pause(&pausable_authority.pubkey(), &[&pausable_authority])
1670+
.await
1671+
.unwrap();
1672+
let error = confidential_transfer_with_option(
1673+
&token,
1674+
&alice_meta.token_account,
1675+
&bob_meta.token_account,
1676+
&alice.pubkey(),
1677+
10,
1678+
&alice_meta.elgamal_keypair,
1679+
&alice_meta.aes_key,
1680+
bob_meta.elgamal_keypair.pubkey(),
1681+
Some(auditor_elgamal_keypair.pubkey()),
1682+
None,
1683+
&[&alice],
1684+
ConfidentialTransferOption::InstructionData,
1685+
)
1686+
.await
1687+
.unwrap_err();
1688+
1689+
assert_eq!(
1690+
error,
1691+
TokenClientError::Client(Box::new(TransportError::TransactionError(
1692+
TransactionError::InstructionError(
1693+
0,
1694+
InstructionError::Custom(TokenError::MintPaused as u32)
1695+
)
1696+
)))
1697+
);
1698+
}
1699+
16201700
#[cfg(feature = "zk-ops")]
16211701
async fn confidential_transfer_transfer_with_option(option: ConfidentialTransferOption) {
16221702
let authority = Keypair::new();
@@ -2328,6 +2408,107 @@ async fn confidential_transfer_transfer_with_fee() {
23282408
.await;
23292409
}
23302410

2411+
#[cfg(feature = "zk-ops")]
2412+
#[tokio::test]
2413+
async fn pause_confidential_transfer_with_fee() {
2414+
let transfer_fee_authority = Keypair::new();
2415+
let withdraw_withheld_authority = Keypair::new();
2416+
2417+
let pausable_authority = Keypair::new();
2418+
let confidential_transfer_authority = Keypair::new();
2419+
let auto_approve_new_accounts = true;
2420+
let auditor_elgamal_keypair = ElGamalKeypair::new_rand();
2421+
let auditor_elgamal_pubkey = (*auditor_elgamal_keypair.pubkey()).into();
2422+
2423+
let confidential_transfer_fee_authority = Keypair::new();
2424+
let withdraw_withheld_authority_elgamal_keypair = ElGamalKeypair::new_rand();
2425+
let withdraw_withheld_authority_elgamal_pubkey =
2426+
(*withdraw_withheld_authority_elgamal_keypair.pubkey()).into();
2427+
2428+
let mut context = TestContext::new().await;
2429+
context
2430+
.init_token_with_mint(vec![
2431+
ExtensionInitializationParams::TransferFeeConfig {
2432+
transfer_fee_config_authority: Some(transfer_fee_authority.pubkey()),
2433+
withdraw_withheld_authority: Some(withdraw_withheld_authority.pubkey()),
2434+
transfer_fee_basis_points: TEST_FEE_BASIS_POINTS,
2435+
maximum_fee: TEST_MAXIMUM_FEE,
2436+
},
2437+
ExtensionInitializationParams::ConfidentialTransferMint {
2438+
authority: Some(confidential_transfer_authority.pubkey()),
2439+
auto_approve_new_accounts,
2440+
auditor_elgamal_pubkey: Some(auditor_elgamal_pubkey),
2441+
},
2442+
ExtensionInitializationParams::ConfidentialTransferFeeConfig {
2443+
authority: Some(confidential_transfer_fee_authority.pubkey()),
2444+
withdraw_withheld_authority_elgamal_pubkey,
2445+
},
2446+
ExtensionInitializationParams::PausableConfig {
2447+
authority: pausable_authority.pubkey(),
2448+
},
2449+
])
2450+
.await
2451+
.unwrap();
2452+
2453+
let TokenContext {
2454+
token,
2455+
alice,
2456+
bob,
2457+
mint_authority,
2458+
decimals,
2459+
..
2460+
} = context.token_context.unwrap();
2461+
2462+
let alice_meta = ConfidentialTokenAccountMeta::new_with_tokens(
2463+
&token,
2464+
&alice,
2465+
None,
2466+
false,
2467+
true,
2468+
&mint_authority,
2469+
100,
2470+
decimals,
2471+
)
2472+
.await;
2473+
2474+
let bob_meta = ConfidentialTokenAccountMeta::new(&token, &bob, None, false, true).await;
2475+
2476+
token
2477+
.pause(&pausable_authority.pubkey(), &[&pausable_authority])
2478+
.await
2479+
.unwrap();
2480+
2481+
let error = confidential_transfer_with_fee_with_option(
2482+
&token,
2483+
&alice_meta.token_account,
2484+
&bob_meta.token_account,
2485+
&alice.pubkey(),
2486+
10,
2487+
&alice_meta.elgamal_keypair,
2488+
&alice_meta.aes_key,
2489+
bob_meta.elgamal_keypair.pubkey(),
2490+
Some(auditor_elgamal_keypair.pubkey()),
2491+
withdraw_withheld_authority_elgamal_keypair.pubkey(),
2492+
TEST_FEE_BASIS_POINTS,
2493+
TEST_MAXIMUM_FEE,
2494+
None,
2495+
&[&alice],
2496+
ConfidentialTransferOption::InstructionData,
2497+
)
2498+
.await
2499+
.unwrap_err();
2500+
2501+
assert_eq!(
2502+
error,
2503+
TokenClientError::Client(Box::new(TransportError::TransactionError(
2504+
TransactionError::InstructionError(
2505+
0,
2506+
InstructionError::Custom(TokenError::MintPaused as u32)
2507+
)
2508+
)))
2509+
);
2510+
}
2511+
23312512
#[cfg(feature = "zk-ops")]
23322513
async fn confidential_transfer_transfer_with_fee_with_option(option: ConfidentialTransferOption) {
23332514
let transfer_fee_authority = Keypair::new();

0 commit comments

Comments
 (0)