sememe 0.0.0-stage → 0.4.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +2667 -2
- package/bin/sememe.mjs +556 -0
- package/index.mjs +48 -0
- package/interface/ata-ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL.json +62 -0
- package/interface/native-11111111111111111111111111111111.json +226 -0
- package/interface/native-ComputeBudget111111111111111111111111111111.json +72 -0
- package/interface/raydium-675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8.json +352 -0
- package/interface/token-TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA.json +334 -0
- package/interface/token-TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb.json +535 -0
- package/interface/token-metadata.json +132 -0
- package/package.json +71 -4
- package/src/account-list.mjs +308 -0
- package/src/account-union.mjs +523 -0
- package/src/account.mjs +199 -0
- package/src/acquire.mjs +145 -0
- package/src/adopt.mjs +205 -0
- package/src/ata-interface.mjs +276 -0
- package/src/ata.mjs +223 -0
- package/src/base58.mjs +96 -0
- package/src/canonical.mjs +24 -0
- package/src/cli-surface.mjs +300 -0
- package/src/codec.mjs +1069 -0
- package/src/conformance.mjs +431 -0
- package/src/corpus/harvest-compute-budget-2026-10-31.json +19122 -0
- package/src/corpus/harvest-system-2026-10-31.json +26090 -0
- package/src/decode.mjs +286 -0
- package/src/dialect.mjs +145 -0
- package/src/discriminators.mjs +177 -0
- package/src/drift.mjs +172 -0
- package/src/gap.mjs +205 -0
- package/src/lighthouse.mjs +168 -0
- package/src/lock.mjs +440 -0
- package/src/native-audit.mjs +56 -0
- package/src/native-corpus.mjs +57 -0
- package/src/native-interface.mjs +252 -0
- package/src/native.mjs +324 -0
- package/src/peer.mjs +95 -0
- package/src/program-id.mjs +74 -0
- package/src/pull.mjs +117 -0
- package/src/rawle.mjs +450 -0
- package/src/raydium-interface.mjs +747 -0
- package/src/raydium.mjs +391 -0
- package/src/receipt-digest.mjs +84 -0
- package/src/receipt-name.mjs +128 -0
- package/src/receipt-order.mjs +56 -0
- package/src/shank.mjs +73 -0
- package/src/short-forms.mjs +209 -0
- package/src/store-read.mjs +250 -0
- package/src/store-write.mjs +46 -0
- package/src/token-interface.mjs +394 -0
- package/src/token-metadata-interface.mjs +371 -0
- package/src/token-metadata.mjs +317 -0
- package/src/token.mjs +443 -0
- package/src/union.mjs +428 -0
- package/vendor/anza-solana-sdk/8db406e2edfafa25939ec782b77b5b6f30de164d/LICENSE.txt +201 -0
- package/vendor/anza-solana-sdk/8db406e2edfafa25939ec782b77b5b6f30de164d/compute-budget-interface/src/lib.rs +93 -0
- package/vendor/anza-solana-sdk/8db406e2edfafa25939ec782b77b5b6f30de164d/system-interface/src/instruction.rs +1899 -0
- package/vendor/lighthouse/4c579479c98635e419b1b167f08be02a71604a71/LICENSE +21 -0
- package/vendor/lighthouse/4c579479c98635e419b1b167f08be02a71604a71/lighthouse.json +2769 -0
- package/vendor/raydium-amm/91739d8d9f5cf347e0ce14ebe4d2bb7069fb31b6/LICENSE +201 -0
- package/vendor/raydium-amm/91739d8d9f5cf347e0ce14ebe4d2bb7069fb31b6/program/src/instruction.rs +1189 -0
- package/vendor/raydium-amm/91739d8d9f5cf347e0ce14ebe4d2bb7069fb31b6/program/src/state.rs +1348 -0
- package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/LICENSE +201 -0
- package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/interface/src/instruction.rs +169 -0
- package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/interface/src/lib.rs +12 -0
- package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/program/src/processor.rs +308 -0
- package/vendor/spl-token/737788ff5bcc3f1f44cfb222f25483187650be76/LICENSE.txt +176 -0
- package/vendor/spl-token/737788ff5bcc3f1f44cfb222f25483187650be76/interface/src/instruction.rs +1963 -0
- package/vendor/spl-token-2022/5251dcc07ecbcb721d6c4f0231090defadf00311/LICENSE.txt +176 -0
- package/vendor/spl-token-2022/5251dcc07ecbcb721d6c4f0231090defadf00311/interface/src/instruction.rs +2868 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/LICENSE +201 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/error.rs +58 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/instruction.rs +596 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/lib.rs +21 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/state.rs +229 -0
|
@@ -0,0 +1,1899 @@
|
|
|
1
|
+
//! Instructions and constructors for the system program.
|
|
2
|
+
//!
|
|
3
|
+
//! The system program is responsible for the creation of accounts and [nonce
|
|
4
|
+
//! accounts][na]. It is responsible for transferring lamports from accounts
|
|
5
|
+
//! owned by the system program, including typical user wallet accounts.
|
|
6
|
+
//!
|
|
7
|
+
//! [na]: https://docs.solanalabs.com/implemented-proposals/durable-tx-nonces
|
|
8
|
+
//!
|
|
9
|
+
//! Account creation typically involves three steps: [`allocate`] space,
|
|
10
|
+
//! [`transfer`] lamports for rent, [`assign`] to its owning program. The
|
|
11
|
+
//! [`create_account`] function does all three at once. All new accounts must
|
|
12
|
+
//! contain enough lamports to be [rent exempt], or else the creation
|
|
13
|
+
//! instruction will fail.
|
|
14
|
+
//!
|
|
15
|
+
//! [rent exempt]: https://solana.com/docs/core/accounts#rent-exemption
|
|
16
|
+
//!
|
|
17
|
+
//! The [`create_account`] function requires that the account have zero
|
|
18
|
+
//! lamports. [`create_account_allow_prefund`] allows for the account to have
|
|
19
|
+
//! lamports prefunded; note that without feature activation of [SIMD-0312],
|
|
20
|
+
//! [`create_account_allow_prefund`] will fail downstream.
|
|
21
|
+
//!
|
|
22
|
+
//! [SIMD-0312]: https://github.com/solana-foundation/solana-improvement-documents/pull/312
|
|
23
|
+
//!
|
|
24
|
+
//! The accounts created by the System program can either be user-controlled,
|
|
25
|
+
//! where the secret keys are held outside the blockchain,
|
|
26
|
+
//! or they can be [program derived addresses][pda],
|
|
27
|
+
//! where write access to accounts is granted by an owning program.
|
|
28
|
+
//!
|
|
29
|
+
//! [pda]: https://docs.rs/solana-address/latest/solana_address/struct.Address.html#method.find_program_address
|
|
30
|
+
//!
|
|
31
|
+
//! Most of the functions in this module construct an [`Instruction`], that must
|
|
32
|
+
//! be submitted to the runtime for execution, either via RPC, typically with
|
|
33
|
+
//! [`RpcClient`], or through [cross-program invocation][cpi].
|
|
34
|
+
//!
|
|
35
|
+
//! When invoking through CPI, the [`invoke`] or [`invoke_signed`] instruction
|
|
36
|
+
//! requires all account references to be provided explicitly as [`AccountInfo`]
|
|
37
|
+
//! values. The account references required are specified in the documentation
|
|
38
|
+
//! for the [`SystemInstruction`] variants for each System program instruction,
|
|
39
|
+
//! and these variants are linked from the documentation for their constructors.
|
|
40
|
+
//!
|
|
41
|
+
//! [`RpcClient`]: https://docs.rs/solana-client/latest/solana_client/rpc_client/struct.RpcClient.html
|
|
42
|
+
//! [cpi]: https://docs.rs/solana-cpi/latest/solana_cpi/index.html
|
|
43
|
+
//! [`invoke`]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke.html
|
|
44
|
+
//! [`invoke_signed`]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke_signed.html
|
|
45
|
+
//! [`AccountInfo`]: https://docs.rs/solana-account-info/latest/solana_account_info/struct.AccountInfo.html
|
|
46
|
+
//! [`Instruction`]:
|
|
47
|
+
//! https://docs.rs/solana-instruction/latest/solana_instruction/struct.Instruction.html
|
|
48
|
+
|
|
49
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
50
|
+
use {
|
|
51
|
+
crate::program::ID,
|
|
52
|
+
alloc::{string::ToString, vec, vec::Vec},
|
|
53
|
+
solana_instruction::{AccountMeta, Instruction},
|
|
54
|
+
};
|
|
55
|
+
#[cfg(feature = "alloc")]
|
|
56
|
+
use {alloc::string::String, solana_address::Address};
|
|
57
|
+
|
|
58
|
+
// Inline some constants to avoid dependencies.
|
|
59
|
+
//
|
|
60
|
+
// Note: replace these inline IDs with the corresponding value from
|
|
61
|
+
// `solana_sdk_ids` once the version is updated to 2.2.0.
|
|
62
|
+
|
|
63
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
64
|
+
const RECENT_BLOCKHASHES_ID: Address =
|
|
65
|
+
Address::from_str_const("SysvarRecentB1ockHashes11111111111111111111");
|
|
66
|
+
|
|
67
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
68
|
+
const RENT_ID: Address = Address::from_str_const("SysvarRent111111111111111111111111111111111");
|
|
69
|
+
|
|
70
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
71
|
+
#[cfg(test)]
|
|
72
|
+
static_assertions::const_assert_eq!(solana_nonce::state::State::size(), NONCE_STATE_SIZE);
|
|
73
|
+
/// The serialized size of the nonce state.
|
|
74
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
75
|
+
const NONCE_STATE_SIZE: usize = 80;
|
|
76
|
+
|
|
77
|
+
/// An instruction to the system program.
|
|
78
|
+
#[cfg_attr(
|
|
79
|
+
feature = "frozen-abi",
|
|
80
|
+
solana_frozen_abi_macro::frozen_abi(digest = "CBvp4X1gf36kwDqnprAa6MpKckptiAHfXSxFRHFnNRVw"),
|
|
81
|
+
derive(
|
|
82
|
+
solana_frozen_abi_macro::AbiExample,
|
|
83
|
+
solana_frozen_abi_macro::AbiEnumVisitor
|
|
84
|
+
)
|
|
85
|
+
)]
|
|
86
|
+
#[cfg_attr(
|
|
87
|
+
feature = "serde",
|
|
88
|
+
derive(serde_derive::Deserialize, serde_derive::Serialize)
|
|
89
|
+
)]
|
|
90
|
+
#[cfg_attr(feature = "wincode", derive(wincode::SchemaRead, wincode::SchemaWrite))]
|
|
91
|
+
#[cfg(feature = "alloc")]
|
|
92
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
93
|
+
pub enum SystemInstruction {
|
|
94
|
+
/// Create a new account
|
|
95
|
+
///
|
|
96
|
+
/// # Account references
|
|
97
|
+
/// 0. `[WRITE, SIGNER]` Funding account
|
|
98
|
+
/// 1. `[WRITE, SIGNER]` New account
|
|
99
|
+
CreateAccount {
|
|
100
|
+
/// Number of lamports to transfer to the new account
|
|
101
|
+
lamports: u64,
|
|
102
|
+
|
|
103
|
+
/// Number of bytes of memory to allocate
|
|
104
|
+
space: u64,
|
|
105
|
+
|
|
106
|
+
/// Address of program that will own the new account
|
|
107
|
+
owner: Address,
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
/// Assign account to a program
|
|
111
|
+
///
|
|
112
|
+
/// # Account references
|
|
113
|
+
/// 0. `[WRITE, SIGNER]` Assigned account public key
|
|
114
|
+
Assign {
|
|
115
|
+
/// Owner program account
|
|
116
|
+
owner: Address,
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
/// Transfer lamports
|
|
120
|
+
///
|
|
121
|
+
/// # Account references
|
|
122
|
+
/// 0. `[WRITE, SIGNER]` Funding account
|
|
123
|
+
/// 1. `[WRITE]` Recipient account
|
|
124
|
+
Transfer { lamports: u64 },
|
|
125
|
+
|
|
126
|
+
/// Create a new account at an address derived from a base address and a seed
|
|
127
|
+
///
|
|
128
|
+
/// # Account references
|
|
129
|
+
/// 0. `[WRITE, SIGNER]` Funding account
|
|
130
|
+
/// 1. `[WRITE]` Created account
|
|
131
|
+
/// 2. `[SIGNER]` (optional) Base account; the account matching the base address below must be
|
|
132
|
+
/// provided as a signer, but may be the same as the funding account
|
|
133
|
+
/// and provided as account 0
|
|
134
|
+
CreateAccountWithSeed {
|
|
135
|
+
/// Base address
|
|
136
|
+
base: Address,
|
|
137
|
+
|
|
138
|
+
/// String of ASCII chars, no longer than `Address::MAX_SEED_LEN`
|
|
139
|
+
seed: String,
|
|
140
|
+
|
|
141
|
+
/// Number of lamports to transfer to the new account
|
|
142
|
+
lamports: u64,
|
|
143
|
+
|
|
144
|
+
/// Number of bytes of memory to allocate
|
|
145
|
+
space: u64,
|
|
146
|
+
|
|
147
|
+
/// Owner program account address
|
|
148
|
+
owner: Address,
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
/// Consumes a stored nonce, replacing it with a successor
|
|
152
|
+
///
|
|
153
|
+
/// # Account references
|
|
154
|
+
/// 0. `[WRITE]` Nonce account
|
|
155
|
+
/// 1. `[]` RecentBlockhashes sysvar
|
|
156
|
+
/// 2. `[SIGNER]` Nonce authority
|
|
157
|
+
AdvanceNonceAccount,
|
|
158
|
+
|
|
159
|
+
/// Withdraw funds from a nonce account
|
|
160
|
+
///
|
|
161
|
+
/// # Account references
|
|
162
|
+
/// 0. `[WRITE]` Nonce account
|
|
163
|
+
/// 1. `[WRITE]` Recipient account
|
|
164
|
+
/// 2. `[]` RecentBlockhashes sysvar
|
|
165
|
+
/// 3. `[]` Rent sysvar
|
|
166
|
+
/// 4. `[SIGNER]` Nonce authority
|
|
167
|
+
///
|
|
168
|
+
/// The `u64` parameter is the lamports to withdraw, which must leave the
|
|
169
|
+
/// account balance above the rent exempt reserve or at zero.
|
|
170
|
+
WithdrawNonceAccount(u64),
|
|
171
|
+
|
|
172
|
+
/// Drive state of Uninitialized nonce account to Initialized, setting the nonce value
|
|
173
|
+
///
|
|
174
|
+
/// # Account references
|
|
175
|
+
/// 0. `[WRITE]` Nonce account
|
|
176
|
+
/// 1. `[]` RecentBlockhashes sysvar
|
|
177
|
+
/// 2. `[]` Rent sysvar
|
|
178
|
+
///
|
|
179
|
+
/// The `Address` parameter specifies the entity authorized to execute nonce
|
|
180
|
+
/// instruction on the account
|
|
181
|
+
///
|
|
182
|
+
/// No signatures are required to execute this instruction, enabling derived
|
|
183
|
+
/// nonce account addresses.
|
|
184
|
+
///
|
|
185
|
+
/// This instruction MUST be included within the same Transaction as the
|
|
186
|
+
/// system program's `CreateAccount` instruction that creates the account
|
|
187
|
+
/// being initialized. Otherwise another party can acquire ownership of the
|
|
188
|
+
/// uninitialized account.
|
|
189
|
+
InitializeNonceAccount(Address),
|
|
190
|
+
|
|
191
|
+
/// Change the entity authorized to execute nonce instructions on the account
|
|
192
|
+
///
|
|
193
|
+
/// # Account references
|
|
194
|
+
/// 0. `[WRITE]` Nonce account
|
|
195
|
+
/// 1. `[SIGNER]` Nonce authority
|
|
196
|
+
///
|
|
197
|
+
/// The `Address` parameter identifies the entity to authorize
|
|
198
|
+
AuthorizeNonceAccount(Address),
|
|
199
|
+
|
|
200
|
+
/// Allocate space in a (possibly new) account without funding
|
|
201
|
+
///
|
|
202
|
+
/// # Account references
|
|
203
|
+
/// 0. `[WRITE, SIGNER]` New account
|
|
204
|
+
Allocate {
|
|
205
|
+
/// Number of bytes of memory to allocate
|
|
206
|
+
space: u64,
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
/// Allocate space for and assign an account at an address
|
|
210
|
+
/// derived from a base public key and a seed
|
|
211
|
+
///
|
|
212
|
+
/// # Account references
|
|
213
|
+
/// 0. `[WRITE]` Allocated account
|
|
214
|
+
/// 1. `[SIGNER]` Base account
|
|
215
|
+
AllocateWithSeed {
|
|
216
|
+
/// Base address
|
|
217
|
+
base: Address,
|
|
218
|
+
|
|
219
|
+
/// String of ASCII chars, no longer than `Address::MAX_SEED_LEN`
|
|
220
|
+
seed: String,
|
|
221
|
+
|
|
222
|
+
/// Number of bytes of memory to allocate
|
|
223
|
+
space: u64,
|
|
224
|
+
|
|
225
|
+
/// Owner program account
|
|
226
|
+
owner: Address,
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
/// Assign account to a program based on a seed
|
|
230
|
+
///
|
|
231
|
+
/// # Account references
|
|
232
|
+
/// 0. `[WRITE]` Assigned account
|
|
233
|
+
/// 1. `[SIGNER]` Base account
|
|
234
|
+
AssignWithSeed {
|
|
235
|
+
/// Base address
|
|
236
|
+
base: Address,
|
|
237
|
+
|
|
238
|
+
/// String of ASCII chars, no longer than `Address::MAX_SEED_LEN`
|
|
239
|
+
seed: String,
|
|
240
|
+
|
|
241
|
+
/// Owner program account
|
|
242
|
+
owner: Address,
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
/// Transfer lamports from a derived address
|
|
246
|
+
///
|
|
247
|
+
/// # Account references
|
|
248
|
+
/// 0. `[WRITE]` Funding account
|
|
249
|
+
/// 1. `[SIGNER]` Base for funding account
|
|
250
|
+
/// 2. `[WRITE]` Recipient account
|
|
251
|
+
TransferWithSeed {
|
|
252
|
+
/// Amount to transfer
|
|
253
|
+
lamports: u64,
|
|
254
|
+
|
|
255
|
+
/// Seed to use to derive the funding account address
|
|
256
|
+
from_seed: String,
|
|
257
|
+
|
|
258
|
+
/// Owner to use to derive the funding account address
|
|
259
|
+
from_owner: Address,
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
/// One-time idempotent upgrade of legacy nonce versions in order to bump
|
|
263
|
+
/// them out of chain blockhash domain.
|
|
264
|
+
///
|
|
265
|
+
/// # Account references
|
|
266
|
+
/// 0. `[WRITE]` Nonce account
|
|
267
|
+
UpgradeNonceAccount,
|
|
268
|
+
|
|
269
|
+
/// Create a new account without enforcing the invariant that the account's
|
|
270
|
+
/// current lamports must be 0.
|
|
271
|
+
///
|
|
272
|
+
/// This constructor is identical to [`create_account`] with the exception that it
|
|
273
|
+
/// **does not** check that the destination account (`to_address`) has a zero
|
|
274
|
+
/// lamport balance prior to creation. This enables patterns where you first transfer
|
|
275
|
+
/// lamports to prefund an account, then use `create_account_allow_prefund` as a single
|
|
276
|
+
/// CPI to transfer additional lamports, allocate space, and assign ownership.
|
|
277
|
+
///
|
|
278
|
+
/// Use [`create_account`] for typical account creation.
|
|
279
|
+
/// Use [`create_account_allow_prefund`] when the target account has already been
|
|
280
|
+
/// prefunded and you want to complete the creation process with a single CPI.
|
|
281
|
+
///
|
|
282
|
+
/// **Safety considerations**
|
|
283
|
+
/// As with `allocate` and `assign` when invoked manually, this instruction can brick
|
|
284
|
+
/// a wallet if used incorrectly; do not pass in a wallet system account as the new
|
|
285
|
+
/// account. This instruction does not prevent the new account from having more
|
|
286
|
+
/// lamports than required for rent exemption, and all lamports will become locked.
|
|
287
|
+
///
|
|
288
|
+
/// # Account references
|
|
289
|
+
/// If `lamports > 0` (meaning lamports are being transferred):
|
|
290
|
+
/// 0. `[WRITE, SIGNER]` New account
|
|
291
|
+
/// 1. `[WRITE, SIGNER]` Funding account
|
|
292
|
+
///
|
|
293
|
+
/// If `lamports == 0` (no lamports to be transferred), you may omit funding account:
|
|
294
|
+
/// 0. `[WRITE, SIGNER]` New account
|
|
295
|
+
CreateAccountAllowPrefund {
|
|
296
|
+
/// Number of lamports to transfer to the new account
|
|
297
|
+
lamports: u64,
|
|
298
|
+
|
|
299
|
+
/// Number of bytes of memory to allocate
|
|
300
|
+
space: u64,
|
|
301
|
+
|
|
302
|
+
/// Address of program that will own the new account
|
|
303
|
+
owner: Address,
|
|
304
|
+
},
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/// Create an instruction using bincode.
|
|
308
|
+
#[cfg(all(feature = "bincode", not(feature = "wincode")))]
|
|
309
|
+
#[inline(always)]
|
|
310
|
+
fn create_instruction<T>(program_id: Address, data: &T, accounts: Vec<AccountMeta>) -> Instruction
|
|
311
|
+
where
|
|
312
|
+
T: serde::Serialize,
|
|
313
|
+
{
|
|
314
|
+
Instruction::new_with_bincode(program_id, data, accounts)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/// Create an instruction using wincode.
|
|
318
|
+
#[cfg(feature = "wincode")]
|
|
319
|
+
#[inline(always)]
|
|
320
|
+
fn create_instruction<T>(program_id: Address, data: &T, accounts: Vec<AccountMeta>) -> Instruction
|
|
321
|
+
where
|
|
322
|
+
T: wincode::Serialize<Src = T>,
|
|
323
|
+
{
|
|
324
|
+
Instruction::new_with_wincode(program_id, data, accounts)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/// Create an account, failing if the account previously had any balance.
|
|
328
|
+
///
|
|
329
|
+
/// This function produces an [`Instruction`] which must be submitted in a
|
|
330
|
+
/// [`Transaction`] or [invoked] to take effect, containing a serialized
|
|
331
|
+
///
|
|
332
|
+
/// [`SystemInstruction::CreateAccount`].
|
|
333
|
+
///
|
|
334
|
+
/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html
|
|
335
|
+
/// [invoked]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke.html
|
|
336
|
+
///
|
|
337
|
+
/// Account creation typically involves three steps: [`allocate`] space,
|
|
338
|
+
/// [`transfer`] lamports for rent, [`assign`] to its owning program. The
|
|
339
|
+
/// [`create_account`] function does all three at once.
|
|
340
|
+
///
|
|
341
|
+
/// # Security issues
|
|
342
|
+
///
|
|
343
|
+
/// Using this function is a security issue if the `to_address` is predictable
|
|
344
|
+
/// by an attacker. The attacker can prefund the address with lamports and
|
|
345
|
+
/// thereby prevent the successful execution of the `create_account` call. This
|
|
346
|
+
/// can DoS an on-chain program.
|
|
347
|
+
///
|
|
348
|
+
/// # Required signers
|
|
349
|
+
///
|
|
350
|
+
/// The `from_address` and `to_address` signers must sign the transaction.
|
|
351
|
+
///
|
|
352
|
+
/// # Examples
|
|
353
|
+
///
|
|
354
|
+
/// These examples use a single invocation of
|
|
355
|
+
/// [`SystemInstruction::CreateAccount`] to create a new account, allocate some
|
|
356
|
+
/// space, transfer it the minimum lamports for rent exemption, and assign it to
|
|
357
|
+
/// the system program,
|
|
358
|
+
///
|
|
359
|
+
/// ## Example: client-side RPC
|
|
360
|
+
///
|
|
361
|
+
/// This example submits the instruction from an RPC client.
|
|
362
|
+
/// The `payer` and `new_account` are signers.
|
|
363
|
+
///
|
|
364
|
+
/// ```
|
|
365
|
+
/// # use solana_example_mocks::solana_keypair;
|
|
366
|
+
/// # use solana_example_mocks::solana_signer;
|
|
367
|
+
/// # use solana_example_mocks::solana_rpc_client;
|
|
368
|
+
/// # use solana_example_mocks::solana_transaction;
|
|
369
|
+
/// use solana_rpc_client::rpc_client::RpcClient;
|
|
370
|
+
/// use solana_address::Address;
|
|
371
|
+
/// use solana_keypair::Keypair;
|
|
372
|
+
/// use solana_signer::Signer;
|
|
373
|
+
/// use solana_system_interface::{instruction, program};
|
|
374
|
+
/// use solana_transaction::Transaction;
|
|
375
|
+
/// use anyhow::Result;
|
|
376
|
+
///
|
|
377
|
+
/// fn create_account(
|
|
378
|
+
/// client: &RpcClient,
|
|
379
|
+
/// payer: &Keypair,
|
|
380
|
+
/// new_account: &Keypair,
|
|
381
|
+
/// space: u64,
|
|
382
|
+
/// ) -> Result<()> {
|
|
383
|
+
/// let rent = client.get_minimum_balance_for_rent_exemption(space.try_into()?)?;
|
|
384
|
+
/// let instr = instruction::create_account(
|
|
385
|
+
/// &payer.pubkey(),
|
|
386
|
+
/// &new_account.pubkey(),
|
|
387
|
+
/// rent,
|
|
388
|
+
/// space,
|
|
389
|
+
/// &program::ID,
|
|
390
|
+
/// );
|
|
391
|
+
///
|
|
392
|
+
/// let blockhash = client.get_latest_blockhash()?;
|
|
393
|
+
/// let tx = Transaction::new_signed_with_payer(
|
|
394
|
+
/// &[instr],
|
|
395
|
+
/// Some(&payer.pubkey()),
|
|
396
|
+
/// &[payer, new_account],
|
|
397
|
+
/// blockhash,
|
|
398
|
+
/// );
|
|
399
|
+
///
|
|
400
|
+
/// let _sig = client.send_and_confirm_transaction(&tx)?;
|
|
401
|
+
///
|
|
402
|
+
/// Ok(())
|
|
403
|
+
/// }
|
|
404
|
+
/// # let payer = Keypair::new();
|
|
405
|
+
/// # let new_account = Keypair::new();
|
|
406
|
+
/// # let client = RpcClient::new(String::new());
|
|
407
|
+
/// # create_account(&client, &payer, &new_account, 0);
|
|
408
|
+
/// #
|
|
409
|
+
/// # Ok::<(), anyhow::Error>(())
|
|
410
|
+
/// ```
|
|
411
|
+
///
|
|
412
|
+
/// ## Example: on-chain program
|
|
413
|
+
///
|
|
414
|
+
/// This example submits the instruction from an on-chain Solana program. The
|
|
415
|
+
/// created account is a [program derived address][pda]. The `payer` and
|
|
416
|
+
/// `new_account_pda` are signers, with `new_account_pda` being signed for
|
|
417
|
+
/// virtually by the program itself via [`invoke_signed`], `payer` being signed
|
|
418
|
+
/// for by the client that submitted the transaction.
|
|
419
|
+
///
|
|
420
|
+
/// [pda]: https://docs.rs/solana-address/latest/solana_address/struct.Address.html#method.find_program_address
|
|
421
|
+
/// [`invoke_signed`]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke_signed.html
|
|
422
|
+
///
|
|
423
|
+
/// ```
|
|
424
|
+
/// use borsh::{BorshDeserialize, BorshSerialize};
|
|
425
|
+
/// use solana_account_info::{next_account_info, AccountInfo};
|
|
426
|
+
/// use solana_address::Address;
|
|
427
|
+
/// use solana_cpi::invoke_signed;
|
|
428
|
+
/// use solana_program_entrypoint::entrypoint;
|
|
429
|
+
/// use solana_program_error::ProgramResult;
|
|
430
|
+
/// use solana_system_interface::{instruction, program};
|
|
431
|
+
/// use solana_sysvar::{rent::Rent, Sysvar};
|
|
432
|
+
///
|
|
433
|
+
/// #[derive(BorshSerialize, BorshDeserialize, Debug)]
|
|
434
|
+
/// pub struct CreateAccountInstruction {
|
|
435
|
+
/// /// The PDA seed used to distinguish the new account from other PDAs
|
|
436
|
+
/// pub new_account_seed: [u8; 16],
|
|
437
|
+
/// /// The PDA bump seed
|
|
438
|
+
/// pub new_account_bump_seed: u8,
|
|
439
|
+
/// /// The amount of space to allocate for `new_account_pda`
|
|
440
|
+
/// pub space: u64,
|
|
441
|
+
/// }
|
|
442
|
+
///
|
|
443
|
+
/// entrypoint!(process_instruction);
|
|
444
|
+
///
|
|
445
|
+
/// fn process_instruction(
|
|
446
|
+
/// program_id: &Address,
|
|
447
|
+
/// accounts: &[AccountInfo],
|
|
448
|
+
/// instruction_data: &[u8],
|
|
449
|
+
/// ) -> ProgramResult {
|
|
450
|
+
/// let instr = CreateAccountInstruction::deserialize(&mut &instruction_data[..])?;
|
|
451
|
+
///
|
|
452
|
+
/// let account_info_iter = &mut accounts.iter();
|
|
453
|
+
///
|
|
454
|
+
/// let payer = next_account_info(account_info_iter)?;
|
|
455
|
+
/// let new_account_pda = next_account_info(account_info_iter)?;
|
|
456
|
+
/// let system_account = next_account_info(account_info_iter)?;
|
|
457
|
+
///
|
|
458
|
+
/// assert!(payer.is_signer);
|
|
459
|
+
/// assert!(payer.is_writable);
|
|
460
|
+
/// // Note that `new_account_pda` is not a signer yet.
|
|
461
|
+
/// // This program will sign for it via `invoke_signed`.
|
|
462
|
+
/// assert!(!new_account_pda.is_signer);
|
|
463
|
+
/// assert!(new_account_pda.is_writable);
|
|
464
|
+
/// assert!(program::check_id(system_account.key));
|
|
465
|
+
///
|
|
466
|
+
/// let new_account_seed = &instr.new_account_seed;
|
|
467
|
+
/// let new_account_bump_seed = instr.new_account_bump_seed;
|
|
468
|
+
///
|
|
469
|
+
/// let rent = Rent::get()?
|
|
470
|
+
/// .minimum_balance(instr.space.try_into().expect("overflow"));
|
|
471
|
+
///
|
|
472
|
+
/// invoke_signed(
|
|
473
|
+
/// &instruction::create_account(
|
|
474
|
+
/// payer.key,
|
|
475
|
+
/// new_account_pda.key,
|
|
476
|
+
/// rent,
|
|
477
|
+
/// instr.space,
|
|
478
|
+
/// &program::ID
|
|
479
|
+
/// ),
|
|
480
|
+
/// &[payer.clone(), new_account_pda.clone()],
|
|
481
|
+
/// &[&[
|
|
482
|
+
/// payer.key.as_ref(),
|
|
483
|
+
/// new_account_seed,
|
|
484
|
+
/// &[new_account_bump_seed],
|
|
485
|
+
/// ]],
|
|
486
|
+
/// )?;
|
|
487
|
+
///
|
|
488
|
+
/// Ok(())
|
|
489
|
+
/// }
|
|
490
|
+
/// ```
|
|
491
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
492
|
+
pub fn create_account(
|
|
493
|
+
from_address: &Address,
|
|
494
|
+
to_address: &Address,
|
|
495
|
+
lamports: u64,
|
|
496
|
+
space: u64,
|
|
497
|
+
owner: &Address,
|
|
498
|
+
) -> Instruction {
|
|
499
|
+
let account_metas = vec![
|
|
500
|
+
AccountMeta::new(*from_address, true),
|
|
501
|
+
AccountMeta::new(*to_address, true),
|
|
502
|
+
];
|
|
503
|
+
create_instruction(
|
|
504
|
+
ID,
|
|
505
|
+
&SystemInstruction::CreateAccount {
|
|
506
|
+
lamports,
|
|
507
|
+
space,
|
|
508
|
+
owner: *owner,
|
|
509
|
+
},
|
|
510
|
+
account_metas,
|
|
511
|
+
)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// we accept `to` as a parameter so that callers do their own error handling when
|
|
515
|
+
// calling create_with_seed()
|
|
516
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
517
|
+
pub fn create_account_with_seed(
|
|
518
|
+
from_address: &Address,
|
|
519
|
+
to_address: &Address, // must match create_with_seed(base, seed, owner)
|
|
520
|
+
base: &Address,
|
|
521
|
+
seed: &str,
|
|
522
|
+
lamports: u64,
|
|
523
|
+
space: u64,
|
|
524
|
+
owner: &Address,
|
|
525
|
+
) -> Instruction {
|
|
526
|
+
let mut account_metas = vec![
|
|
527
|
+
AccountMeta::new(*from_address, true),
|
|
528
|
+
AccountMeta::new(*to_address, false),
|
|
529
|
+
];
|
|
530
|
+
if base != from_address {
|
|
531
|
+
account_metas.push(AccountMeta::new_readonly(*base, true));
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
create_instruction(
|
|
535
|
+
ID,
|
|
536
|
+
&SystemInstruction::CreateAccountWithSeed {
|
|
537
|
+
base: *base,
|
|
538
|
+
seed: seed.to_string(),
|
|
539
|
+
lamports,
|
|
540
|
+
space,
|
|
541
|
+
owner: *owner,
|
|
542
|
+
},
|
|
543
|
+
account_metas,
|
|
544
|
+
)
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/// Assign ownership of an account from the system program.
|
|
548
|
+
///
|
|
549
|
+
/// This function produces an [`Instruction`] which must be submitted in a
|
|
550
|
+
/// [`Transaction`] or [invoked] to take effect, containing a serialized
|
|
551
|
+
/// [`SystemInstruction::Assign`].
|
|
552
|
+
///
|
|
553
|
+
/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html
|
|
554
|
+
/// [invoked]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke.html
|
|
555
|
+
///
|
|
556
|
+
/// # Required signers
|
|
557
|
+
///
|
|
558
|
+
/// The `address` signer must sign the transaction.
|
|
559
|
+
///
|
|
560
|
+
/// # Examples
|
|
561
|
+
///
|
|
562
|
+
/// These examples allocate space for an account, transfer it the minimum
|
|
563
|
+
/// balance for rent exemption, and assign the account to a program.
|
|
564
|
+
///
|
|
565
|
+
/// ## Example: client-side RPC
|
|
566
|
+
///
|
|
567
|
+
/// This example submits the instructions from an RPC client.
|
|
568
|
+
/// It assigns the account to a provided program account.
|
|
569
|
+
/// The `payer` and `new_account` are signers.
|
|
570
|
+
///
|
|
571
|
+
/// ```
|
|
572
|
+
/// # use solana_example_mocks::solana_keypair;
|
|
573
|
+
/// # use solana_example_mocks::solana_signer;
|
|
574
|
+
/// # use solana_example_mocks::solana_rpc_client;
|
|
575
|
+
/// # use solana_example_mocks::solana_transaction;
|
|
576
|
+
/// use solana_rpc_client::rpc_client::RpcClient;
|
|
577
|
+
/// use solana_address::Address;
|
|
578
|
+
/// use solana_keypair::Keypair;
|
|
579
|
+
/// use solana_signer::Signer;
|
|
580
|
+
/// use solana_system_interface::instruction;
|
|
581
|
+
/// use solana_transaction::Transaction;
|
|
582
|
+
/// use anyhow::Result;
|
|
583
|
+
///
|
|
584
|
+
/// fn create_account(
|
|
585
|
+
/// client: &RpcClient,
|
|
586
|
+
/// payer: &Keypair,
|
|
587
|
+
/// new_account: &Keypair,
|
|
588
|
+
/// owning_program: &Address,
|
|
589
|
+
/// space: u64,
|
|
590
|
+
/// ) -> Result<()> {
|
|
591
|
+
/// let rent = client.get_minimum_balance_for_rent_exemption(space.try_into()?)?;
|
|
592
|
+
///
|
|
593
|
+
/// let transfer_instr = instruction::transfer(
|
|
594
|
+
/// &payer.pubkey(),
|
|
595
|
+
/// &new_account.pubkey(),
|
|
596
|
+
/// rent,
|
|
597
|
+
/// );
|
|
598
|
+
///
|
|
599
|
+
/// let allocate_instr = instruction::allocate(
|
|
600
|
+
/// &new_account.pubkey(),
|
|
601
|
+
/// space,
|
|
602
|
+
/// );
|
|
603
|
+
///
|
|
604
|
+
/// let assign_instr = instruction::assign(
|
|
605
|
+
/// &new_account.pubkey(),
|
|
606
|
+
/// owning_program,
|
|
607
|
+
/// );
|
|
608
|
+
///
|
|
609
|
+
/// let blockhash = client.get_latest_blockhash()?;
|
|
610
|
+
/// let tx = Transaction::new_signed_with_payer(
|
|
611
|
+
/// &[transfer_instr, allocate_instr, assign_instr],
|
|
612
|
+
/// Some(&payer.pubkey()),
|
|
613
|
+
/// &[payer, new_account],
|
|
614
|
+
/// blockhash,
|
|
615
|
+
/// );
|
|
616
|
+
///
|
|
617
|
+
/// let _sig = client.send_and_confirm_transaction(&tx)?;
|
|
618
|
+
///
|
|
619
|
+
/// Ok(())
|
|
620
|
+
/// }
|
|
621
|
+
/// # let client = RpcClient::new(String::new());
|
|
622
|
+
/// # let payer = Keypair::new();
|
|
623
|
+
/// # let new_account = Keypair::new();
|
|
624
|
+
/// # let owning_program = Address::new_unique();
|
|
625
|
+
/// # create_account(&client, &payer, &new_account, &owning_program, 1);
|
|
626
|
+
/// #
|
|
627
|
+
/// # Ok::<(), anyhow::Error>(())
|
|
628
|
+
/// ```
|
|
629
|
+
///
|
|
630
|
+
/// ## Example: on-chain program
|
|
631
|
+
///
|
|
632
|
+
/// This example submits the instructions from an on-chain Solana program. The
|
|
633
|
+
/// created account is a [program derived address][pda], funded by `payer`, and
|
|
634
|
+
/// assigned to the running program. The `payer` and `new_account_pda` are
|
|
635
|
+
/// signers, with `new_account_pda` being signed for virtually by the program
|
|
636
|
+
/// itself via [`invoke_signed`], `payer` being signed for by the client that
|
|
637
|
+
/// submitted the transaction.
|
|
638
|
+
///
|
|
639
|
+
/// [pda]: https://docs.rs/solana-address/latest/solana_address/struct.Address.html#method.find_program_address
|
|
640
|
+
/// [`invoke_signed`]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke_signed.html
|
|
641
|
+
///
|
|
642
|
+
/// ```
|
|
643
|
+
/// use borsh::{BorshDeserialize, BorshSerialize};
|
|
644
|
+
/// use solana_account_info::{next_account_info, AccountInfo};
|
|
645
|
+
/// use solana_cpi::invoke_signed;
|
|
646
|
+
/// use solana_program_entrypoint::entrypoint;
|
|
647
|
+
/// use solana_program_error::ProgramResult;
|
|
648
|
+
/// use solana_address::Address;
|
|
649
|
+
/// use solana_system_interface::{instruction, program};
|
|
650
|
+
/// use solana_sysvar::{rent::Rent, Sysvar};
|
|
651
|
+
///
|
|
652
|
+
/// #[derive(BorshSerialize, BorshDeserialize, Debug)]
|
|
653
|
+
/// pub struct CreateAccountInstruction {
|
|
654
|
+
/// /// The PDA seed used to distinguish the new account from other PDAs
|
|
655
|
+
/// pub new_account_seed: [u8; 16],
|
|
656
|
+
/// /// The PDA bump seed
|
|
657
|
+
/// pub new_account_bump_seed: u8,
|
|
658
|
+
/// /// The amount of space to allocate for `new_account_pda`
|
|
659
|
+
/// pub space: u64,
|
|
660
|
+
/// }
|
|
661
|
+
///
|
|
662
|
+
/// entrypoint!(process_instruction);
|
|
663
|
+
///
|
|
664
|
+
/// fn process_instruction(
|
|
665
|
+
/// program_id: &Address,
|
|
666
|
+
/// accounts: &[AccountInfo],
|
|
667
|
+
/// instruction_data: &[u8],
|
|
668
|
+
/// ) -> ProgramResult {
|
|
669
|
+
/// let instr = CreateAccountInstruction::deserialize(&mut &instruction_data[..])?;
|
|
670
|
+
///
|
|
671
|
+
/// let account_info_iter = &mut accounts.iter();
|
|
672
|
+
///
|
|
673
|
+
/// let payer = next_account_info(account_info_iter)?;
|
|
674
|
+
/// let new_account_pda = next_account_info(account_info_iter)?;
|
|
675
|
+
/// let system_account = next_account_info(account_info_iter)?;
|
|
676
|
+
///
|
|
677
|
+
/// assert!(payer.is_signer);
|
|
678
|
+
/// assert!(payer.is_writable);
|
|
679
|
+
/// // Note that `new_account_pda` is not a signer yet.
|
|
680
|
+
/// // This program will sign for it via `invoke_signed`.
|
|
681
|
+
/// assert!(!new_account_pda.is_signer);
|
|
682
|
+
/// assert!(new_account_pda.is_writable);
|
|
683
|
+
/// assert!(program::check_id(system_account.key));
|
|
684
|
+
///
|
|
685
|
+
/// let new_account_seed = &instr.new_account_seed;
|
|
686
|
+
/// let new_account_bump_seed = instr.new_account_bump_seed;
|
|
687
|
+
///
|
|
688
|
+
/// let rent = Rent::get()?
|
|
689
|
+
/// .minimum_balance(instr.space.try_into().expect("overflow"));
|
|
690
|
+
///
|
|
691
|
+
/// invoke_signed(
|
|
692
|
+
/// &instruction::create_account(
|
|
693
|
+
/// payer.key,
|
|
694
|
+
/// new_account_pda.key,
|
|
695
|
+
/// rent,
|
|
696
|
+
/// instr.space,
|
|
697
|
+
/// &program::ID
|
|
698
|
+
/// ),
|
|
699
|
+
/// &[payer.clone(), new_account_pda.clone()],
|
|
700
|
+
/// &[&[
|
|
701
|
+
/// payer.key.as_ref(),
|
|
702
|
+
/// new_account_seed,
|
|
703
|
+
/// &[new_account_bump_seed],
|
|
704
|
+
/// ]],
|
|
705
|
+
/// )?;
|
|
706
|
+
///
|
|
707
|
+
/// Ok(())
|
|
708
|
+
/// }
|
|
709
|
+
/// ```
|
|
710
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
711
|
+
pub fn assign(address: &Address, owner: &Address) -> Instruction {
|
|
712
|
+
let account_metas = vec![AccountMeta::new(*address, true)];
|
|
713
|
+
create_instruction(
|
|
714
|
+
ID,
|
|
715
|
+
&SystemInstruction::Assign { owner: *owner },
|
|
716
|
+
account_metas,
|
|
717
|
+
)
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
721
|
+
pub fn assign_with_seed(
|
|
722
|
+
address: &Address, // must match create_with_seed(base, seed, owner)
|
|
723
|
+
base: &Address,
|
|
724
|
+
seed: &str,
|
|
725
|
+
owner: &Address,
|
|
726
|
+
) -> Instruction {
|
|
727
|
+
let account_metas = vec![
|
|
728
|
+
AccountMeta::new(*address, false),
|
|
729
|
+
AccountMeta::new_readonly(*base, true),
|
|
730
|
+
];
|
|
731
|
+
create_instruction(
|
|
732
|
+
ID,
|
|
733
|
+
&SystemInstruction::AssignWithSeed {
|
|
734
|
+
base: *base,
|
|
735
|
+
seed: seed.to_string(),
|
|
736
|
+
owner: *owner,
|
|
737
|
+
},
|
|
738
|
+
account_metas,
|
|
739
|
+
)
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/// Transfer lamports from an account owned by the system program.
|
|
743
|
+
///
|
|
744
|
+
/// This function produces an [`Instruction`] which must be submitted in a
|
|
745
|
+
/// [`Transaction`] or [invoked] to take effect, containing a serialized
|
|
746
|
+
/// [`SystemInstruction::Transfer`].
|
|
747
|
+
///
|
|
748
|
+
/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html
|
|
749
|
+
/// [invoked]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke.html
|
|
750
|
+
///
|
|
751
|
+
/// # Required signers
|
|
752
|
+
///
|
|
753
|
+
/// The `from_address` signer must sign the transaction.
|
|
754
|
+
///
|
|
755
|
+
/// # Examples
|
|
756
|
+
///
|
|
757
|
+
/// These examples allocate space for an account, transfer it the minimum
|
|
758
|
+
/// balance for rent exemption, and assign the account to a program.
|
|
759
|
+
///
|
|
760
|
+
/// # Example: client-side RPC
|
|
761
|
+
///
|
|
762
|
+
/// This example submits the instructions from an RPC client.
|
|
763
|
+
/// It assigns the account to a provided program account.
|
|
764
|
+
/// The `payer` and `new_account` are signers.
|
|
765
|
+
///
|
|
766
|
+
/// ```
|
|
767
|
+
/// # use solana_example_mocks::solana_keypair;
|
|
768
|
+
/// # use solana_example_mocks::solana_signer;
|
|
769
|
+
/// # use solana_example_mocks::solana_rpc_client;
|
|
770
|
+
/// # use solana_example_mocks::solana_transaction;
|
|
771
|
+
/// use solana_rpc_client::rpc_client::RpcClient;
|
|
772
|
+
/// use solana_address::Address;
|
|
773
|
+
/// use solana_keypair::Keypair;
|
|
774
|
+
/// use solana_signer::Signer;
|
|
775
|
+
/// use solana_system_interface::instruction;
|
|
776
|
+
/// use solana_transaction::Transaction;
|
|
777
|
+
/// use anyhow::Result;
|
|
778
|
+
///
|
|
779
|
+
/// fn create_account(
|
|
780
|
+
/// client: &RpcClient,
|
|
781
|
+
/// payer: &Keypair,
|
|
782
|
+
/// new_account: &Keypair,
|
|
783
|
+
/// owning_program: &Address,
|
|
784
|
+
/// space: u64,
|
|
785
|
+
/// ) -> Result<()> {
|
|
786
|
+
/// let rent = client.get_minimum_balance_for_rent_exemption(space.try_into()?)?;
|
|
787
|
+
///
|
|
788
|
+
/// let transfer_instr = instruction::transfer(
|
|
789
|
+
/// &payer.pubkey(),
|
|
790
|
+
/// &new_account.pubkey(),
|
|
791
|
+
/// rent,
|
|
792
|
+
/// );
|
|
793
|
+
///
|
|
794
|
+
/// let allocate_instr = instruction::allocate(
|
|
795
|
+
/// &new_account.pubkey(),
|
|
796
|
+
/// space,
|
|
797
|
+
/// );
|
|
798
|
+
///
|
|
799
|
+
/// let assign_instr = instruction::assign(
|
|
800
|
+
/// &new_account.pubkey(),
|
|
801
|
+
/// owning_program,
|
|
802
|
+
/// );
|
|
803
|
+
///
|
|
804
|
+
/// let blockhash = client.get_latest_blockhash()?;
|
|
805
|
+
/// let tx = Transaction::new_signed_with_payer(
|
|
806
|
+
/// &[transfer_instr, allocate_instr, assign_instr],
|
|
807
|
+
/// Some(&payer.pubkey()),
|
|
808
|
+
/// &[payer, new_account],
|
|
809
|
+
/// blockhash,
|
|
810
|
+
/// );
|
|
811
|
+
///
|
|
812
|
+
/// let _sig = client.send_and_confirm_transaction(&tx)?;
|
|
813
|
+
///
|
|
814
|
+
/// Ok(())
|
|
815
|
+
/// }
|
|
816
|
+
/// # let client = RpcClient::new(String::new());
|
|
817
|
+
/// # let payer = Keypair::new();
|
|
818
|
+
/// # let new_account = Keypair::new();
|
|
819
|
+
/// # let owning_program = Address::new_unique();
|
|
820
|
+
/// # create_account(&client, &payer, &new_account, &owning_program, 1);
|
|
821
|
+
/// #
|
|
822
|
+
/// # Ok::<(), anyhow::Error>(())
|
|
823
|
+
/// ```
|
|
824
|
+
///
|
|
825
|
+
/// ## Example: on-chain program
|
|
826
|
+
///
|
|
827
|
+
/// This example submits the instructions from an on-chain Solana program. The
|
|
828
|
+
/// created account is a [program derived address][pda], funded by `payer`, and
|
|
829
|
+
/// assigned to the running program. The `payer` and `new_account_pda` are
|
|
830
|
+
/// signers, with `new_account_pda` being signed for virtually by the program
|
|
831
|
+
/// itself via [`invoke_signed`], `payer` being signed for by the client that
|
|
832
|
+
/// submitted the transaction.
|
|
833
|
+
///
|
|
834
|
+
/// [pda]: https://docs.rs/solana-address/latest/solana_address/struct.Address.html#method.find_program_address
|
|
835
|
+
/// [`invoke_signed`]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke_signed.html
|
|
836
|
+
///
|
|
837
|
+
/// ```
|
|
838
|
+
/// # use borsh::{BorshDeserialize, BorshSerialize};
|
|
839
|
+
/// use solana_account_info::{next_account_info, AccountInfo};
|
|
840
|
+
/// use solana_cpi::invoke_signed;
|
|
841
|
+
/// use solana_program_entrypoint::entrypoint;
|
|
842
|
+
/// use solana_program_error::ProgramResult;
|
|
843
|
+
/// use solana_address::Address;
|
|
844
|
+
/// use solana_system_interface::{instruction, program};
|
|
845
|
+
/// use solana_sysvar::{rent::Rent, Sysvar};
|
|
846
|
+
///
|
|
847
|
+
/// #[derive(BorshSerialize, BorshDeserialize, Debug)]
|
|
848
|
+
/// # #[borsh(crate = "borsh")]
|
|
849
|
+
/// pub struct CreateAccountInstruction {
|
|
850
|
+
/// /// The PDA seed used to distinguish the new account from other PDAs
|
|
851
|
+
/// pub new_account_seed: [u8; 16],
|
|
852
|
+
/// /// The PDA bump seed
|
|
853
|
+
/// pub new_account_bump_seed: u8,
|
|
854
|
+
/// /// The amount of space to allocate for `new_account_pda`
|
|
855
|
+
/// pub space: u64,
|
|
856
|
+
/// }
|
|
857
|
+
///
|
|
858
|
+
/// entrypoint!(process_instruction);
|
|
859
|
+
///
|
|
860
|
+
/// fn process_instruction(
|
|
861
|
+
/// program_id: &Address,
|
|
862
|
+
/// accounts: &[AccountInfo],
|
|
863
|
+
/// instruction_data: &[u8],
|
|
864
|
+
/// ) -> ProgramResult {
|
|
865
|
+
/// let instr = CreateAccountInstruction::deserialize(&mut &instruction_data[..])?;
|
|
866
|
+
///
|
|
867
|
+
/// let account_info_iter = &mut accounts.iter();
|
|
868
|
+
///
|
|
869
|
+
/// let payer = next_account_info(account_info_iter)?;
|
|
870
|
+
/// let new_account_pda = next_account_info(account_info_iter)?;
|
|
871
|
+
/// let system_account = next_account_info(account_info_iter)?;
|
|
872
|
+
///
|
|
873
|
+
/// assert!(payer.is_signer);
|
|
874
|
+
/// assert!(payer.is_writable);
|
|
875
|
+
/// // Note that `new_account_pda` is not a signer yet.
|
|
876
|
+
/// // This program will sign for it via `invoke_signed`.
|
|
877
|
+
/// assert!(!new_account_pda.is_signer);
|
|
878
|
+
/// assert!(new_account_pda.is_writable);
|
|
879
|
+
/// assert!(program::check_id(system_account.key));
|
|
880
|
+
///
|
|
881
|
+
/// let new_account_seed = &instr.new_account_seed;
|
|
882
|
+
/// let new_account_bump_seed = instr.new_account_bump_seed;
|
|
883
|
+
///
|
|
884
|
+
/// let rent = Rent::get()?
|
|
885
|
+
/// .minimum_balance(instr.space.try_into().expect("overflow"));
|
|
886
|
+
///
|
|
887
|
+
/// invoke_signed(
|
|
888
|
+
/// &instruction::create_account(
|
|
889
|
+
/// payer.key,
|
|
890
|
+
/// new_account_pda.key,
|
|
891
|
+
/// rent,
|
|
892
|
+
/// instr.space,
|
|
893
|
+
/// &program::ID
|
|
894
|
+
/// ),
|
|
895
|
+
/// &[payer.clone(), new_account_pda.clone()],
|
|
896
|
+
/// &[&[
|
|
897
|
+
/// payer.key.as_ref(),
|
|
898
|
+
/// new_account_seed,
|
|
899
|
+
/// &[new_account_bump_seed],
|
|
900
|
+
/// ]],
|
|
901
|
+
/// )?;
|
|
902
|
+
///
|
|
903
|
+
/// Ok(())
|
|
904
|
+
/// }
|
|
905
|
+
/// ```
|
|
906
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
907
|
+
pub fn transfer(from_address: &Address, to_address: &Address, lamports: u64) -> Instruction {
|
|
908
|
+
let account_metas = vec![
|
|
909
|
+
AccountMeta::new(*from_address, true),
|
|
910
|
+
AccountMeta::new(*to_address, false),
|
|
911
|
+
];
|
|
912
|
+
create_instruction(ID, &SystemInstruction::Transfer { lamports }, account_metas)
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
916
|
+
pub fn transfer_with_seed(
|
|
917
|
+
from_address: &Address, // must match create_with_seed(base, seed, owner)
|
|
918
|
+
from_base: &Address,
|
|
919
|
+
from_seed: String,
|
|
920
|
+
from_owner: &Address,
|
|
921
|
+
to_address: &Address,
|
|
922
|
+
lamports: u64,
|
|
923
|
+
) -> Instruction {
|
|
924
|
+
let account_metas = vec![
|
|
925
|
+
AccountMeta::new(*from_address, false),
|
|
926
|
+
AccountMeta::new_readonly(*from_base, true),
|
|
927
|
+
AccountMeta::new(*to_address, false),
|
|
928
|
+
];
|
|
929
|
+
create_instruction(
|
|
930
|
+
ID,
|
|
931
|
+
&SystemInstruction::TransferWithSeed {
|
|
932
|
+
lamports,
|
|
933
|
+
from_seed,
|
|
934
|
+
from_owner: *from_owner,
|
|
935
|
+
},
|
|
936
|
+
account_metas,
|
|
937
|
+
)
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/// Allocate space for an account.
|
|
941
|
+
///
|
|
942
|
+
/// This function produces an [`Instruction`] which must be submitted in a
|
|
943
|
+
/// [`Transaction`] or [invoked] to take effect, containing a serialized
|
|
944
|
+
/// [`SystemInstruction::Allocate`].
|
|
945
|
+
///
|
|
946
|
+
/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html
|
|
947
|
+
/// [invoked]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke.html
|
|
948
|
+
///
|
|
949
|
+
/// The transaction will fail if the account already has size greater than 0,
|
|
950
|
+
/// or if the requested size is greater than [`super::MAX_PERMITTED_DATA_LENGTH`].
|
|
951
|
+
///
|
|
952
|
+
/// # Required signers
|
|
953
|
+
///
|
|
954
|
+
/// The `address` signer must sign the transaction.
|
|
955
|
+
///
|
|
956
|
+
/// # Examples
|
|
957
|
+
///
|
|
958
|
+
/// These examples allocate space for an account, transfer it the minimum
|
|
959
|
+
/// balance for rent exemption, and assign the account to a program.
|
|
960
|
+
///
|
|
961
|
+
/// # Example: client-side RPC
|
|
962
|
+
///
|
|
963
|
+
/// This example submits the instructions from an RPC client.
|
|
964
|
+
/// It assigns the account to a provided program account.
|
|
965
|
+
/// The `payer` and `new_account` are signers.
|
|
966
|
+
///
|
|
967
|
+
/// ```
|
|
968
|
+
/// # use solana_example_mocks::solana_keypair;
|
|
969
|
+
/// # use solana_example_mocks::solana_signer;
|
|
970
|
+
/// # use solana_example_mocks::solana_rpc_client;
|
|
971
|
+
/// # use solana_example_mocks::solana_transaction;
|
|
972
|
+
/// use solana_rpc_client::rpc_client::RpcClient;
|
|
973
|
+
/// use solana_address::Address;
|
|
974
|
+
/// use solana_keypair::Keypair;
|
|
975
|
+
/// use solana_signer::Signer;
|
|
976
|
+
/// use solana_system_interface::instruction;
|
|
977
|
+
/// use solana_transaction::Transaction;
|
|
978
|
+
/// use anyhow::Result;
|
|
979
|
+
///
|
|
980
|
+
/// fn create_account(
|
|
981
|
+
/// client: &RpcClient,
|
|
982
|
+
/// payer: &Keypair,
|
|
983
|
+
/// new_account: &Keypair,
|
|
984
|
+
/// owning_program: &Address,
|
|
985
|
+
/// space: u64,
|
|
986
|
+
/// ) -> Result<()> {
|
|
987
|
+
/// let rent = client.get_minimum_balance_for_rent_exemption(space.try_into()?)?;
|
|
988
|
+
///
|
|
989
|
+
/// let transfer_instr = instruction::transfer(
|
|
990
|
+
/// &payer.pubkey(),
|
|
991
|
+
/// &new_account.pubkey(),
|
|
992
|
+
/// rent,
|
|
993
|
+
/// );
|
|
994
|
+
///
|
|
995
|
+
/// let allocate_instr = instruction::allocate(
|
|
996
|
+
/// &new_account.pubkey(),
|
|
997
|
+
/// space,
|
|
998
|
+
/// );
|
|
999
|
+
///
|
|
1000
|
+
/// let assign_instr = instruction::assign(
|
|
1001
|
+
/// &new_account.pubkey(),
|
|
1002
|
+
/// owning_program,
|
|
1003
|
+
/// );
|
|
1004
|
+
///
|
|
1005
|
+
/// let blockhash = client.get_latest_blockhash()?;
|
|
1006
|
+
/// let tx = Transaction::new_signed_with_payer(
|
|
1007
|
+
/// &[transfer_instr, allocate_instr, assign_instr],
|
|
1008
|
+
/// Some(&payer.pubkey()),
|
|
1009
|
+
/// &[payer, new_account],
|
|
1010
|
+
/// blockhash,
|
|
1011
|
+
/// );
|
|
1012
|
+
///
|
|
1013
|
+
/// let _sig = client.send_and_confirm_transaction(&tx)?;
|
|
1014
|
+
///
|
|
1015
|
+
/// Ok(())
|
|
1016
|
+
/// }
|
|
1017
|
+
/// # let client = RpcClient::new(String::new());
|
|
1018
|
+
/// # let payer = Keypair::new();
|
|
1019
|
+
/// # let new_account = Keypair::new();
|
|
1020
|
+
/// # let owning_program = Address::new_unique();
|
|
1021
|
+
/// # create_account(&client, &payer, &new_account, &owning_program, 1);
|
|
1022
|
+
/// #
|
|
1023
|
+
/// # Ok::<(), anyhow::Error>(())
|
|
1024
|
+
/// ```
|
|
1025
|
+
///
|
|
1026
|
+
/// ## Example: on-chain program
|
|
1027
|
+
///
|
|
1028
|
+
/// This example submits the instructions from an on-chain Solana program. The
|
|
1029
|
+
/// created account is a [program derived address][pda], funded by `payer`, and
|
|
1030
|
+
/// assigned to the running program. The `payer` and `new_account_pda` are
|
|
1031
|
+
/// signers, with `new_account_pda` being signed for virtually by the program
|
|
1032
|
+
/// itself via [`invoke_signed`], `payer` being signed for by the client that
|
|
1033
|
+
/// submitted the transaction.
|
|
1034
|
+
///
|
|
1035
|
+
/// [pda]: https://docs.rs/solana-address/latest/solana_address/struct.Address.html#method.find_program_address
|
|
1036
|
+
/// [`invoke_signed`]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke_signed.html
|
|
1037
|
+
///
|
|
1038
|
+
/// ```
|
|
1039
|
+
/// use borsh::{BorshDeserialize, BorshSerialize};
|
|
1040
|
+
/// use solana_account_info::{next_account_info, AccountInfo};
|
|
1041
|
+
/// use solana_cpi::invoke_signed;
|
|
1042
|
+
/// use solana_program_entrypoint::entrypoint;
|
|
1043
|
+
/// use solana_program_error::ProgramResult;
|
|
1044
|
+
/// use solana_address::Address;
|
|
1045
|
+
/// use solana_system_interface::{instruction, program};
|
|
1046
|
+
/// use solana_sysvar::{rent::Rent, Sysvar};
|
|
1047
|
+
///
|
|
1048
|
+
/// #[derive(BorshSerialize, BorshDeserialize, Debug)]
|
|
1049
|
+
/// pub struct CreateAccountInstruction {
|
|
1050
|
+
/// /// The PDA seed used to distinguish the new account from other PDAs
|
|
1051
|
+
/// pub new_account_seed: [u8; 16],
|
|
1052
|
+
/// /// The PDA bump seed
|
|
1053
|
+
/// pub new_account_bump_seed: u8,
|
|
1054
|
+
/// /// The amount of space to allocate for `new_account_pda`
|
|
1055
|
+
/// pub space: u64,
|
|
1056
|
+
/// }
|
|
1057
|
+
///
|
|
1058
|
+
/// entrypoint!(process_instruction);
|
|
1059
|
+
///
|
|
1060
|
+
/// fn process_instruction(
|
|
1061
|
+
/// program_id: &Address,
|
|
1062
|
+
/// accounts: &[AccountInfo],
|
|
1063
|
+
/// instruction_data: &[u8],
|
|
1064
|
+
/// ) -> ProgramResult {
|
|
1065
|
+
/// let instr = CreateAccountInstruction::deserialize(&mut &instruction_data[..])?;
|
|
1066
|
+
///
|
|
1067
|
+
/// let account_info_iter = &mut accounts.iter();
|
|
1068
|
+
///
|
|
1069
|
+
/// let payer = next_account_info(account_info_iter)?;
|
|
1070
|
+
/// let new_account_pda = next_account_info(account_info_iter)?;
|
|
1071
|
+
/// let system_account = next_account_info(account_info_iter)?;
|
|
1072
|
+
///
|
|
1073
|
+
/// assert!(payer.is_signer);
|
|
1074
|
+
/// assert!(payer.is_writable);
|
|
1075
|
+
/// // Note that `new_account_pda` is not a signer yet.
|
|
1076
|
+
/// // This program will sign for it via `invoke_signed`.
|
|
1077
|
+
/// assert!(!new_account_pda.is_signer);
|
|
1078
|
+
/// assert!(new_account_pda.is_writable);
|
|
1079
|
+
/// assert!(program::check_id(system_account.key));
|
|
1080
|
+
///
|
|
1081
|
+
/// let new_account_seed = &instr.new_account_seed;
|
|
1082
|
+
/// let new_account_bump_seed = instr.new_account_bump_seed;
|
|
1083
|
+
///
|
|
1084
|
+
/// let rent = Rent::get()?
|
|
1085
|
+
/// .minimum_balance(instr.space.try_into().expect("overflow"));
|
|
1086
|
+
///
|
|
1087
|
+
/// invoke_signed(
|
|
1088
|
+
/// &instruction::create_account(
|
|
1089
|
+
/// payer.key,
|
|
1090
|
+
/// new_account_pda.key,
|
|
1091
|
+
/// rent,
|
|
1092
|
+
/// instr.space,
|
|
1093
|
+
/// &program::ID
|
|
1094
|
+
/// ),
|
|
1095
|
+
/// &[payer.clone(), new_account_pda.clone()],
|
|
1096
|
+
/// &[&[
|
|
1097
|
+
/// payer.key.as_ref(),
|
|
1098
|
+
/// new_account_seed,
|
|
1099
|
+
/// &[new_account_bump_seed],
|
|
1100
|
+
/// ]],
|
|
1101
|
+
/// )?;
|
|
1102
|
+
///
|
|
1103
|
+
/// Ok(())
|
|
1104
|
+
/// }
|
|
1105
|
+
/// ```
|
|
1106
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
1107
|
+
pub fn allocate(address: &Address, space: u64) -> Instruction {
|
|
1108
|
+
let account_metas = vec![AccountMeta::new(*address, true)];
|
|
1109
|
+
create_instruction(ID, &SystemInstruction::Allocate { space }, account_metas)
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
1113
|
+
pub fn allocate_with_seed(
|
|
1114
|
+
address: &Address, // must match create_with_seed(base, seed, owner)
|
|
1115
|
+
base: &Address,
|
|
1116
|
+
seed: &str,
|
|
1117
|
+
space: u64,
|
|
1118
|
+
owner: &Address,
|
|
1119
|
+
) -> Instruction {
|
|
1120
|
+
let account_metas = vec![
|
|
1121
|
+
AccountMeta::new(*address, false),
|
|
1122
|
+
AccountMeta::new_readonly(*base, true),
|
|
1123
|
+
];
|
|
1124
|
+
create_instruction(
|
|
1125
|
+
ID,
|
|
1126
|
+
&SystemInstruction::AllocateWithSeed {
|
|
1127
|
+
base: *base,
|
|
1128
|
+
seed: seed.to_string(),
|
|
1129
|
+
space,
|
|
1130
|
+
owner: *owner,
|
|
1131
|
+
},
|
|
1132
|
+
account_metas,
|
|
1133
|
+
)
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/// Transfer lamports from an account owned by the system program to multiple accounts.
|
|
1137
|
+
///
|
|
1138
|
+
/// This function produces a vector of [`Instruction`]s which must be submitted
|
|
1139
|
+
/// in a [`Transaction`] or [invoked] to take effect, containing serialized
|
|
1140
|
+
/// [`SystemInstruction::Transfer`]s.
|
|
1141
|
+
///
|
|
1142
|
+
/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html
|
|
1143
|
+
/// [invoked]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke.html
|
|
1144
|
+
///
|
|
1145
|
+
/// # Required signers
|
|
1146
|
+
///
|
|
1147
|
+
/// The `from_address` signer must sign the transaction.
|
|
1148
|
+
///
|
|
1149
|
+
/// # Examples
|
|
1150
|
+
///
|
|
1151
|
+
/// ## Example: client-side RPC
|
|
1152
|
+
///
|
|
1153
|
+
/// This example performs multiple transfers in a single transaction.
|
|
1154
|
+
///
|
|
1155
|
+
/// ```
|
|
1156
|
+
/// # use solana_example_mocks::solana_keypair;
|
|
1157
|
+
/// # use solana_example_mocks::solana_signer;
|
|
1158
|
+
/// # use solana_example_mocks::solana_rpc_client;
|
|
1159
|
+
/// # use solana_example_mocks::solana_transaction;
|
|
1160
|
+
/// use solana_rpc_client::rpc_client::RpcClient;
|
|
1161
|
+
/// use solana_address::Address;
|
|
1162
|
+
/// use solana_keypair::Keypair;
|
|
1163
|
+
/// use solana_signer::Signer;
|
|
1164
|
+
/// use solana_system_interface::instruction;
|
|
1165
|
+
/// use solana_transaction::Transaction;
|
|
1166
|
+
/// use anyhow::Result;
|
|
1167
|
+
///
|
|
1168
|
+
/// fn transfer_lamports_to_many(
|
|
1169
|
+
/// client: &RpcClient,
|
|
1170
|
+
/// from: &Keypair,
|
|
1171
|
+
/// to_and_amount: &[(Address, u64)],
|
|
1172
|
+
/// ) -> Result<()> {
|
|
1173
|
+
/// let instrs = instruction::transfer_many(&from.pubkey(), to_and_amount);
|
|
1174
|
+
///
|
|
1175
|
+
/// let blockhash = client.get_latest_blockhash()?;
|
|
1176
|
+
/// let tx = Transaction::new_signed_with_payer(
|
|
1177
|
+
/// &instrs,
|
|
1178
|
+
/// Some(&from.pubkey()),
|
|
1179
|
+
/// &[from],
|
|
1180
|
+
/// blockhash,
|
|
1181
|
+
/// );
|
|
1182
|
+
///
|
|
1183
|
+
/// let _sig = client.send_and_confirm_transaction(&tx)?;
|
|
1184
|
+
///
|
|
1185
|
+
/// Ok(())
|
|
1186
|
+
/// }
|
|
1187
|
+
/// # let from = Keypair::new();
|
|
1188
|
+
/// # let to_and_amount = vec![
|
|
1189
|
+
/// # (Address::new_unique(), 1_000),
|
|
1190
|
+
/// # (Address::new_unique(), 2_000),
|
|
1191
|
+
/// # (Address::new_unique(), 3_000),
|
|
1192
|
+
/// # ];
|
|
1193
|
+
/// # let client = RpcClient::new(String::new());
|
|
1194
|
+
/// # transfer_lamports_to_many(&client, &from, &to_and_amount);
|
|
1195
|
+
/// #
|
|
1196
|
+
/// # Ok::<(), anyhow::Error>(())
|
|
1197
|
+
/// ```
|
|
1198
|
+
///
|
|
1199
|
+
/// ## Example: on-chain program
|
|
1200
|
+
///
|
|
1201
|
+
/// This example makes multiple transfers out of a "bank" account,
|
|
1202
|
+
/// a [program derived address][pda] owned by the calling program.
|
|
1203
|
+
/// This example submits the instructions from an on-chain Solana program. The
|
|
1204
|
+
/// created account is a [program derived address][pda], and it is assigned to
|
|
1205
|
+
/// the running program. The `payer` and `new_account_pda` are signers, with
|
|
1206
|
+
/// `new_account_pda` being signed for virtually by the program itself via
|
|
1207
|
+
/// [`invoke_signed`], `payer` being signed for by the client that submitted the
|
|
1208
|
+
/// transaction.
|
|
1209
|
+
///
|
|
1210
|
+
/// [pda]: https://docs.rs/solana-address/latest/solana_address/struct.Address.html#method.find_program_address
|
|
1211
|
+
/// [`invoke_signed`]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke_signed.html
|
|
1212
|
+
///
|
|
1213
|
+
/// ```
|
|
1214
|
+
/// # use borsh::{BorshDeserialize, BorshSerialize};
|
|
1215
|
+
/// use solana_account_info::{next_account_info, next_account_infos, AccountInfo};
|
|
1216
|
+
/// use solana_cpi::invoke_signed;
|
|
1217
|
+
/// use solana_program_entrypoint::entrypoint;
|
|
1218
|
+
/// use solana_program_error::ProgramResult;
|
|
1219
|
+
/// use solana_address::Address;
|
|
1220
|
+
/// use solana_system_interface::{instruction, program};
|
|
1221
|
+
///
|
|
1222
|
+
/// /// # Accounts
|
|
1223
|
+
/// ///
|
|
1224
|
+
/// /// - 0: bank_pda - writable
|
|
1225
|
+
/// /// - 1: system_program - executable
|
|
1226
|
+
/// /// - *: to - writable
|
|
1227
|
+
/// # #[derive(BorshSerialize, BorshDeserialize, Debug)]
|
|
1228
|
+
/// # #[borsh(crate = "borsh")]
|
|
1229
|
+
/// pub struct TransferLamportsToManyInstruction {
|
|
1230
|
+
/// pub bank_pda_bump_seed: u8,
|
|
1231
|
+
/// pub amount_list: Vec<u64>,
|
|
1232
|
+
/// }
|
|
1233
|
+
///
|
|
1234
|
+
/// entrypoint!(process_instruction);
|
|
1235
|
+
///
|
|
1236
|
+
/// fn process_instruction(
|
|
1237
|
+
/// program_id: &Address,
|
|
1238
|
+
/// accounts: &[AccountInfo],
|
|
1239
|
+
/// instruction_data: &[u8],
|
|
1240
|
+
/// ) -> ProgramResult {
|
|
1241
|
+
/// let instr = TransferLamportsToManyInstruction::deserialize(&mut &instruction_data[..])?;
|
|
1242
|
+
///
|
|
1243
|
+
/// let account_info_iter = &mut accounts.iter();
|
|
1244
|
+
///
|
|
1245
|
+
/// let bank_pda = next_account_info(account_info_iter)?;
|
|
1246
|
+
/// let bank_pda_bump_seed = instr.bank_pda_bump_seed;
|
|
1247
|
+
/// let system_account = next_account_info(account_info_iter)?;
|
|
1248
|
+
///
|
|
1249
|
+
/// assert!(program::check_id(system_account.key));
|
|
1250
|
+
///
|
|
1251
|
+
/// let to_accounts = next_account_infos(account_info_iter, account_info_iter.len())?;
|
|
1252
|
+
///
|
|
1253
|
+
/// for to_account in to_accounts {
|
|
1254
|
+
/// assert!(to_account.is_writable);
|
|
1255
|
+
/// // ... do other verification ...
|
|
1256
|
+
/// }
|
|
1257
|
+
///
|
|
1258
|
+
/// let to_and_amount = to_accounts
|
|
1259
|
+
/// .iter()
|
|
1260
|
+
/// .zip(instr.amount_list.iter())
|
|
1261
|
+
/// .map(|(to, amount)| (*to.key, *amount))
|
|
1262
|
+
/// .collect::<Vec<(Address, u64)>>();
|
|
1263
|
+
///
|
|
1264
|
+
/// let instrs = instruction::transfer_many(bank_pda.key, to_and_amount.as_ref());
|
|
1265
|
+
///
|
|
1266
|
+
/// for instr in instrs {
|
|
1267
|
+
/// invoke_signed(&instr, accounts, &[&[b"bank", &[bank_pda_bump_seed]]])?;
|
|
1268
|
+
/// }
|
|
1269
|
+
///
|
|
1270
|
+
/// Ok(())
|
|
1271
|
+
/// }
|
|
1272
|
+
/// ```
|
|
1273
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
1274
|
+
pub fn transfer_many(from_address: &Address, to_lamports: &[(Address, u64)]) -> Vec<Instruction> {
|
|
1275
|
+
to_lamports
|
|
1276
|
+
.iter()
|
|
1277
|
+
.map(|(to_address, lamports)| transfer(from_address, to_address, *lamports))
|
|
1278
|
+
.collect()
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
1282
|
+
pub fn create_nonce_account_with_seed(
|
|
1283
|
+
from_address: &Address,
|
|
1284
|
+
nonce_address: &Address,
|
|
1285
|
+
base: &Address,
|
|
1286
|
+
seed: &str,
|
|
1287
|
+
authority: &Address,
|
|
1288
|
+
lamports: u64,
|
|
1289
|
+
) -> Vec<Instruction> {
|
|
1290
|
+
vec![
|
|
1291
|
+
create_account_with_seed(
|
|
1292
|
+
from_address,
|
|
1293
|
+
nonce_address,
|
|
1294
|
+
base,
|
|
1295
|
+
seed,
|
|
1296
|
+
lamports,
|
|
1297
|
+
NONCE_STATE_SIZE as u64,
|
|
1298
|
+
&ID,
|
|
1299
|
+
),
|
|
1300
|
+
create_instruction(
|
|
1301
|
+
ID,
|
|
1302
|
+
&SystemInstruction::InitializeNonceAccount(*authority),
|
|
1303
|
+
vec![
|
|
1304
|
+
AccountMeta::new(*nonce_address, false),
|
|
1305
|
+
#[allow(deprecated)]
|
|
1306
|
+
AccountMeta::new_readonly(RECENT_BLOCKHASHES_ID, false),
|
|
1307
|
+
AccountMeta::new_readonly(RENT_ID, false),
|
|
1308
|
+
],
|
|
1309
|
+
),
|
|
1310
|
+
]
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
/// Create an account containing a durable transaction nonce.
|
|
1314
|
+
///
|
|
1315
|
+
/// This function produces a vector of [`Instruction`]s which must be submitted
|
|
1316
|
+
/// in a [`Transaction`] or [invoked] to take effect, containing a serialized
|
|
1317
|
+
/// [`SystemInstruction::CreateAccount`] and
|
|
1318
|
+
/// [`SystemInstruction::InitializeNonceAccount`].
|
|
1319
|
+
///
|
|
1320
|
+
/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html
|
|
1321
|
+
/// [invoked]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke.html
|
|
1322
|
+
///
|
|
1323
|
+
/// A [durable transaction nonce][dtn] is a special account that enables
|
|
1324
|
+
/// execution of transactions that have been signed in the past.
|
|
1325
|
+
///
|
|
1326
|
+
/// Standard Solana transactions include a [recent blockhash][rbh] (sometimes
|
|
1327
|
+
/// referred to as a _[nonce]_). During execution the Solana runtime verifies
|
|
1328
|
+
/// the recent blockhash is approximately less than two minutes old, and that in
|
|
1329
|
+
/// those two minutes no other identical transaction with the same blockhash has
|
|
1330
|
+
/// been executed. These checks prevent accidental replay of transactions.
|
|
1331
|
+
/// Consequently, it is not possible to sign a transaction, wait more than two
|
|
1332
|
+
/// minutes, then successfully execute that transaction.
|
|
1333
|
+
///
|
|
1334
|
+
/// [dtn]: https://docs.solanalabs.com/implemented-proposals/durable-tx-nonces
|
|
1335
|
+
/// [rbh]: https://docs.rs/solana-program/latest/solana_program/message/legacy/struct.Message.html#structfield.recent_blockhash
|
|
1336
|
+
/// [nonce]: https://en.wikipedia.org/wiki/Cryptographic_nonce
|
|
1337
|
+
///
|
|
1338
|
+
/// Durable transaction nonces are an alternative to the standard recent
|
|
1339
|
+
/// blockhash nonce. They are stored in accounts on chain, and every time they
|
|
1340
|
+
/// are used their value is changed to a new value for their next use. The
|
|
1341
|
+
/// runtime verifies that each durable nonce value is only used once, and there
|
|
1342
|
+
/// are no restrictions on how "old" the nonce is. Because they are stored on
|
|
1343
|
+
/// chain and require additional instructions to use, transacting with durable
|
|
1344
|
+
/// transaction nonces is more expensive than with standard transactions.
|
|
1345
|
+
///
|
|
1346
|
+
/// The value of the durable nonce is itself a blockhash and is accessible via
|
|
1347
|
+
/// the [`blockhash`] field of [`nonce::state::Data`], which is deserialized
|
|
1348
|
+
/// from the nonce account data.
|
|
1349
|
+
///
|
|
1350
|
+
/// [`blockhash`]: https://docs.rs/solana-program/latest/solana_program/message/legacy/struct.Message.html#structfield.recent_blockhash
|
|
1351
|
+
/// [`nonce::state::Data`]: https://docs.rs/solana-nonce/latest/solana_nonce/state/struct.Data.html
|
|
1352
|
+
///
|
|
1353
|
+
/// The basic durable transaction nonce lifecycle is
|
|
1354
|
+
///
|
|
1355
|
+
/// 1) Create the nonce account with the `create_nonce_account` instruction.
|
|
1356
|
+
/// 2) Submit specially-formed transactions that include the
|
|
1357
|
+
/// [`advance_nonce_account`] instruction.
|
|
1358
|
+
/// 3) Destroy the nonce account by withdrawing its lamports with the
|
|
1359
|
+
/// [`withdraw_nonce_account`] instruction.
|
|
1360
|
+
///
|
|
1361
|
+
/// Nonce accounts have an associated _authority_ account, which is stored in
|
|
1362
|
+
/// their account data, and can be changed with the [`authorize_nonce_account`]
|
|
1363
|
+
/// instruction. The authority must sign transactions that include the
|
|
1364
|
+
/// `advance_nonce_account`, `authorize_nonce_account` and
|
|
1365
|
+
/// `withdraw_nonce_account` instructions.
|
|
1366
|
+
///
|
|
1367
|
+
/// Nonce accounts are owned by the system program.
|
|
1368
|
+
///
|
|
1369
|
+
/// This constructor creates a [`SystemInstruction::CreateAccount`] instruction
|
|
1370
|
+
/// and a [`SystemInstruction::InitializeNonceAccount`] instruction.
|
|
1371
|
+
///
|
|
1372
|
+
/// # Required signers
|
|
1373
|
+
///
|
|
1374
|
+
/// The `from_address` and `nonce_address` signers must sign the transaction.
|
|
1375
|
+
///
|
|
1376
|
+
/// # Examples
|
|
1377
|
+
///
|
|
1378
|
+
/// Create a nonce account from an off-chain client:
|
|
1379
|
+
///
|
|
1380
|
+
/// ```
|
|
1381
|
+
/// # use solana_example_mocks::solana_keypair;
|
|
1382
|
+
/// # use solana_example_mocks::solana_signer;
|
|
1383
|
+
/// # use solana_example_mocks::solana_rpc_client;
|
|
1384
|
+
/// # use solana_example_mocks::solana_transaction;
|
|
1385
|
+
/// use solana_keypair::Keypair;
|
|
1386
|
+
/// use solana_nonce::state::State;
|
|
1387
|
+
/// use solana_rpc_client::rpc_client::RpcClient;
|
|
1388
|
+
/// use solana_signer::Signer;
|
|
1389
|
+
/// use solana_system_interface::instruction;
|
|
1390
|
+
/// use solana_transaction::Transaction;
|
|
1391
|
+
/// use anyhow::Result;
|
|
1392
|
+
///
|
|
1393
|
+
/// fn submit_create_nonce_account_tx(
|
|
1394
|
+
/// client: &RpcClient,
|
|
1395
|
+
/// payer: &Keypair,
|
|
1396
|
+
/// ) -> Result<()> {
|
|
1397
|
+
///
|
|
1398
|
+
/// let nonce_account = Keypair::new();
|
|
1399
|
+
///
|
|
1400
|
+
/// let nonce_rent = client.get_minimum_balance_for_rent_exemption(State::size())?;
|
|
1401
|
+
/// let instr = instruction::create_nonce_account(
|
|
1402
|
+
/// &payer.pubkey(),
|
|
1403
|
+
/// &nonce_account.pubkey(),
|
|
1404
|
+
/// &payer.pubkey(), // Make the fee payer the nonce account authority
|
|
1405
|
+
/// nonce_rent,
|
|
1406
|
+
/// );
|
|
1407
|
+
///
|
|
1408
|
+
/// let mut tx = Transaction::new_with_payer(&instr, Some(&payer.pubkey()));
|
|
1409
|
+
///
|
|
1410
|
+
/// let blockhash = client.get_latest_blockhash()?;
|
|
1411
|
+
/// tx.try_sign(&[&nonce_account, payer], blockhash)?;
|
|
1412
|
+
///
|
|
1413
|
+
/// client.send_and_confirm_transaction(&tx)?;
|
|
1414
|
+
///
|
|
1415
|
+
/// Ok(())
|
|
1416
|
+
/// }
|
|
1417
|
+
/// #
|
|
1418
|
+
/// # let client = RpcClient::new(String::new());
|
|
1419
|
+
/// # let payer = Keypair::new();
|
|
1420
|
+
/// # submit_create_nonce_account_tx(&client, &payer)?;
|
|
1421
|
+
/// #
|
|
1422
|
+
/// # Ok::<(), anyhow::Error>(())
|
|
1423
|
+
/// ```
|
|
1424
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
1425
|
+
pub fn create_nonce_account(
|
|
1426
|
+
from_address: &Address,
|
|
1427
|
+
nonce_address: &Address,
|
|
1428
|
+
authority: &Address,
|
|
1429
|
+
lamports: u64,
|
|
1430
|
+
) -> Vec<Instruction> {
|
|
1431
|
+
vec![
|
|
1432
|
+
create_account(
|
|
1433
|
+
from_address,
|
|
1434
|
+
nonce_address,
|
|
1435
|
+
lamports,
|
|
1436
|
+
NONCE_STATE_SIZE as u64,
|
|
1437
|
+
&ID,
|
|
1438
|
+
),
|
|
1439
|
+
create_instruction(
|
|
1440
|
+
ID,
|
|
1441
|
+
&SystemInstruction::InitializeNonceAccount(*authority),
|
|
1442
|
+
vec![
|
|
1443
|
+
AccountMeta::new(*nonce_address, false),
|
|
1444
|
+
#[allow(deprecated)]
|
|
1445
|
+
AccountMeta::new_readonly(RECENT_BLOCKHASHES_ID, false),
|
|
1446
|
+
AccountMeta::new_readonly(RENT_ID, false),
|
|
1447
|
+
],
|
|
1448
|
+
),
|
|
1449
|
+
]
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
/// Advance the value of a durable transaction nonce.
|
|
1453
|
+
///
|
|
1454
|
+
/// This function produces an [`Instruction`] which must be submitted in a
|
|
1455
|
+
/// [`Transaction`] or [invoked] to take effect, containing a serialized
|
|
1456
|
+
/// [`SystemInstruction::AdvanceNonceAccount`].
|
|
1457
|
+
///
|
|
1458
|
+
/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html
|
|
1459
|
+
/// [invoked]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke.html
|
|
1460
|
+
///
|
|
1461
|
+
/// Every transaction that relies on a durable transaction nonce must contain a
|
|
1462
|
+
/// [`SystemInstruction::AdvanceNonceAccount`] instruction as the first
|
|
1463
|
+
/// instruction in the [`Message`], as created by this function. When included
|
|
1464
|
+
/// in the first position, the Solana runtime recognizes the transaction as one
|
|
1465
|
+
/// that relies on a durable transaction nonce and processes it accordingly. The
|
|
1466
|
+
/// [`Message::new_with_nonce`] function can be used to construct a `Message` in
|
|
1467
|
+
/// the correct format without calling `advance_nonce_account` directly.
|
|
1468
|
+
///
|
|
1469
|
+
/// When constructing a transaction that includes an `AdvanceNonceInstruction`
|
|
1470
|
+
/// the [`recent_blockhash`] must be treated differently — instead of
|
|
1471
|
+
/// setting it to a recent blockhash, the value of the nonce must be retrieved
|
|
1472
|
+
/// and deserialized from the nonce account, and that value specified as the
|
|
1473
|
+
/// "recent blockhash". A nonce account can be deserialized with the
|
|
1474
|
+
/// [`solana_rpc_client_nonce_utils::data_from_account`][dfa] function.
|
|
1475
|
+
///
|
|
1476
|
+
/// For further description of durable transaction nonces see
|
|
1477
|
+
/// [`create_nonce_account`].
|
|
1478
|
+
///
|
|
1479
|
+
/// [`Message`]: https://docs.rs/solana-program/latest/solana_program/message/legacy/struct.Message.html
|
|
1480
|
+
/// [`Message::new_with_nonce`]: https://docs.rs/solana-program/latest/solana_program/message/legacy/struct.Message.html#method.new_with_nonce
|
|
1481
|
+
/// [`recent_blockhash`]: https://docs.rs/solana-program/latest/solana_program/message/legacy/struct.Message.html#structfield.recent_blockhash
|
|
1482
|
+
/// [dfa]: https://docs.rs/solana-rpc-client-nonce-utils/latest/solana_rpc_client_nonce_utils/fn.data_from_account.html
|
|
1483
|
+
///
|
|
1484
|
+
/// # Required signers
|
|
1485
|
+
///
|
|
1486
|
+
/// The `authorized_address` signer must sign the transaction.
|
|
1487
|
+
///
|
|
1488
|
+
/// # Examples
|
|
1489
|
+
///
|
|
1490
|
+
/// Create and sign a transaction with a durable nonce:
|
|
1491
|
+
///
|
|
1492
|
+
/// ```
|
|
1493
|
+
/// # use solana_example_mocks::solana_keypair;
|
|
1494
|
+
/// # use solana_example_mocks::solana_signer;
|
|
1495
|
+
/// # use solana_example_mocks::solana_rpc_client;
|
|
1496
|
+
/// # use solana_example_mocks::solana_rpc_client_nonce_utils;
|
|
1497
|
+
/// # use solana_example_mocks::solana_transaction;
|
|
1498
|
+
/// # use solana_example_mocks::solana_account::Account;
|
|
1499
|
+
/// use solana_rpc_client::rpc_client::RpcClient;
|
|
1500
|
+
/// use solana_address::Address;
|
|
1501
|
+
/// use solana_keypair::Keypair;
|
|
1502
|
+
/// use solana_signer::Signer;
|
|
1503
|
+
/// use solana_system_interface::instruction;
|
|
1504
|
+
/// use solana_transaction::Transaction;
|
|
1505
|
+
/// use std::path::Path;
|
|
1506
|
+
/// use anyhow::Result;
|
|
1507
|
+
///
|
|
1508
|
+
/// fn create_transfer_tx_with_nonce(
|
|
1509
|
+
/// client: &RpcClient,
|
|
1510
|
+
/// nonce_account_address: &Address,
|
|
1511
|
+
/// payer: &Keypair,
|
|
1512
|
+
/// receiver: &Address,
|
|
1513
|
+
/// amount: u64,
|
|
1514
|
+
/// tx_path: &Path,
|
|
1515
|
+
/// ) -> Result<()> {
|
|
1516
|
+
///
|
|
1517
|
+
/// let instr_transfer = instruction::transfer(
|
|
1518
|
+
/// &payer.pubkey(),
|
|
1519
|
+
/// receiver,
|
|
1520
|
+
/// amount,
|
|
1521
|
+
/// );
|
|
1522
|
+
///
|
|
1523
|
+
/// // In this example, `payer` is `nonce_account_address`'s authority
|
|
1524
|
+
/// let instr_advance_nonce_account = instruction::advance_nonce_account(
|
|
1525
|
+
/// nonce_account_address,
|
|
1526
|
+
/// &payer.pubkey(),
|
|
1527
|
+
/// );
|
|
1528
|
+
///
|
|
1529
|
+
/// // The `advance_nonce_account` instruction must be the first issued in
|
|
1530
|
+
/// // the transaction.
|
|
1531
|
+
/// let mut tx = Transaction::new_with_payer(
|
|
1532
|
+
/// &[
|
|
1533
|
+
/// instr_advance_nonce_account,
|
|
1534
|
+
/// instr_transfer
|
|
1535
|
+
/// ],
|
|
1536
|
+
/// Some(&payer.pubkey()),
|
|
1537
|
+
/// );
|
|
1538
|
+
///
|
|
1539
|
+
/// // Sign the tx with nonce_account's `blockhash` instead of the
|
|
1540
|
+
/// // network's latest blockhash.
|
|
1541
|
+
/// # client.set_get_account_response(*nonce_account_address, Account {
|
|
1542
|
+
/// # lamports: 1,
|
|
1543
|
+
/// # data: vec![0],
|
|
1544
|
+
/// # owner: solana_system_interface::program::ID,
|
|
1545
|
+
/// # executable: false,
|
|
1546
|
+
/// # });
|
|
1547
|
+
/// let nonce_account = client.get_account(nonce_account_address)?;
|
|
1548
|
+
/// let nonce_data = solana_rpc_client_nonce_utils::data_from_account(&nonce_account)?;
|
|
1549
|
+
/// let blockhash = nonce_data.blockhash();
|
|
1550
|
+
///
|
|
1551
|
+
/// tx.try_sign(&[payer], blockhash)?;
|
|
1552
|
+
///
|
|
1553
|
+
/// // Save the signed transaction locally for later submission.
|
|
1554
|
+
/// save_tx_to_file(&tx_path, &tx)?;
|
|
1555
|
+
///
|
|
1556
|
+
/// Ok(())
|
|
1557
|
+
/// }
|
|
1558
|
+
/// #
|
|
1559
|
+
/// # fn save_tx_to_file(path: &Path, tx: &Transaction) -> Result<()> {
|
|
1560
|
+
/// # Ok(())
|
|
1561
|
+
/// # }
|
|
1562
|
+
/// #
|
|
1563
|
+
/// # let client = RpcClient::new(String::new());
|
|
1564
|
+
/// # let nonce_account_address = Address::new_unique();
|
|
1565
|
+
/// # let payer = Keypair::new();
|
|
1566
|
+
/// # let receiver = Address::new_unique();
|
|
1567
|
+
/// # create_transfer_tx_with_nonce(&client, &nonce_account_address, &payer, &receiver, 1024, Path::new("new_tx"))?;
|
|
1568
|
+
/// #
|
|
1569
|
+
/// # Ok::<(), anyhow::Error>(())
|
|
1570
|
+
/// ```
|
|
1571
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
1572
|
+
pub fn advance_nonce_account(nonce_address: &Address, authorized_address: &Address) -> Instruction {
|
|
1573
|
+
let account_metas = vec![
|
|
1574
|
+
AccountMeta::new(*nonce_address, false),
|
|
1575
|
+
#[allow(deprecated)]
|
|
1576
|
+
AccountMeta::new_readonly(RECENT_BLOCKHASHES_ID, false),
|
|
1577
|
+
AccountMeta::new_readonly(*authorized_address, true),
|
|
1578
|
+
];
|
|
1579
|
+
create_instruction(ID, &SystemInstruction::AdvanceNonceAccount, account_metas)
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
/// Withdraw lamports from a durable transaction nonce account.
|
|
1583
|
+
///
|
|
1584
|
+
/// This function produces an [`Instruction`] which must be submitted in a
|
|
1585
|
+
/// [`Transaction`] or [invoked] to take effect, containing a serialized
|
|
1586
|
+
/// [`SystemInstruction::WithdrawNonceAccount`].
|
|
1587
|
+
///
|
|
1588
|
+
/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html
|
|
1589
|
+
/// [invoked]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke.html
|
|
1590
|
+
///
|
|
1591
|
+
/// Withdrawing the entire balance of a nonce account will cause the runtime to
|
|
1592
|
+
/// destroy it upon successful completion of the transaction.
|
|
1593
|
+
///
|
|
1594
|
+
/// Otherwise, nonce accounts must maintain a balance greater than or equal to
|
|
1595
|
+
/// the minimum required for [rent exemption]. If the result of this instruction
|
|
1596
|
+
/// would leave the nonce account with a balance less than required for rent
|
|
1597
|
+
/// exemption, but also greater than zero, then the transaction will fail.
|
|
1598
|
+
///
|
|
1599
|
+
/// [rent exemption]: https://solana.com/docs/core/accounts#rent-exemption
|
|
1600
|
+
///
|
|
1601
|
+
/// This constructor creates a [`SystemInstruction::WithdrawNonceAccount`]
|
|
1602
|
+
/// instruction.
|
|
1603
|
+
///
|
|
1604
|
+
/// # Required signers
|
|
1605
|
+
///
|
|
1606
|
+
/// The `authorized_address` signer must sign the transaction.
|
|
1607
|
+
///
|
|
1608
|
+
/// # Examples
|
|
1609
|
+
///
|
|
1610
|
+
/// ```
|
|
1611
|
+
/// # use solana_example_mocks::solana_account;
|
|
1612
|
+
/// # use solana_example_mocks::solana_keypair;
|
|
1613
|
+
/// # use solana_example_mocks::solana_signer;
|
|
1614
|
+
/// # use solana_example_mocks::solana_rpc_client;
|
|
1615
|
+
/// # use solana_example_mocks::solana_transaction;
|
|
1616
|
+
/// # use solana_account::Account;
|
|
1617
|
+
/// use solana_rpc_client::rpc_client::RpcClient;
|
|
1618
|
+
/// use solana_address::Address;
|
|
1619
|
+
/// use solana_keypair::Keypair;
|
|
1620
|
+
/// use solana_signer::Signer;
|
|
1621
|
+
/// use solana_system_interface::instruction;
|
|
1622
|
+
/// use solana_transaction::Transaction;
|
|
1623
|
+
/// use anyhow::Result;
|
|
1624
|
+
///
|
|
1625
|
+
/// fn submit_withdraw_nonce_account_tx(
|
|
1626
|
+
/// client: &RpcClient,
|
|
1627
|
+
/// nonce_account_address: &Address,
|
|
1628
|
+
/// authorized_account: &Keypair,
|
|
1629
|
+
/// ) -> Result<()> {
|
|
1630
|
+
///
|
|
1631
|
+
/// let nonce_balance = client.get_balance(nonce_account_address)?;
|
|
1632
|
+
///
|
|
1633
|
+
/// let instr = instruction::withdraw_nonce_account(
|
|
1634
|
+
/// nonce_account_address,
|
|
1635
|
+
/// &authorized_account.pubkey(),
|
|
1636
|
+
/// &authorized_account.pubkey(),
|
|
1637
|
+
/// nonce_balance,
|
|
1638
|
+
/// );
|
|
1639
|
+
///
|
|
1640
|
+
/// let mut tx = Transaction::new_with_payer(&[instr], Some(&authorized_account.pubkey()));
|
|
1641
|
+
///
|
|
1642
|
+
/// let blockhash = client.get_latest_blockhash()?;
|
|
1643
|
+
/// tx.try_sign(&[authorized_account], blockhash)?;
|
|
1644
|
+
///
|
|
1645
|
+
/// client.send_and_confirm_transaction(&tx)?;
|
|
1646
|
+
///
|
|
1647
|
+
/// Ok(())
|
|
1648
|
+
/// }
|
|
1649
|
+
/// #
|
|
1650
|
+
/// # let client = RpcClient::new(String::new());
|
|
1651
|
+
/// # let nonce_account_address = Address::new_unique();
|
|
1652
|
+
/// # let payer = Keypair::new();
|
|
1653
|
+
/// # submit_withdraw_nonce_account_tx(&client, &nonce_account_address, &payer)?;
|
|
1654
|
+
/// #
|
|
1655
|
+
/// # Ok::<(), anyhow::Error>(())
|
|
1656
|
+
/// ```
|
|
1657
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
1658
|
+
pub fn withdraw_nonce_account(
|
|
1659
|
+
nonce_address: &Address,
|
|
1660
|
+
authorized_address: &Address,
|
|
1661
|
+
to_address: &Address,
|
|
1662
|
+
lamports: u64,
|
|
1663
|
+
) -> Instruction {
|
|
1664
|
+
let account_metas = vec![
|
|
1665
|
+
AccountMeta::new(*nonce_address, false),
|
|
1666
|
+
AccountMeta::new(*to_address, false),
|
|
1667
|
+
#[allow(deprecated)]
|
|
1668
|
+
AccountMeta::new_readonly(RECENT_BLOCKHASHES_ID, false),
|
|
1669
|
+
AccountMeta::new_readonly(RENT_ID, false),
|
|
1670
|
+
AccountMeta::new_readonly(*authorized_address, true),
|
|
1671
|
+
];
|
|
1672
|
+
create_instruction(
|
|
1673
|
+
ID,
|
|
1674
|
+
&SystemInstruction::WithdrawNonceAccount(lamports),
|
|
1675
|
+
account_metas,
|
|
1676
|
+
)
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
/// Change the authority of a durable transaction nonce account.
|
|
1680
|
+
///
|
|
1681
|
+
/// This function produces an [`Instruction`] which must be submitted in a
|
|
1682
|
+
/// [`Transaction`] or [invoked] to take effect, containing a serialized
|
|
1683
|
+
/// [`SystemInstruction::AuthorizeNonceAccount`].
|
|
1684
|
+
///
|
|
1685
|
+
/// [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html
|
|
1686
|
+
/// [invoked]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke.html
|
|
1687
|
+
///
|
|
1688
|
+
/// This constructor creates a [`SystemInstruction::AuthorizeNonceAccount`]
|
|
1689
|
+
/// instruction.
|
|
1690
|
+
///
|
|
1691
|
+
/// # Required signers
|
|
1692
|
+
///
|
|
1693
|
+
/// The `authorized_address` signer must sign the transaction.
|
|
1694
|
+
///
|
|
1695
|
+
/// # Examples
|
|
1696
|
+
///
|
|
1697
|
+
/// ```
|
|
1698
|
+
/// # use solana_example_mocks::solana_keypair;
|
|
1699
|
+
/// # use solana_example_mocks::solana_signer;
|
|
1700
|
+
/// # use solana_example_mocks::solana_rpc_client;
|
|
1701
|
+
/// # use solana_example_mocks::solana_transaction;
|
|
1702
|
+
/// use solana_rpc_client::rpc_client::RpcClient;
|
|
1703
|
+
/// use solana_address::Address;
|
|
1704
|
+
/// use solana_keypair::Keypair;
|
|
1705
|
+
/// use solana_signer::Signer;
|
|
1706
|
+
/// use solana_system_interface::instruction;
|
|
1707
|
+
/// use solana_transaction::Transaction;
|
|
1708
|
+
/// use anyhow::Result;
|
|
1709
|
+
///
|
|
1710
|
+
/// fn authorize_nonce_account_tx(
|
|
1711
|
+
/// client: &RpcClient,
|
|
1712
|
+
/// nonce_account_address: &Address,
|
|
1713
|
+
/// authorized_account: &Keypair,
|
|
1714
|
+
/// new_authority_address: &Address,
|
|
1715
|
+
/// ) -> Result<()> {
|
|
1716
|
+
///
|
|
1717
|
+
/// let instr = instruction::authorize_nonce_account(
|
|
1718
|
+
/// nonce_account_address,
|
|
1719
|
+
/// &authorized_account.pubkey(),
|
|
1720
|
+
/// new_authority_address,
|
|
1721
|
+
/// );
|
|
1722
|
+
///
|
|
1723
|
+
/// let mut tx = Transaction::new_with_payer(&[instr], Some(&authorized_account.pubkey()));
|
|
1724
|
+
///
|
|
1725
|
+
/// let blockhash = client.get_latest_blockhash()?;
|
|
1726
|
+
/// tx.try_sign(&[authorized_account], blockhash)?;
|
|
1727
|
+
///
|
|
1728
|
+
/// client.send_and_confirm_transaction(&tx)?;
|
|
1729
|
+
///
|
|
1730
|
+
/// Ok(())
|
|
1731
|
+
/// }
|
|
1732
|
+
/// #
|
|
1733
|
+
/// # let client = RpcClient::new(String::new());
|
|
1734
|
+
/// # let nonce_account_address = Address::new_unique();
|
|
1735
|
+
/// # let payer = Keypair::new();
|
|
1736
|
+
/// # let new_authority_address = Address::new_unique();
|
|
1737
|
+
/// # authorize_nonce_account_tx(&client, &nonce_account_address, &payer, &new_authority_address)?;
|
|
1738
|
+
/// #
|
|
1739
|
+
/// # Ok::<(), anyhow::Error>(())
|
|
1740
|
+
/// ```
|
|
1741
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
1742
|
+
pub fn authorize_nonce_account(
|
|
1743
|
+
nonce_address: &Address,
|
|
1744
|
+
authorized_address: &Address,
|
|
1745
|
+
new_authority: &Address,
|
|
1746
|
+
) -> Instruction {
|
|
1747
|
+
let account_metas = vec![
|
|
1748
|
+
AccountMeta::new(*nonce_address, false),
|
|
1749
|
+
AccountMeta::new_readonly(*authorized_address, true),
|
|
1750
|
+
];
|
|
1751
|
+
create_instruction(
|
|
1752
|
+
ID,
|
|
1753
|
+
&SystemInstruction::AuthorizeNonceAccount(*new_authority),
|
|
1754
|
+
account_metas,
|
|
1755
|
+
)
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
/// One-time idempotent upgrade of legacy nonce versions in order to bump
|
|
1759
|
+
/// them out of chain blockhash domain.
|
|
1760
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
1761
|
+
pub fn upgrade_nonce_account(nonce_address: Address) -> Instruction {
|
|
1762
|
+
let account_metas = vec![AccountMeta::new(nonce_address, /*is_signer:*/ false)];
|
|
1763
|
+
create_instruction(ID, &SystemInstruction::UpgradeNonceAccount, account_metas)
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
/// Create a new account without enforcing zero lamports on the destination
|
|
1767
|
+
/// account.
|
|
1768
|
+
///
|
|
1769
|
+
/// # Required signers
|
|
1770
|
+
///
|
|
1771
|
+
/// The `new_account_address` signer must sign the transaction. If present,
|
|
1772
|
+
/// the payer in `payer_and_lamports` must also sign the transaction.
|
|
1773
|
+
#[cfg(any(feature = "bincode", feature = "wincode"))]
|
|
1774
|
+
pub fn create_account_allow_prefund(
|
|
1775
|
+
new_account_address: &Address,
|
|
1776
|
+
payer_and_lamports: Option<(&Address, u64)>,
|
|
1777
|
+
space: u64,
|
|
1778
|
+
owner: &Address,
|
|
1779
|
+
) -> Instruction {
|
|
1780
|
+
let mut account_metas = vec![AccountMeta::new(*new_account_address, true)];
|
|
1781
|
+
let lamports = match payer_and_lamports {
|
|
1782
|
+
None => 0,
|
|
1783
|
+
Some((from, lamports)) => {
|
|
1784
|
+
account_metas.push(AccountMeta::new(*from, true));
|
|
1785
|
+
lamports
|
|
1786
|
+
}
|
|
1787
|
+
};
|
|
1788
|
+
|
|
1789
|
+
create_instruction(
|
|
1790
|
+
ID,
|
|
1791
|
+
&SystemInstruction::CreateAccountAllowPrefund {
|
|
1792
|
+
lamports,
|
|
1793
|
+
space,
|
|
1794
|
+
owner: *owner,
|
|
1795
|
+
},
|
|
1796
|
+
account_metas,
|
|
1797
|
+
)
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
#[cfg(feature = "bincode")]
|
|
1801
|
+
#[cfg(test)]
|
|
1802
|
+
mod tests {
|
|
1803
|
+
use {super::*, solana_sysvar_id::SysvarId};
|
|
1804
|
+
|
|
1805
|
+
fn get_keys(instruction: &Instruction) -> Vec<Address> {
|
|
1806
|
+
instruction.accounts.iter().map(|x| x.pubkey).collect()
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
#[allow(deprecated)]
|
|
1810
|
+
#[test]
|
|
1811
|
+
fn test_constants() {
|
|
1812
|
+
// Ensure that the constants are in sync with the solana program.
|
|
1813
|
+
assert_eq!(
|
|
1814
|
+
RECENT_BLOCKHASHES_ID,
|
|
1815
|
+
solana_sysvar::recent_blockhashes::RecentBlockhashes::id(),
|
|
1816
|
+
);
|
|
1817
|
+
|
|
1818
|
+
// Ensure that the constants are in sync with the solana rent.
|
|
1819
|
+
assert_eq!(RENT_ID, solana_sysvar::rent::Rent::id());
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
#[test]
|
|
1823
|
+
fn test_move_many() {
|
|
1824
|
+
let alice_address = Address::new_unique();
|
|
1825
|
+
let bob_address = Address::new_unique();
|
|
1826
|
+
let carol_address = Address::new_unique();
|
|
1827
|
+
let to_lamports = vec![(bob_address, 1), (carol_address, 2)];
|
|
1828
|
+
|
|
1829
|
+
let instructions = transfer_many(&alice_address, &to_lamports);
|
|
1830
|
+
assert_eq!(instructions.len(), 2);
|
|
1831
|
+
assert_eq!(get_keys(&instructions[0]), vec![alice_address, bob_address]);
|
|
1832
|
+
assert_eq!(
|
|
1833
|
+
get_keys(&instructions[1]),
|
|
1834
|
+
vec![alice_address, carol_address]
|
|
1835
|
+
);
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
#[test]
|
|
1839
|
+
fn test_create_nonce_account() {
|
|
1840
|
+
let from_address = Address::new_unique();
|
|
1841
|
+
let nonce_address = Address::new_unique();
|
|
1842
|
+
let authorized = nonce_address;
|
|
1843
|
+
let ixs = create_nonce_account(&from_address, &nonce_address, &authorized, 42);
|
|
1844
|
+
assert_eq!(ixs.len(), 2);
|
|
1845
|
+
let ix = &ixs[0];
|
|
1846
|
+
assert_eq!(ix.program_id, crate::program::ID);
|
|
1847
|
+
let addresss: Vec<_> = ix.accounts.iter().map(|am| am.pubkey).collect();
|
|
1848
|
+
assert!(addresss.contains(&from_address));
|
|
1849
|
+
assert!(addresss.contains(&nonce_address));
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
#[test]
|
|
1853
|
+
fn test_create_account_allow_prefund_with_from_address() {
|
|
1854
|
+
let from_address = Address::new_unique();
|
|
1855
|
+
let to_address = Address::new_unique();
|
|
1856
|
+
|
|
1857
|
+
let instr = create_account_allow_prefund(
|
|
1858
|
+
&to_address,
|
|
1859
|
+
Some((&from_address, 1)),
|
|
1860
|
+
8, // arbitrary space
|
|
1861
|
+
&crate::program::ID,
|
|
1862
|
+
);
|
|
1863
|
+
|
|
1864
|
+
assert_eq!(instr.program_id, crate::program::ID);
|
|
1865
|
+
// Expect two account metas: [to, from]
|
|
1866
|
+
assert_eq!(instr.accounts.len(), 2);
|
|
1867
|
+
|
|
1868
|
+
let to_meta = &instr.accounts[0];
|
|
1869
|
+
assert_eq!(to_meta.pubkey, to_address);
|
|
1870
|
+
assert!(to_meta.is_signer);
|
|
1871
|
+
assert!(to_meta.is_writable);
|
|
1872
|
+
|
|
1873
|
+
let from_meta = &instr.accounts[1];
|
|
1874
|
+
assert_eq!(from_meta.pubkey, from_address);
|
|
1875
|
+
assert!(from_meta.is_signer);
|
|
1876
|
+
assert!(from_meta.is_writable);
|
|
1877
|
+
}
|
|
1878
|
+
|
|
1879
|
+
#[test]
|
|
1880
|
+
fn test_create_account_allow_prefund_without_from_address() {
|
|
1881
|
+
let to_address = Address::new_unique();
|
|
1882
|
+
|
|
1883
|
+
let instr = create_account_allow_prefund(
|
|
1884
|
+
&to_address,
|
|
1885
|
+
None,
|
|
1886
|
+
8, // arbitrary space
|
|
1887
|
+
&crate::program::ID,
|
|
1888
|
+
);
|
|
1889
|
+
|
|
1890
|
+
assert_eq!(instr.program_id, crate::program::ID);
|
|
1891
|
+
// Expect a single account meta: [to]
|
|
1892
|
+
assert_eq!(instr.accounts.len(), 1);
|
|
1893
|
+
|
|
1894
|
+
let to_meta = &instr.accounts[0];
|
|
1895
|
+
assert_eq!(to_meta.pubkey, to_address);
|
|
1896
|
+
assert!(to_meta.is_signer);
|
|
1897
|
+
assert!(to_meta.is_writable);
|
|
1898
|
+
}
|
|
1899
|
+
}
|