interface: Support multiple programs with the same interface via token-2022 (#2386)
This commit is contained in:
parent
9044b9b8cd
commit
777f2eace1
|
@ -261,6 +261,7 @@ dependencies = [
|
|||
"solana-program",
|
||||
"spl-associated-token-account",
|
||||
"spl-token",
|
||||
"spl-token-2022",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3144,9 +3145,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-account-decoder"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f301f42bfbb5fc3dae9068f485544d4260af768d34b773ae9bfc9faf09ea737a"
|
||||
checksum = "701ca0143761d40eb6e2933e8854d1c0a2918ede7419264b71bd142980c5fb32"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"base64 0.13.1",
|
||||
|
@ -3169,9 +3170,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-address-lookup-table-program"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28a3d1316bdac7ec880e2e73be90ccaaf13d1f868cbc15904d3e8030f6c65169"
|
||||
checksum = "03f403a837de4e5d6135bb8100b7aa982a1e5ecc166386258ce3583cd12e2d7c"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bytemuck",
|
||||
|
@ -3190,9 +3191,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-clap-utils"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dae89175548ab57598ec68051b99234cd4a92a92ea3dac90fa6c48c74a3af28"
|
||||
checksum = "94635c6ba33899361777993370090a027abcefda4463f0f51863e0508cc0cd8a"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap 2.34.0",
|
||||
|
@ -3208,9 +3209,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-cli-config"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d75a1cd1e7f04b50a2df322721ba76dd1fafd94623ceb3eab64479bd21571a0"
|
||||
checksum = "7f3185e08728970d1cb67dbcd887180feef72d05b2c0a3a3c61af7f3df5383ed"
|
||||
dependencies = [
|
||||
"dirs-next",
|
||||
"lazy_static",
|
||||
|
@ -3224,9 +3225,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-client"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f420729f477b91581874613f917f4bba9d8e96bf778b22f866f2913fa8f4546d"
|
||||
checksum = "1263dd1bd7473cc367e703f5198396e11dc83be37d10fb3f12fceca0a1eec749"
|
||||
dependencies = [
|
||||
"async-mutex",
|
||||
"async-trait",
|
||||
|
@ -3278,9 +3279,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-config-program"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2afc57e9a42e836bfc5363293969f464106db22a2fd653968708e0313429dba8"
|
||||
checksum = "16219e0c1b2f0c919f238c8951078b45b9c6c00b18acec547eebe2821d2db916"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"chrono",
|
||||
|
@ -3292,9 +3293,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-faucet"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80f0fb1e5de36e1828a4885deb5e06f299d9983c23ab81fdc890f4d03db844ea"
|
||||
checksum = "435cfeb35c5f1e67e7e2ad5ac4106f04edaca0609ad52dbbc7ac051d884d6eca"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"byteorder",
|
||||
|
@ -3316,9 +3317,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-frozen-abi"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e9d5107e663df4a87c658ee764e9f0e4d15adf8bc1d1c9088b45ed8eaaf4958"
|
||||
checksum = "6c5a383f43792311db749bbed4e7794222c9f118b609bc8252b4ea3ad88b4188"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"blake3",
|
||||
|
@ -3350,9 +3351,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-frozen-abi-macro"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e4600fe5ae28cec848debc4ea3b41f34d9d8fd088aca209fbb1e8205489d08d"
|
||||
checksum = "062e282539e770967500945cd2fdb78170a1ea45aff7ad1b4ce4e2cc0b557db8"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.47",
|
||||
"quote 1.0.21",
|
||||
|
@ -3362,9 +3363,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-logger"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f78a1849908659ed28696b92f030b1048b8ddafadfad0e95e79dcd21fe31072"
|
||||
checksum = "0c2bcbaba2c683e7bf80ff4f3a3cdcdaabdb0b21333e8d89aed06be136193d39"
|
||||
dependencies = [
|
||||
"env_logger",
|
||||
"lazy_static",
|
||||
|
@ -3373,9 +3374,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-measure"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7597f7bc74f86e77aad037369b5f5176dfb23fe01a58df350e4d19c12faf8f7f"
|
||||
checksum = "33bbb0e7ee37cdfd18f2636e687cfafcc2e85a7768e283941fd08da022bd0f66"
|
||||
dependencies = [
|
||||
"log",
|
||||
"solana-sdk",
|
||||
|
@ -3383,9 +3384,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-metrics"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7d280910a33496a33222f3a15f0537d33c55aadf16616ed02cc4731a2bf465f"
|
||||
checksum = "f77f7044d57975f001a2c8f3756e4a04f10ca886c69eb8ce0b1786aad52c663d"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"gethostname",
|
||||
|
@ -3397,9 +3398,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-net-utils"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bda47bc41e4e6f43de660786541325550f21383b44bf163a04ca4b214fec850"
|
||||
checksum = "96e4f0b106e881e087226056612ed06ad3c4ff6260d3f9a1c1d54649c127d34f"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"clap 3.2.23",
|
||||
|
@ -3419,9 +3420,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-perf"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fccf3d9e7f6f75727811a618ccd4016d11073023ae9e459ae35d46fb009bde1c"
|
||||
checksum = "c02d0782ecaf35dafc7a88c63ec1f265edf6051b55489180d95757d71a4d66d6"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bincode",
|
||||
|
@ -3446,9 +3447,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-program"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512475cccb7e13f96ba76ed091b2d79a8431a485c73be728cd2235f9adba5a4e"
|
||||
checksum = "75602376f2cea17ac301292a3ded6db73e968310ac482857237d95a34473b62a"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"bincode",
|
||||
|
@ -3495,9 +3496,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-program-runtime"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0741578bbee3a0170a620a4185202d8a559ac4bbba4435fd3d56c8cf432e5ca4"
|
||||
checksum = "eb4a1b61c005eb9c0767b215e428c51adfa6e0023691d37f05653a4cd29bce2b"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"bincode",
|
||||
|
@ -3522,9 +3523,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-rayon-threadlimit"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5acea18b6b1270e8bbb5fb47b8fd943bd30d673bb12b74ae6b485a0ae87da50"
|
||||
checksum = "7091fe2ae498f482f549450e9c5c04e89867dd8622612c742e7c1586b11cc2c1"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"num_cpus",
|
||||
|
@ -3532,9 +3533,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-remote-wallet"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdabea51bcec2773d7a65a37f6e9e9f497ffd6f0b2e8bdc719a9e17c8f711f64"
|
||||
checksum = "874c76b56601eaf7a91a4d119824b57625c638ce42c601166d1e44eef4b28fc6"
|
||||
dependencies = [
|
||||
"console",
|
||||
"dialoguer",
|
||||
|
@ -3551,9 +3552,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-sdk"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0cb45fd782d3793c3821dd961b9c7a28b675e187f7f22cff06e694c7743904ce"
|
||||
checksum = "a46085d2548bb943e7210b28b09378e361350577b391a94457ad78af1a9f75ef"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"base64 0.13.1",
|
||||
|
@ -3602,9 +3603,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-sdk-macro"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a08cc4804804ecb9eb07a16c7ff2d4a770fe0533298f36f867a5efc2e3284745"
|
||||
checksum = "faa38323e649c70b698e49f1ded17849a9b5da2e0821a38ad08327307009e274"
|
||||
dependencies = [
|
||||
"bs58 0.4.0",
|
||||
"proc-macro2 1.0.47",
|
||||
|
@ -3615,9 +3616,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-streamer"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73ced03c13b40fb283782ddc302d8bb7343e3c5f0ce07eb5412a641875b0d4f9"
|
||||
checksum = "57ec79681ce38d1b80ffad5507a4b25f6fc9eba827a589fc789561a022a605cf"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"futures-util",
|
||||
|
@ -3644,9 +3645,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-transaction-status"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89bd8aad27d2f4f9eabcb8979ec360a2cc2237845dfe9f9c53bb2a9bfd377029"
|
||||
checksum = "72d3da9fd5d3d7b7c0bc8c071e614c15f73d75612b1a724a4ebf3139458cbb24"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"base64 0.13.1",
|
||||
|
@ -3673,9 +3674,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-version"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7079bd2671de7c61165e25b2002c5b7178f216b221fe2ba3c8fd8b2af96f6a93"
|
||||
checksum = "f9592a3fb652a0b84593e18935db930e5f7e9614efaf26e15f3cace1c6d47151"
|
||||
dependencies = [
|
||||
"log",
|
||||
"rustc_version 0.4.0",
|
||||
|
@ -3689,9 +3690,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-vote-program"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b83e383b6e1810ae9b3ececd1e9a620e19983e3959ed22866ece6fd6c39c0658"
|
||||
checksum = "1eddab05371499a937a222f101fd9e2b708b87c575ca3cf01e0c012e14aff79d"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"log",
|
||||
|
@ -3710,9 +3711,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "solana-zk-token-sdk"
|
||||
version = "1.14.7"
|
||||
version = "1.14.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d4f9818b158e7a49266b83e0c06e551ba429d2395a55de5803eb6e2daa1260c"
|
||||
checksum = "d81faf1b8f5c550923f01e9b2c41aec8f646cceff7fd72ca6712d10a4022f163"
|
||||
dependencies = [
|
||||
"aes-gcm-siv",
|
||||
"arrayref",
|
||||
|
@ -3757,9 +3758,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "spl-associated-token-account"
|
||||
version = "1.1.1"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16a33ecc83137583902c3e13c02f34151c8b2f2b74120f9c2b3ff841953e083d"
|
||||
checksum = "fbc000f0fdf1f12f99d77d398137c1751345b18c88258ce0f99b7872cf6c9bd6"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"borsh",
|
||||
|
@ -3797,9 +3798,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "spl-token-2022"
|
||||
version = "0.4.2"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0a97cbf60b91b610c846ccf8eecca96d92a24a19ffbf9fe06cd0c84e76ec45e"
|
||||
checksum = "0edb869dbe159b018f17fb9bfa67118c30f232d7f54a73742bc96794dff77ed8"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"bytemuck",
|
||||
|
|
|
@ -223,58 +223,47 @@ use std::ops::{Deref, DerefMut};
|
|||
/// ```
|
||||
/// to access mint accounts.
|
||||
#[derive(Clone)]
|
||||
pub struct Account<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> {
|
||||
pub struct Account<'info, T: AccountSerialize + AccountDeserialize + Clone> {
|
||||
account: T,
|
||||
info: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone + fmt::Debug> fmt::Debug
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Clone + fmt::Debug> fmt::Debug
|
||||
for Account<'info, T>
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Account")
|
||||
self.fmt_with_name("Account", f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Clone + fmt::Debug> Account<'info, T> {
|
||||
pub(crate) fn fmt_with_name(&self, name: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct(name)
|
||||
.field("account", &self.account)
|
||||
.field("info", &self.info)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AccountSerialize + AccountDeserialize + crate::Owner + Clone> Account<'a, T> {
|
||||
fn new(info: AccountInfo<'a>, account: T) -> Account<'a, T> {
|
||||
impl<'a, T: AccountSerialize + AccountDeserialize + Clone> Account<'a, T> {
|
||||
pub(crate) fn new(info: AccountInfo<'a>, account: T) -> Account<'a, T> {
|
||||
Self { info, account }
|
||||
}
|
||||
|
||||
/// Deserializes the given `info` into a `Account`.
|
||||
#[inline(never)]
|
||||
pub fn try_from(info: &AccountInfo<'a>) -> Result<Account<'a, T>> {
|
||||
if info.owner == &system_program::ID && info.lamports() == 0 {
|
||||
return Err(ErrorCode::AccountNotInitialized.into());
|
||||
pub(crate) fn exit_with_expected_owner(
|
||||
&self,
|
||||
expected_owner: &Pubkey,
|
||||
program_id: &Pubkey,
|
||||
) -> Result<()> {
|
||||
// Only persist if the owner is the current program and the account is not closed.
|
||||
if expected_owner == program_id && !crate::common::is_closed(&self.info) {
|
||||
let info = self.to_account_info();
|
||||
let mut data = info.try_borrow_mut_data()?;
|
||||
let dst: &mut [u8] = &mut data;
|
||||
let mut writer = BpfWriter::new(dst);
|
||||
self.account.try_serialize(&mut writer)?;
|
||||
}
|
||||
if info.owner != &T::owner() {
|
||||
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
|
||||
.with_pubkeys((*info.owner, T::owner())));
|
||||
}
|
||||
let mut data: &[u8] = &info.try_borrow_data()?;
|
||||
Ok(Account::new(info.clone(), T::try_deserialize(&mut data)?))
|
||||
}
|
||||
|
||||
/// Deserializes the given `info` into a `Account` without checking
|
||||
/// the account discriminator. Be careful when using this and avoid it if
|
||||
/// possible.
|
||||
#[inline(never)]
|
||||
pub fn try_from_unchecked(info: &AccountInfo<'a>) -> Result<Account<'a, T>> {
|
||||
if info.owner == &system_program::ID && info.lamports() == 0 {
|
||||
return Err(ErrorCode::AccountNotInitialized.into());
|
||||
}
|
||||
if info.owner != &T::owner() {
|
||||
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
|
||||
.with_pubkeys((*info.owner, T::owner())));
|
||||
}
|
||||
let mut data: &[u8] = &info.try_borrow_data()?;
|
||||
Ok(Account::new(
|
||||
info.clone(),
|
||||
T::try_deserialize_unchecked(&mut data)?,
|
||||
))
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reloads the account from storage. This is useful, for example, when
|
||||
|
@ -310,6 +299,41 @@ impl<'a, T: AccountSerialize + AccountDeserialize + crate::Owner + Clone> Accoun
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> Account<'a, T> {
|
||||
/// Deserializes the given `info` into a `Account`.
|
||||
#[inline(never)]
|
||||
pub fn try_from(info: &AccountInfo<'a>) -> Result<Account<'a, T>> {
|
||||
if info.owner == &system_program::ID && info.lamports() == 0 {
|
||||
return Err(ErrorCode::AccountNotInitialized.into());
|
||||
}
|
||||
if info.owner != &T::owner() {
|
||||
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
|
||||
.with_pubkeys((*info.owner, T::owner())));
|
||||
}
|
||||
let mut data: &[u8] = &info.try_borrow_data()?;
|
||||
Ok(Account::new(info.clone(), T::try_deserialize(&mut data)?))
|
||||
}
|
||||
|
||||
/// Deserializes the given `info` into a `Account` without checking
|
||||
/// the account discriminator. Be careful when using this and avoid it if
|
||||
/// possible.
|
||||
#[inline(never)]
|
||||
pub fn try_from_unchecked(info: &AccountInfo<'a>) -> Result<Account<'a, T>> {
|
||||
if info.owner == &system_program::ID && info.lamports() == 0 {
|
||||
return Err(ErrorCode::AccountNotInitialized.into());
|
||||
}
|
||||
if info.owner != &T::owner() {
|
||||
return Err(Error::from(ErrorCode::AccountOwnedByWrongProgram)
|
||||
.with_pubkeys((*info.owner, T::owner())));
|
||||
}
|
||||
let mut data: &[u8] = &info.try_borrow_data()?;
|
||||
Ok(Account::new(
|
||||
info.clone(),
|
||||
T::try_deserialize_unchecked(&mut data)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> Accounts<'info>
|
||||
for Account<'info, T>
|
||||
where
|
||||
|
@ -336,19 +360,11 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> AccountsEx
|
|||
for Account<'info, T>
|
||||
{
|
||||
fn exit(&self, program_id: &Pubkey) -> Result<()> {
|
||||
// Only persist if the owner is the current program and the account is not closed.
|
||||
if &T::owner() == program_id && !crate::common::is_closed(&self.info) {
|
||||
let info = self.to_account_info();
|
||||
let mut data = info.try_borrow_mut_data()?;
|
||||
let dst: &mut [u8] = &mut data;
|
||||
let mut writer = BpfWriter::new(dst);
|
||||
self.account.try_serialize(&mut writer)?;
|
||||
}
|
||||
Ok(())
|
||||
self.exit_with_expected_owner(&T::owner(), program_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> AccountsClose<'info>
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AccountsClose<'info>
|
||||
for Account<'info, T>
|
||||
{
|
||||
fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()> {
|
||||
|
@ -356,9 +372,7 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> AccountsCl
|
|||
}
|
||||
}
|
||||
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> ToAccountMetas
|
||||
for Account<'info, T>
|
||||
{
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountMetas for Account<'info, T> {
|
||||
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
|
||||
let is_signer = is_signer.unwrap_or(self.info.is_signer);
|
||||
let meta = match self.info.is_writable {
|
||||
|
@ -369,7 +383,7 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> ToAccountM
|
|||
}
|
||||
}
|
||||
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> ToAccountInfos<'info>
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfos<'info>
|
||||
for Account<'info, T>
|
||||
{
|
||||
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
|
||||
|
@ -377,7 +391,7 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> ToAccountI
|
|||
}
|
||||
}
|
||||
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> AsRef<AccountInfo<'info>>
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AsRef<AccountInfo<'info>>
|
||||
for Account<'info, T>
|
||||
{
|
||||
fn as_ref(&self) -> &AccountInfo<'info> {
|
||||
|
@ -385,15 +399,13 @@ impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> AsRef<Acco
|
|||
}
|
||||
}
|
||||
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> AsRef<T>
|
||||
for Account<'info, T>
|
||||
{
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AsRef<T> for Account<'info, T> {
|
||||
fn as_ref(&self) -> &T {
|
||||
&self.account
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> Deref for Account<'a, T> {
|
||||
impl<'a, T: AccountSerialize + AccountDeserialize + Clone> Deref for Account<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
@ -401,7 +413,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> Deref for Acc
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> DerefMut for Account<'a, T> {
|
||||
impl<'a, T: AccountSerialize + AccountDeserialize + Clone> DerefMut for Account<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
#[cfg(feature = "anchor-debug")]
|
||||
if !self.info.is_writable {
|
||||
|
@ -412,7 +424,7 @@ impl<'a, T: AccountSerialize + AccountDeserialize + Owner + Clone> DerefMut for
|
|||
}
|
||||
}
|
||||
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Owner + Clone> Key for Account<'info, T> {
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> Key for Account<'info, T> {
|
||||
fn key(&self) -> Pubkey {
|
||||
*self.info.key
|
||||
}
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
//! Type validating that the account is one of a set of given Programs
|
||||
|
||||
use crate::accounts::program::Program;
|
||||
use crate::error::{Error, ErrorCode};
|
||||
use crate::{
|
||||
AccountDeserialize, Accounts, AccountsExit, CheckId, Key, Result, ToAccountInfos,
|
||||
ToAccountMetas,
|
||||
};
|
||||
use solana_program::account_info::AccountInfo;
|
||||
use solana_program::instruction::AccountMeta;
|
||||
use solana_program::pubkey::Pubkey;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Type validating that the account is one of a set of given Programs
|
||||
///
|
||||
/// The `Interface` wraps over the [`Program`](crate::Program), allowing for
|
||||
/// multiple possible program ids. Useful for any program that implements an
|
||||
/// instruction interface. For example, spl-token and spl-token-2022 both implement
|
||||
/// the spl-token interface.
|
||||
///
|
||||
/// # Table of Contents
|
||||
/// - [Basic Functionality](#basic-functionality)
|
||||
/// - [Out of the Box Types](#out-of-the-box-types)
|
||||
///
|
||||
/// # Basic Functionality
|
||||
///
|
||||
/// Checks:
|
||||
///
|
||||
/// - `expected_programs.contains(account_info.key)`
|
||||
/// - `account_info.executable == true`
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// #[program]
|
||||
/// mod my_program {
|
||||
/// fn set_admin_settings(...){...}
|
||||
/// }
|
||||
///
|
||||
/// #[account]
|
||||
/// #[derive(Default)]
|
||||
/// pub struct AdminSettings {
|
||||
/// ...
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Accounts)]
|
||||
/// pub struct SetAdminSettings<'info> {
|
||||
/// #[account(mut, seeds = [b"admin"], bump)]
|
||||
/// pub admin_settings: Account<'info, AdminSettings>,
|
||||
/// #[account(constraint = program.programdata_address()? == Some(program_data.key()))]
|
||||
/// pub program: Interface<'info, MyProgram>,
|
||||
/// #[account(constraint = program_data.upgrade_authority_address == Some(authority.key()))]
|
||||
/// pub program_data: Account<'info, ProgramData>,
|
||||
/// pub authority: Signer<'info>,
|
||||
/// }
|
||||
/// ```
|
||||
/// The given program has a function with which the upgrade authority can set admin settings.
|
||||
///
|
||||
/// The required constraints are as follows:
|
||||
///
|
||||
/// - `program` is the account of the program itself.
|
||||
/// Its constraint checks that `program_data` is the account that contains the program's upgrade authority.
|
||||
/// Implicitly, this checks that `program` is a BPFUpgradeable program (`program.programdata_address()?`
|
||||
/// will be `None` if it's not).
|
||||
/// - `program_data`'s constraint checks that its upgrade authority is the `authority` account.
|
||||
/// - Finally, `authority` needs to sign the transaction.
|
||||
///
|
||||
/// # Out of the Box Types
|
||||
///
|
||||
/// Between the [`anchor_lang`](https://docs.rs/anchor-lang/latest/anchor_lang) and [`anchor_spl`](https://docs.rs/anchor_spl/latest/anchor_spl) crates,
|
||||
/// the following `Interface` types are provided out of the box:
|
||||
///
|
||||
/// - [`TokenInterface`](https://docs.rs/anchor-spl/latest/anchor_spl/token_interface/struct.TokenInterface.html)
|
||||
///
|
||||
#[derive(Clone)]
|
||||
pub struct Interface<'info, T>(Program<'info, T>);
|
||||
impl<'a, T> Interface<'a, T> {
|
||||
pub(crate) fn new(info: AccountInfo<'a>) -> Self {
|
||||
Self(Program::new(info))
|
||||
}
|
||||
pub fn programdata_address(&self) -> Result<Option<Pubkey>> {
|
||||
self.0.programdata_address()
|
||||
}
|
||||
}
|
||||
impl<'a, T: CheckId> TryFrom<&AccountInfo<'a>> for Interface<'a, T> {
|
||||
type Error = Error;
|
||||
/// Deserializes the given `info` into a `Program`.
|
||||
fn try_from(info: &AccountInfo<'a>) -> Result<Self> {
|
||||
T::check_id(info.key)?;
|
||||
if !info.executable {
|
||||
return Err(ErrorCode::InvalidProgramExecutable.into());
|
||||
}
|
||||
Ok(Self::new(info.clone()))
|
||||
}
|
||||
}
|
||||
impl<'info, T> Deref for Interface<'info, T> {
|
||||
type Target = AccountInfo<'info>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl<'info, T> AsRef<AccountInfo<'info>> for Interface<'info, T> {
|
||||
fn as_ref(&self) -> &AccountInfo<'info> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, T: CheckId> Accounts<'info> for Interface<'info, T> {
|
||||
#[inline(never)]
|
||||
fn try_accounts(
|
||||
_program_id: &Pubkey,
|
||||
accounts: &mut &[AccountInfo<'info>],
|
||||
_ix_data: &[u8],
|
||||
_bumps: &mut BTreeMap<String, u8>,
|
||||
_reallocs: &mut BTreeSet<Pubkey>,
|
||||
) -> Result<Self> {
|
||||
if accounts.is_empty() {
|
||||
return Err(ErrorCode::AccountNotEnoughKeys.into());
|
||||
}
|
||||
let account = &accounts[0];
|
||||
*accounts = &accounts[1..];
|
||||
Self::try_from(account)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, T> ToAccountMetas for Interface<'info, T> {
|
||||
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
|
||||
self.0.to_account_metas(is_signer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, T> ToAccountInfos<'info> for Interface<'info, T> {
|
||||
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
|
||||
self.0.to_account_infos()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, T: AccountDeserialize> AccountsExit<'info> for Interface<'info, T> {}
|
||||
|
||||
impl<'info, T: AccountDeserialize> Key for Interface<'info, T> {
|
||||
fn key(&self) -> Pubkey {
|
||||
self.0.key()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,331 @@
|
|||
//! Account container that checks ownership on deserialization.
|
||||
|
||||
use crate::accounts::account::Account;
|
||||
use crate::error::ErrorCode;
|
||||
use crate::{
|
||||
AccountDeserialize, AccountSerialize, Accounts, AccountsClose, AccountsExit, CheckOwner, Key,
|
||||
Owners, Result, ToAccountInfos, ToAccountMetas,
|
||||
};
|
||||
use solana_program::account_info::AccountInfo;
|
||||
use solana_program::instruction::AccountMeta;
|
||||
use solana_program::pubkey::Pubkey;
|
||||
use solana_program::system_program;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fmt;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// Wrapper around [`AccountInfo`](crate::solana_program::account_info::AccountInfo)
|
||||
/// that verifies program ownership and deserializes underlying data into a Rust type.
|
||||
///
|
||||
/// # Table of Contents
|
||||
/// - [Basic Functionality](#basic-functionality)
|
||||
/// - [Using InterfaceAccount with non-anchor types](#using-interface-account-with-non-anchor-types)
|
||||
/// - [Out of the box wrapper types](#out-of-the-box-wrapper-types)
|
||||
///
|
||||
/// # Basic Functionality
|
||||
///
|
||||
/// InterfaceAccount checks that `T::owners().contains(Account.info.owner)`.
|
||||
/// This means that the data type that Accounts wraps around (`=T`) needs to
|
||||
/// implement the [Owners trait](crate::Owners).
|
||||
/// The `#[account]` attribute implements the Owners trait for
|
||||
/// a struct using multiple `crate::ID`s declared by [`declareId`](crate::declare_id)
|
||||
/// in the same program. It follows that InterfaceAccount can also be used
|
||||
/// with a `T` that comes from a different program.
|
||||
///
|
||||
/// Checks:
|
||||
///
|
||||
/// - `T::owners().contains(InterfaceAccount.info.owner)`
|
||||
/// - `!(InterfaceAccount.info.owner == SystemProgram && InterfaceAccount.info.lamports() == 0)`
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// use anchor_lang::prelude::*;
|
||||
/// use other_program::Auth;
|
||||
///
|
||||
/// declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
|
||||
///
|
||||
/// #[program]
|
||||
/// mod hello_anchor {
|
||||
/// use super::*;
|
||||
/// pub fn set_data(ctx: Context<SetData>, data: u64) -> Result<()> {
|
||||
/// if (*ctx.accounts.auth_account).authorized {
|
||||
/// (*ctx.accounts.my_account).data = data;
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[account]
|
||||
/// #[derive(Default)]
|
||||
/// pub struct MyData {
|
||||
/// pub data: u64
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Accounts)]
|
||||
/// pub struct SetData<'info> {
|
||||
/// #[account(mut)]
|
||||
/// pub my_account: InterfaceAccount<'info, MyData> // checks that my_account.info.owner == Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS
|
||||
/// pub auth_account: InterfaceAccount<'info, Auth> // checks that auth_account.info.owner == FEZGUxNhZWpYPj9MJCrZJvUo1iF9ys34UHx52y4SzVW9
|
||||
/// }
|
||||
///
|
||||
/// // In a different program
|
||||
///
|
||||
/// ...
|
||||
/// declare_id!("FEZGUxNhZWpYPj9MJCrZJvUo1iF9ys34UHx52y4SzVW9");
|
||||
/// #[account]
|
||||
/// #[derive(Default)]
|
||||
/// pub struct Auth {
|
||||
/// pub authorized: bool
|
||||
/// }
|
||||
/// ...
|
||||
/// ```
|
||||
///
|
||||
/// # Using InterfaceAccount with non-anchor programs
|
||||
///
|
||||
/// InterfaceAccount can also be used with non-anchor programs. The data types from
|
||||
/// those programs are not annotated with `#[account]` so you have to
|
||||
/// - create a wrapper type around the structs you want to wrap with InterfaceAccount
|
||||
/// - implement the functions required by InterfaceAccount yourself
|
||||
/// instead of using `#[account]`. You only have to implement a fraction of the
|
||||
/// functions `#[account]` generates. See the example below for the code you have
|
||||
/// to write.
|
||||
///
|
||||
/// The mint wrapper type that Anchor provides out of the box for the token program ([source](https://github.com/coral-xyz/anchor/blob/master/spl/src/token.rs))
|
||||
/// ```ignore
|
||||
/// #[derive(Clone)]
|
||||
/// pub struct Mint(spl_token::state::Mint);
|
||||
///
|
||||
/// // This is necessary so we can use "anchor_spl::token::Mint::LEN"
|
||||
/// // because rust does not resolve "anchor_spl::token::Mint::LEN" to
|
||||
/// // "spl_token::state::Mint::LEN" automatically
|
||||
/// impl Mint {
|
||||
/// pub const LEN: usize = spl_token::state::Mint::LEN;
|
||||
/// }
|
||||
///
|
||||
/// // You don't have to implement the "try_deserialize" function
|
||||
/// // from this trait. It delegates to
|
||||
/// // "try_deserialize_unchecked" by default which is what we want here
|
||||
/// // because non-anchor accounts don't have a discriminator to check
|
||||
/// impl anchor_lang::AccountDeserialize for Mint {
|
||||
/// fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self> {
|
||||
/// spl_token::state::Mint::unpack(buf).map(Mint)
|
||||
/// }
|
||||
/// }
|
||||
/// // AccountSerialize defaults to a no-op which is what we want here
|
||||
/// // because it's a foreign program, so our program does not
|
||||
/// // have permission to write to the foreign program's accounts anyway
|
||||
/// impl anchor_lang::AccountSerialize for Mint {}
|
||||
///
|
||||
/// impl anchor_lang::Owner for Mint {
|
||||
/// fn owner() -> Pubkey {
|
||||
/// // pub use spl_token::ID is used at the top of the file
|
||||
/// ID
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Implement the "std::ops::Deref" trait for better user experience
|
||||
/// impl Deref for Mint {
|
||||
/// type Target = spl_token::state::Mint;
|
||||
///
|
||||
/// fn deref(&self) -> &Self::Target {
|
||||
/// &self.0
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Out of the box wrapper types
|
||||
///
|
||||
/// ### SPL Types
|
||||
///
|
||||
/// Anchor provides wrapper types to access accounts owned by the token programs. Use
|
||||
/// ```ignore
|
||||
/// use anchor_spl::token_interface::TokenAccount;
|
||||
///
|
||||
/// #[derive(Accounts)]
|
||||
/// pub struct Example {
|
||||
/// pub my_acc: InterfaceAccount<'info, TokenAccount>
|
||||
/// }
|
||||
/// ```
|
||||
/// to access token accounts and
|
||||
/// ```ignore
|
||||
/// use anchor_spl::token_interface::Mint;
|
||||
///
|
||||
/// #[derive(Accounts)]
|
||||
/// pub struct Example {
|
||||
/// pub my_acc: InterfaceAccount<'info, Mint>
|
||||
/// }
|
||||
/// ```
|
||||
/// to access mint accounts.
|
||||
#[derive(Clone)]
|
||||
pub struct InterfaceAccount<'info, T: AccountSerialize + AccountDeserialize + Clone> {
|
||||
account: Account<'info, T>,
|
||||
// The owner here is used to make sure that changes aren't incorrectly propagated
|
||||
// to an account with a modified owner
|
||||
owner: Pubkey,
|
||||
}
|
||||
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Clone + fmt::Debug> fmt::Debug
|
||||
for InterfaceAccount<'info, T>
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.account.fmt_with_name("InterfaceAccount", f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AccountSerialize + AccountDeserialize + Clone> InterfaceAccount<'a, T> {
|
||||
fn new(info: AccountInfo<'a>, account: T) -> Self {
|
||||
let owner = *info.owner;
|
||||
Self {
|
||||
account: Account::new(info, account),
|
||||
owner,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reloads the account from storage. This is useful, for example, when
|
||||
/// observing side effects after CPI.
|
||||
pub fn reload(&mut self) -> Result<()> {
|
||||
self.account.reload()
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> T {
|
||||
self.account.into_inner()
|
||||
}
|
||||
|
||||
/// Sets the inner account.
|
||||
///
|
||||
/// Instead of this:
|
||||
/// ```ignore
|
||||
/// pub fn new_user(ctx: Context<CreateUser>, new_user:User) -> Result<()> {
|
||||
/// (*ctx.accounts.user_to_create).name = new_user.name;
|
||||
/// (*ctx.accounts.user_to_create).age = new_user.age;
|
||||
/// (*ctx.accounts.user_to_create).address = new_user.address;
|
||||
/// }
|
||||
/// ```
|
||||
/// You can do this:
|
||||
/// ```ignore
|
||||
/// pub fn new_user(ctx: Context<CreateUser>, new_user:User) -> Result<()> {
|
||||
/// ctx.accounts.user_to_create.set_inner(new_user);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn set_inner(&mut self, inner: T) {
|
||||
self.account.set_inner(inner);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AccountSerialize + AccountDeserialize + CheckOwner + Clone> InterfaceAccount<'a, T> {
|
||||
/// Deserializes the given `info` into a `InterfaceAccount`.
|
||||
#[inline(never)]
|
||||
pub fn try_from(info: &AccountInfo<'a>) -> Result<Self> {
|
||||
if info.owner == &system_program::ID && info.lamports() == 0 {
|
||||
return Err(ErrorCode::AccountNotInitialized.into());
|
||||
}
|
||||
T::check_owner(info.owner)?;
|
||||
let mut data: &[u8] = &info.try_borrow_data()?;
|
||||
Ok(Self::new(info.clone(), T::try_deserialize(&mut data)?))
|
||||
}
|
||||
|
||||
/// Deserializes the given `info` into a `InterfaceAccount` without checking
|
||||
/// the account discriminator. Be careful when using this and avoid it if
|
||||
/// possible.
|
||||
#[inline(never)]
|
||||
pub fn try_from_unchecked(info: &AccountInfo<'a>) -> Result<Self> {
|
||||
if info.owner == &system_program::ID && info.lamports() == 0 {
|
||||
return Err(ErrorCode::AccountNotInitialized.into());
|
||||
}
|
||||
T::check_owner(info.owner)?;
|
||||
let mut data: &[u8] = &info.try_borrow_data()?;
|
||||
Ok(Self::new(
|
||||
info.clone(),
|
||||
T::try_deserialize_unchecked(&mut data)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + CheckOwner + Clone> Accounts<'info>
|
||||
for InterfaceAccount<'info, T>
|
||||
{
|
||||
#[inline(never)]
|
||||
fn try_accounts(
|
||||
_program_id: &Pubkey,
|
||||
accounts: &mut &[AccountInfo<'info>],
|
||||
_ix_data: &[u8],
|
||||
_bumps: &mut BTreeMap<String, u8>,
|
||||
_reallocs: &mut BTreeSet<Pubkey>,
|
||||
) -> Result<Self> {
|
||||
if accounts.is_empty() {
|
||||
return Err(ErrorCode::AccountNotEnoughKeys.into());
|
||||
}
|
||||
let account = &accounts[0];
|
||||
*accounts = &accounts[1..];
|
||||
Self::try_from(account)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Owners + Clone> AccountsExit<'info>
|
||||
for InterfaceAccount<'info, T>
|
||||
{
|
||||
fn exit(&self, program_id: &Pubkey) -> Result<()> {
|
||||
self.account
|
||||
.exit_with_expected_owner(&self.owner, program_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AccountsClose<'info>
|
||||
for InterfaceAccount<'info, T>
|
||||
{
|
||||
fn close(&self, sol_destination: AccountInfo<'info>) -> Result<()> {
|
||||
self.account.close(sol_destination)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountMetas
|
||||
for InterfaceAccount<'info, T>
|
||||
{
|
||||
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
|
||||
self.account.to_account_metas(is_signer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> ToAccountInfos<'info>
|
||||
for InterfaceAccount<'info, T>
|
||||
{
|
||||
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
|
||||
self.account.to_account_infos()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AsRef<AccountInfo<'info>>
|
||||
for InterfaceAccount<'info, T>
|
||||
{
|
||||
fn as_ref(&self) -> &AccountInfo<'info> {
|
||||
self.account.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> AsRef<T>
|
||||
for InterfaceAccount<'info, T>
|
||||
{
|
||||
fn as_ref(&self) -> &T {
|
||||
self.account.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AccountSerialize + AccountDeserialize + Clone> Deref for InterfaceAccount<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.account.deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AccountSerialize + AccountDeserialize + Clone> DerefMut for InterfaceAccount<'a, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.account.deref_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, T: AccountSerialize + AccountDeserialize + Clone> Key for InterfaceAccount<'info, T> {
|
||||
fn key(&self) -> Pubkey {
|
||||
self.account.key()
|
||||
}
|
||||
}
|
|
@ -4,6 +4,8 @@ pub mod account;
|
|||
pub mod account_info;
|
||||
pub mod account_loader;
|
||||
pub mod boxed;
|
||||
pub mod interface;
|
||||
pub mod interface_account;
|
||||
pub mod option;
|
||||
pub mod program;
|
||||
pub mod signer;
|
||||
|
|
|
@ -9,6 +9,7 @@ use solana_program::bpf_loader_upgradeable::{self, UpgradeableLoaderState};
|
|||
use solana_program::instruction::AccountMeta;
|
||||
use solana_program::pubkey::Pubkey;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
|
@ -75,38 +76,25 @@ use std::ops::Deref;
|
|||
/// - [`Token`](https://docs.rs/anchor-spl/latest/anchor_spl/token/struct.Token.html)
|
||||
///
|
||||
#[derive(Clone)]
|
||||
pub struct Program<'info, T: Id + Clone> {
|
||||
pub struct Program<'info, T> {
|
||||
info: AccountInfo<'info>,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'info, T: Id + Clone + fmt::Debug> fmt::Debug for Program<'info, T> {
|
||||
impl<'info, T: fmt::Debug> fmt::Debug for Program<'info, T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Program").field("info", &self.info).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Id + Clone> Program<'a, T> {
|
||||
fn new(info: AccountInfo<'a>) -> Program<'a, T> {
|
||||
impl<'a, T> Program<'a, T> {
|
||||
pub(crate) fn new(info: AccountInfo<'a>) -> Program<'a, T> {
|
||||
Self {
|
||||
info,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Deserializes the given `info` into a `Program`.
|
||||
#[inline(never)]
|
||||
pub fn try_from(info: &AccountInfo<'a>) -> Result<Program<'a, T>> {
|
||||
if info.key != &T::id() {
|
||||
return Err(Error::from(ErrorCode::InvalidProgramId).with_pubkeys((*info.key, T::id())));
|
||||
}
|
||||
if !info.executable {
|
||||
return Err(ErrorCode::InvalidProgramExecutable.into());
|
||||
}
|
||||
|
||||
Ok(Program::new(info.clone()))
|
||||
}
|
||||
|
||||
pub fn programdata_address(&self) -> Result<Option<Pubkey>> {
|
||||
if *self.info.owner == bpf_loader_upgradeable::ID {
|
||||
let mut data: &[u8] = &self.info.try_borrow_data()?;
|
||||
|
@ -137,10 +125,22 @@ impl<'a, T: Id + Clone> Program<'a, T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'info, T> Accounts<'info> for Program<'info, T>
|
||||
where
|
||||
T: Id + Clone,
|
||||
{
|
||||
impl<'a, T: Id> TryFrom<&AccountInfo<'a>> for Program<'a, T> {
|
||||
type Error = Error;
|
||||
/// Deserializes the given `info` into a `Program`.
|
||||
fn try_from(info: &AccountInfo<'a>) -> Result<Self> {
|
||||
if info.key != &T::id() {
|
||||
return Err(Error::from(ErrorCode::InvalidProgramId).with_pubkeys((*info.key, T::id())));
|
||||
}
|
||||
if !info.executable {
|
||||
return Err(ErrorCode::InvalidProgramExecutable.into());
|
||||
}
|
||||
|
||||
Ok(Program::new(info.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, T: Id> Accounts<'info> for Program<'info, T> {
|
||||
#[inline(never)]
|
||||
fn try_accounts(
|
||||
_program_id: &Pubkey,
|
||||
|
@ -158,7 +158,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
impl<'info, T: Id + Clone> ToAccountMetas for Program<'info, T> {
|
||||
impl<'info, T> ToAccountMetas for Program<'info, T> {
|
||||
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
|
||||
let is_signer = is_signer.unwrap_or(self.info.is_signer);
|
||||
let meta = match self.info.is_writable {
|
||||
|
@ -169,19 +169,19 @@ impl<'info, T: Id + Clone> ToAccountMetas for Program<'info, T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'info, T: Id + Clone> ToAccountInfos<'info> for Program<'info, T> {
|
||||
impl<'info, T> ToAccountInfos<'info> for Program<'info, T> {
|
||||
fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
|
||||
vec![self.info.clone()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, T: Id + Clone> AsRef<AccountInfo<'info>> for Program<'info, T> {
|
||||
impl<'info, T> AsRef<AccountInfo<'info>> for Program<'info, T> {
|
||||
fn as_ref(&self) -> &AccountInfo<'info> {
|
||||
&self.info
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info, T: Id + Clone> Deref for Program<'info, T> {
|
||||
impl<'info, T> Deref for Program<'info, T> {
|
||||
type Target = AccountInfo<'info>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
@ -189,9 +189,9 @@ impl<'info, T: Id + Clone> Deref for Program<'info, T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'info, T: AccountDeserialize + Id + Clone> AccountsExit<'info> for Program<'info, T> {}
|
||||
impl<'info, T: AccountDeserialize> AccountsExit<'info> for Program<'info, T> {}
|
||||
|
||||
impl<'info, T: AccountDeserialize + Id + Clone> Key for Program<'info, T> {
|
||||
impl<'info, T: AccountDeserialize> Key for Program<'info, T> {
|
||||
fn key(&self) -> Pubkey {
|
||||
*self.info.key
|
||||
}
|
||||
|
|
|
@ -224,11 +224,54 @@ pub trait Owner {
|
|||
fn owner() -> Pubkey;
|
||||
}
|
||||
|
||||
/// Defines a list of addresses expected to own an account.
|
||||
pub trait Owners {
|
||||
fn owners() -> &'static [Pubkey];
|
||||
}
|
||||
|
||||
/// Defines a trait for checking the owner of a program.
|
||||
pub trait CheckOwner {
|
||||
fn check_owner(owner: &Pubkey) -> Result<()>;
|
||||
}
|
||||
|
||||
impl<T: Owners> CheckOwner for T {
|
||||
fn check_owner(owner: &Pubkey) -> Result<()> {
|
||||
if !Self::owners().contains(owner) {
|
||||
Err(
|
||||
error::Error::from(error::ErrorCode::AccountOwnedByWrongProgram)
|
||||
.with_account_name(*owner),
|
||||
)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the id of a program.
|
||||
pub trait Id {
|
||||
fn id() -> Pubkey;
|
||||
}
|
||||
|
||||
/// Defines the possible ids of a program.
|
||||
pub trait Ids {
|
||||
fn ids() -> &'static [Pubkey];
|
||||
}
|
||||
|
||||
/// Defines a trait for checking the id of a program.
|
||||
pub trait CheckId {
|
||||
fn check_id(id: &Pubkey) -> Result<()>;
|
||||
}
|
||||
|
||||
impl<T: Ids> CheckId for T {
|
||||
fn check_id(id: &Pubkey) -> Result<()> {
|
||||
if !Self::ids().contains(id) {
|
||||
Err(error::Error::from(error::ErrorCode::InvalidProgramId).with_account_name(*id))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the Pubkey of an account.
|
||||
pub trait Key {
|
||||
fn key(&self) -> Pubkey;
|
||||
|
@ -245,7 +288,8 @@ impl Key for Pubkey {
|
|||
pub mod prelude {
|
||||
pub use super::{
|
||||
access_control, account, accounts::account::Account,
|
||||
accounts::account_loader::AccountLoader, accounts::program::Program,
|
||||
accounts::account_loader::AccountLoader, accounts::interface::Interface,
|
||||
accounts::interface_account::InterfaceAccount, accounts::program::Program,
|
||||
accounts::signer::Signer, accounts::system_account::SystemAccount,
|
||||
accounts::sysvar::Sysvar, accounts::unchecked_account::UncheckedAccount, constant,
|
||||
context::Context, context::CpiContext, declare_id, emit, err, error, event, program,
|
||||
|
|
|
@ -284,6 +284,7 @@ pub fn generate_constraint_signer(f: &Field, c: &ConstraintSigner) -> proc_macro
|
|||
let info = match f.ty {
|
||||
Ty::AccountInfo => quote! { #ident },
|
||||
Ty::Account(_) => quote! { #ident.to_account_info() },
|
||||
Ty::InterfaceAccount(_) => quote! { #ident.to_account_info() },
|
||||
Ty::AccountLoader(_) => quote! { #ident.to_account_info() },
|
||||
_ => panic!("Invalid syntax: signer cannot be specified."),
|
||||
};
|
||||
|
@ -520,9 +521,11 @@ fn generate_constraint_init_group(
|
|||
|
||||
let payer_optional_check = check_scope.generate_check(payer);
|
||||
|
||||
let token_account_space = generate_get_token_account_space(mint);
|
||||
|
||||
let create_account = generate_create_account(
|
||||
field,
|
||||
quote! {anchor_spl::token::TokenAccount::LEN},
|
||||
quote! {#token_account_space},
|
||||
quote! {&token_program.key()},
|
||||
quote! {#payer},
|
||||
seeds_with_bump,
|
||||
|
@ -544,13 +547,13 @@ fn generate_constraint_init_group(
|
|||
|
||||
// Initialize the token account.
|
||||
let cpi_program = token_program.to_account_info();
|
||||
let accounts = anchor_spl::token::InitializeAccount3 {
|
||||
let accounts = ::anchor_spl::token_interface::InitializeAccount3 {
|
||||
account: #field.to_account_info(),
|
||||
mint: #mint.to_account_info(),
|
||||
authority: #owner.to_account_info(),
|
||||
};
|
||||
let cpi_ctx = anchor_lang::context::CpiContext::new(cpi_program, accounts);
|
||||
anchor_spl::token::initialize_account3(cpi_ctx)?;
|
||||
::anchor_spl::token_interface::initialize_account3(cpi_ctx)?;
|
||||
}
|
||||
|
||||
let pa: #ty_decl = #from_account_info_unchecked;
|
||||
|
@ -599,7 +602,7 @@ fn generate_constraint_init_group(
|
|||
#payer_optional_check
|
||||
|
||||
let cpi_program = associated_token_program.to_account_info();
|
||||
let cpi_accounts = anchor_spl::associated_token::Create {
|
||||
let cpi_accounts = ::anchor_spl::associated_token::Create {
|
||||
payer: #payer.to_account_info(),
|
||||
associated_token: #field.to_account_info(),
|
||||
authority: #owner.to_account_info(),
|
||||
|
@ -608,7 +611,7 @@ fn generate_constraint_init_group(
|
|||
token_program: token_program.to_account_info(),
|
||||
};
|
||||
let cpi_ctx = anchor_lang::context::CpiContext::new(cpi_program, cpi_accounts);
|
||||
anchor_spl::associated_token::create(cpi_ctx)?;
|
||||
::anchor_spl::associated_token::create(cpi_ctx)?;
|
||||
}
|
||||
let pa: #ty_decl = #from_account_info_unchecked;
|
||||
if #if_needed {
|
||||
|
@ -619,7 +622,7 @@ fn generate_constraint_init_group(
|
|||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str).with_pubkeys((pa.owner, #owner.key())));
|
||||
}
|
||||
|
||||
if pa.key() != anchor_spl::associated_token::get_associated_token_address(&#owner.key(), &#mint.key()) {
|
||||
if pa.key() != ::anchor_spl::associated_token::get_associated_token_address(&#owner.key(), &#mint.key()) {
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::AccountNotAssociatedTokenAccount).with_account_name(#name_str));
|
||||
}
|
||||
}
|
||||
|
@ -654,7 +657,7 @@ fn generate_constraint_init_group(
|
|||
|
||||
let create_account = generate_create_account(
|
||||
field,
|
||||
quote! {anchor_spl::token::Mint::LEN},
|
||||
quote! {::anchor_spl::token::Mint::LEN},
|
||||
quote! {&token_program.key()},
|
||||
quote! {#payer},
|
||||
seeds_with_bump,
|
||||
|
@ -682,11 +685,11 @@ fn generate_constraint_init_group(
|
|||
|
||||
// Initialize the mint account.
|
||||
let cpi_program = token_program.to_account_info();
|
||||
let accounts = anchor_spl::token::InitializeMint2 {
|
||||
let accounts = ::anchor_spl::token_interface::InitializeMint2 {
|
||||
mint: #field.to_account_info(),
|
||||
};
|
||||
let cpi_ctx = anchor_lang::context::CpiContext::new(cpi_program, accounts);
|
||||
anchor_spl::token::initialize_mint2(cpi_ctx, #decimals, &#owner.key(), #freeze_authority)?;
|
||||
::anchor_spl::token_interface::initialize_mint2(cpi_ctx, #decimals, &#owner.key(), #freeze_authority)?;
|
||||
}
|
||||
let pa: #ty_decl = #from_account_info_unchecked;
|
||||
if #if_needed {
|
||||
|
@ -707,7 +710,7 @@ fn generate_constraint_init_group(
|
|||
};
|
||||
}
|
||||
}
|
||||
InitKind::Program { owner } => {
|
||||
InitKind::Program { owner } | InitKind::Interface { owner } => {
|
||||
// Define the space variable.
|
||||
let space = quote! {let space = #space;};
|
||||
|
||||
|
@ -895,7 +898,7 @@ fn generate_constraint_associated_token(
|
|||
if my_owner != wallet_address {
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintTokenOwner).with_account_name(#name_str).with_pubkeys((my_owner, wallet_address)));
|
||||
}
|
||||
let __associated_token_address = anchor_spl::associated_token::get_associated_token_address(&wallet_address, &#spl_token_mint_address.key());
|
||||
let __associated_token_address = ::anchor_spl::associated_token::get_associated_token_address(&wallet_address, &#spl_token_mint_address.key());
|
||||
let my_key = #name.key();
|
||||
if my_key != __associated_token_address {
|
||||
return Err(anchor_lang::error::Error::from(anchor_lang::error::ErrorCode::ConstraintAssociated).with_account_name(#name_str).with_pubkeys((my_key, __associated_token_address)));
|
||||
|
@ -1028,6 +1031,25 @@ impl<'a> OptionalCheckScope<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn generate_get_token_account_space(mint: &Expr) -> proc_macro2::TokenStream {
|
||||
quote! {
|
||||
{
|
||||
let mint_info = #mint.to_account_info();
|
||||
if *mint_info.owner == ::anchor_spl::token_2022::Token2022::id() {
|
||||
use ::anchor_spl::token_2022::spl_token_2022::extension::{BaseStateWithExtensions, ExtensionType, StateWithExtensions};
|
||||
use ::anchor_spl::token_2022::spl_token_2022::state::{Account, Mint};
|
||||
let mint_data = mint_info.try_borrow_data()?;
|
||||
let mint_state = StateWithExtensions::<Mint>::unpack(&mint_data)?;
|
||||
let mint_extensions = mint_state.get_extension_types()?;
|
||||
let required_extensions = ExtensionType::get_required_init_account_extensions(&mint_extensions);
|
||||
ExtensionType::get_account_len::<Account>(&required_extensions)
|
||||
} else {
|
||||
::anchor_spl::token::TokenAccount::LEN
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generated code to create an account with with system program with the
|
||||
// given `space` amount of data, owned by `owner`.
|
||||
//
|
||||
|
@ -1051,13 +1073,14 @@ fn generate_create_account(
|
|||
let __current_lamports = #field.lamports();
|
||||
if __current_lamports == 0 {
|
||||
// Create the token account with right amount of lamports and space, and the correct owner.
|
||||
let lamports = __anchor_rent.minimum_balance(#space);
|
||||
let space = #space;
|
||||
let lamports = __anchor_rent.minimum_balance(space);
|
||||
let cpi_accounts = anchor_lang::system_program::CreateAccount {
|
||||
from: #payer.to_account_info(),
|
||||
to: #field.to_account_info()
|
||||
};
|
||||
let cpi_context = anchor_lang::context::CpiContext::new(system_program.to_account_info(), cpi_accounts);
|
||||
anchor_lang::system_program::create_account(cpi_context.with_signer(&[#seeds_with_nonce]), lamports, #space as u64, #owner)?;
|
||||
anchor_lang::system_program::create_account(cpi_context.with_signer(&[#seeds_with_nonce]), lamports, space as u64, #owner)?;
|
||||
} else {
|
||||
require_keys_neq!(#payer.key(), #field.key(), anchor_lang::error::ErrorCode::TryingToInitPayerAsProgramAccount);
|
||||
// Fund the account for rent exemption.
|
||||
|
|
|
@ -254,7 +254,8 @@ impl Field {
|
|||
Ty::SystemAccount => quote! {
|
||||
SystemAccount
|
||||
},
|
||||
Ty::Account(AccountTy { boxed, .. }) => {
|
||||
Ty::Account(AccountTy { boxed, .. })
|
||||
| Ty::InterfaceAccount(InterfaceAccountTy { boxed, .. }) => {
|
||||
if *boxed {
|
||||
quote! {
|
||||
Box<#container_ty<#account_ty>>
|
||||
|
@ -321,7 +322,8 @@ impl Field {
|
|||
Ty::UncheckedAccount => {
|
||||
quote! { UncheckedAccount::try_from(#field.to_account_info()) }
|
||||
}
|
||||
Ty::Account(AccountTy { boxed, .. }) => {
|
||||
Ty::Account(AccountTy { boxed, .. })
|
||||
| Ty::InterfaceAccount(InterfaceAccountTy { boxed, .. }) => {
|
||||
let stream = if checked {
|
||||
quote! {
|
||||
match #container_ty::try_from(&#field) {
|
||||
|
@ -392,6 +394,10 @@ impl Field {
|
|||
},
|
||||
Ty::Sysvar(_) => quote! { anchor_lang::accounts::sysvar::Sysvar },
|
||||
Ty::Program(_) => quote! { anchor_lang::accounts::program::Program },
|
||||
Ty::Interface(_) => quote! { anchor_lang::accounts::interface::Interface },
|
||||
Ty::InterfaceAccount(_) => {
|
||||
quote! { anchor_lang::accounts::interface_account::InterfaceAccount }
|
||||
}
|
||||
Ty::AccountInfo => quote! {},
|
||||
Ty::UncheckedAccount => quote! {},
|
||||
Ty::Signer => quote! {},
|
||||
|
@ -424,6 +430,12 @@ impl Field {
|
|||
#ident
|
||||
}
|
||||
}
|
||||
Ty::InterfaceAccount(ty) => {
|
||||
let ident = &ty.account_type_path;
|
||||
quote! {
|
||||
#ident
|
||||
}
|
||||
}
|
||||
Ty::AccountLoader(ty) => {
|
||||
let ident = &ty.account_type_path;
|
||||
quote! {
|
||||
|
@ -448,6 +460,12 @@ impl Field {
|
|||
#program
|
||||
}
|
||||
}
|
||||
Ty::Interface(ty) => {
|
||||
let program = &ty.account_type_path;
|
||||
quote! {
|
||||
#program
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -471,6 +489,8 @@ pub enum Ty {
|
|||
Sysvar(SysvarTy),
|
||||
Account(AccountTy),
|
||||
Program(ProgramTy),
|
||||
Interface(InterfaceTy),
|
||||
InterfaceAccount(InterfaceAccountTy),
|
||||
Signer,
|
||||
SystemAccount,
|
||||
ProgramData,
|
||||
|
@ -504,12 +524,26 @@ pub struct AccountTy {
|
|||
pub boxed: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct InterfaceAccountTy {
|
||||
// The struct type of the account.
|
||||
pub account_type_path: TypePath,
|
||||
// True if the account has been boxed via `Box<T>`.
|
||||
pub boxed: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ProgramTy {
|
||||
// The struct type of the account.
|
||||
pub account_type_path: TypePath,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct InterfaceTy {
|
||||
// The struct type of the account.
|
||||
pub account_type_path: TypePath,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
pub name: String,
|
||||
|
@ -760,6 +794,9 @@ pub enum InitKind {
|
|||
Program {
|
||||
owner: Option<Expr>,
|
||||
},
|
||||
Interface {
|
||||
owner: Option<Expr>,
|
||||
},
|
||||
// Owner for token and mint represents the authority. Not to be confused
|
||||
// with the owner of the AccountInfo.
|
||||
Token {
|
||||
|
|
|
@ -91,7 +91,7 @@ fn constraints_cross_checks(fields: &[AccountField]) -> ParseResult<()> {
|
|||
let kind = &init_fields[0].constraints.init.as_ref().unwrap().kind;
|
||||
// init token/a_token/mint needs token program.
|
||||
match kind {
|
||||
InitKind::Program { .. } => (),
|
||||
InitKind::Program { .. } | InitKind::Interface { .. } => (),
|
||||
InitKind::Token { .. } | InitKind::AssociatedToken { .. } | InitKind::Mint { .. } => {
|
||||
if !fields
|
||||
.iter()
|
||||
|
@ -289,6 +289,8 @@ fn is_field_primitive(f: &syn::Field) -> ParseResult<bool> {
|
|||
| "AccountLoader"
|
||||
| "Account"
|
||||
| "Program"
|
||||
| "Interface"
|
||||
| "InterfaceAccount"
|
||||
| "Signer"
|
||||
| "SystemAccount"
|
||||
| "ProgramData"
|
||||
|
@ -305,6 +307,8 @@ fn parse_ty(f: &syn::Field) -> ParseResult<(Ty, bool)> {
|
|||
"AccountLoader" => Ty::AccountLoader(parse_program_account_loader(&path)?),
|
||||
"Account" => Ty::Account(parse_account_ty(&path)?),
|
||||
"Program" => Ty::Program(parse_program_ty(&path)?),
|
||||
"Interface" => Ty::Interface(parse_interface_ty(&path)?),
|
||||
"InterfaceAccount" => Ty::InterfaceAccount(parse_interface_account_ty(&path)?),
|
||||
"Signer" => Ty::Signer,
|
||||
"SystemAccount" => Ty::SystemAccount,
|
||||
"ProgramData" => Ty::ProgramData,
|
||||
|
@ -358,6 +362,12 @@ fn ident_string(f: &syn::Field) -> ParseResult<(String, bool, Path)> {
|
|||
{
|
||||
return Ok(("Account".to_string(), optional, path));
|
||||
}
|
||||
if parser::tts_to_string(&path)
|
||||
.replace(' ', "")
|
||||
.starts_with("Box<InterfaceAccount<")
|
||||
{
|
||||
return Ok(("InterfaceAccount".to_string(), optional, path));
|
||||
}
|
||||
// TODO: allow segmented paths.
|
||||
if path.segments.len() != 1 {
|
||||
return Err(ParseError::new(
|
||||
|
@ -388,17 +398,31 @@ fn parse_account_ty(path: &syn::Path) -> ParseResult<AccountTy> {
|
|||
})
|
||||
}
|
||||
|
||||
fn parse_interface_account_ty(path: &syn::Path) -> ParseResult<InterfaceAccountTy> {
|
||||
let account_type_path = parse_account(path)?;
|
||||
let boxed = parser::tts_to_string(path)
|
||||
.replace(' ', "")
|
||||
.starts_with("Box<InterfaceAccount<");
|
||||
Ok(InterfaceAccountTy {
|
||||
account_type_path,
|
||||
boxed,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_program_ty(path: &syn::Path) -> ParseResult<ProgramTy> {
|
||||
let account_type_path = parse_account(path)?;
|
||||
Ok(ProgramTy { account_type_path })
|
||||
}
|
||||
|
||||
fn parse_interface_ty(path: &syn::Path) -> ParseResult<InterfaceTy> {
|
||||
let account_type_path = parse_account(path)?;
|
||||
Ok(InterfaceTy { account_type_path })
|
||||
}
|
||||
|
||||
// TODO: this whole method is a hack. Do something more idiomatic.
|
||||
fn parse_account(mut path: &syn::Path) -> ParseResult<syn::TypePath> {
|
||||
if parser::tts_to_string(path)
|
||||
.replace(' ', "")
|
||||
.starts_with("Box<Account<")
|
||||
{
|
||||
let path_str = parser::tts_to_string(path).replace(' ', "");
|
||||
if path_str.starts_with("Box<Account<") || path_str.starts_with("Box<InterfaceAccount<") {
|
||||
let segments = &path.segments[0];
|
||||
match &segments.arguments {
|
||||
syn::PathArguments::AngleBracketed(args) => {
|
||||
|
|
|
@ -8,9 +8,10 @@ license = "Apache-2.0"
|
|||
description = "CPI clients for SPL programs"
|
||||
|
||||
[features]
|
||||
default = ["mint", "token", "associated_token"]
|
||||
default = ["mint", "token", "token_2022", "associated_token"]
|
||||
mint = []
|
||||
token = ["spl-token"]
|
||||
token_2022 = ["spl-token-2022"]
|
||||
associated_token = ["spl-associated-token-account"]
|
||||
governance = []
|
||||
shmem = []
|
||||
|
@ -25,5 +26,6 @@ borsh = { version = "^0.9", optional = true }
|
|||
serum_dex = { git = "https://github.com/openbook-dex/program/", rev = "1be91f2", version = "0.4.0", features = ["no-entrypoint"], optional = true }
|
||||
solana-program = "1.13.5"
|
||||
spl-token = { version = "3.5.0", features = ["no-entrypoint"], optional = true }
|
||||
spl-token-2022 = { version = "0.5.0", features = ["no-entrypoint"], optional = true }
|
||||
spl-associated-token-account = { version = "1.1.1", features = ["no-entrypoint"], optional = true }
|
||||
mpl-token-metadata = { version = "^1.4.3", optional = true, features = ["no-entrypoint"] }
|
||||
|
|
|
@ -2,16 +2,17 @@ use anchor_lang::solana_program::account_info::AccountInfo;
|
|||
use anchor_lang::solana_program::pubkey::Pubkey;
|
||||
use anchor_lang::Result;
|
||||
use anchor_lang::{context::CpiContext, Accounts};
|
||||
use spl_token;
|
||||
|
||||
pub use spl_associated_token_account::{get_associated_token_address, ID};
|
||||
pub use spl_associated_token_account::{
|
||||
get_associated_token_address, get_associated_token_address_with_program_id, ID,
|
||||
};
|
||||
|
||||
pub fn create<'info>(ctx: CpiContext<'_, '_, '_, 'info, Create<'info>>) -> Result<()> {
|
||||
let ix = spl_associated_token_account::instruction::create_associated_token_account(
|
||||
ctx.accounts.payer.key,
|
||||
ctx.accounts.authority.key,
|
||||
ctx.accounts.mint.key,
|
||||
&spl_token::ID,
|
||||
ctx.accounts.token_program.key,
|
||||
);
|
||||
solana_program::program::invoke_signed(
|
||||
&ix,
|
||||
|
@ -35,7 +36,7 @@ pub fn create_idempotent<'info>(
|
|||
ctx.accounts.payer.key,
|
||||
ctx.accounts.authority.key,
|
||||
ctx.accounts.mint.key,
|
||||
&spl_token::ID,
|
||||
ctx.accounts.token_program.key,
|
||||
);
|
||||
solana_program::program::invoke_signed(
|
||||
&ix,
|
||||
|
|
|
@ -7,6 +7,12 @@ pub mod mint;
|
|||
#[cfg(feature = "token")]
|
||||
pub mod token;
|
||||
|
||||
#[cfg(feature = "token_2022")]
|
||||
pub mod token_2022;
|
||||
|
||||
#[cfg(feature = "token_2022")]
|
||||
pub mod token_interface;
|
||||
|
||||
#[cfg(feature = "dex")]
|
||||
pub mod dex;
|
||||
|
||||
|
|
|
@ -0,0 +1,547 @@
|
|||
use anchor_lang::solana_program::account_info::AccountInfo;
|
||||
|
||||
use anchor_lang::solana_program::pubkey::Pubkey;
|
||||
use anchor_lang::{context::CpiContext, Accounts};
|
||||
use anchor_lang::{solana_program, Result};
|
||||
|
||||
pub use spl_token_2022;
|
||||
pub use spl_token_2022::ID;
|
||||
|
||||
#[deprecated(
|
||||
since = "0.27.0",
|
||||
note = "please use `transfer_checked` or `transfer_checked_with_fee` instead"
|
||||
)]
|
||||
pub fn transfer<'info>(
|
||||
ctx: CpiContext<'_, '_, '_, 'info, Transfer<'info>>,
|
||||
amount: u64,
|
||||
) -> Result<()> {
|
||||
#[allow(deprecated)]
|
||||
let ix = spl_token_2022::instruction::transfer(
|
||||
ctx.program.key,
|
||||
ctx.accounts.from.key,
|
||||
ctx.accounts.to.key,
|
||||
ctx.accounts.authority.key,
|
||||
&[],
|
||||
amount,
|
||||
)?;
|
||||
solana_program::program::invoke_signed(
|
||||
&ix,
|
||||
&[
|
||||
ctx.accounts.from.clone(),
|
||||
ctx.accounts.to.clone(),
|
||||
ctx.accounts.authority.clone(),
|
||||
],
|
||||
ctx.signer_seeds,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn transfer_checked<'info>(
|
||||
ctx: CpiContext<'_, '_, '_, 'info, TransferChecked<'info>>,
|
||||
amount: u64,
|
||||
decimals: u8,
|
||||
) -> Result<()> {
|
||||
let ix = spl_token_2022::instruction::transfer_checked(
|
||||
ctx.program.key,
|
||||
ctx.accounts.from.key,
|
||||
ctx.accounts.mint.key,
|
||||
ctx.accounts.to.key,
|
||||
ctx.accounts.authority.key,
|
||||
&[],
|
||||
amount,
|
||||
decimals,
|
||||
)?;
|
||||
solana_program::program::invoke_signed(
|
||||
&ix,
|
||||
&[
|
||||
ctx.accounts.from.clone(),
|
||||
ctx.accounts.mint.clone(),
|
||||
ctx.accounts.to.clone(),
|
||||
ctx.accounts.authority.clone(),
|
||||
],
|
||||
ctx.signer_seeds,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn mint_to<'info>(
|
||||
ctx: CpiContext<'_, '_, '_, 'info, MintTo<'info>>,
|
||||
amount: u64,
|
||||
) -> Result<()> {
|
||||
let ix = spl_token_2022::instruction::mint_to(
|
||||
ctx.program.key,
|
||||
ctx.accounts.mint.key,
|
||||
ctx.accounts.to.key,
|
||||
ctx.accounts.authority.key,
|
||||
&[],
|
||||
amount,
|
||||
)?;
|
||||
solana_program::program::invoke_signed(
|
||||
&ix,
|
||||
&[
|
||||
ctx.accounts.to.clone(),
|
||||
ctx.accounts.mint.clone(),
|
||||
ctx.accounts.authority.clone(),
|
||||
],
|
||||
ctx.signer_seeds,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn burn<'info>(ctx: CpiContext<'_, '_, '_, 'info, Burn<'info>>, amount: u64) -> Result<()> {
|
||||
let ix = spl_token_2022::instruction::burn(
|
||||
ctx.program.key,
|
||||
ctx.accounts.from.key,
|
||||
ctx.accounts.mint.key,
|
||||
ctx.accounts.authority.key,
|
||||
&[],
|
||||
amount,
|
||||
)?;
|
||||
solana_program::program::invoke_signed(
|
||||
&ix,
|
||||
&[
|
||||
ctx.accounts.from.clone(),
|
||||
ctx.accounts.mint.clone(),
|
||||
ctx.accounts.authority.clone(),
|
||||
],
|
||||
ctx.signer_seeds,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn approve<'info>(
|
||||
ctx: CpiContext<'_, '_, '_, 'info, Approve<'info>>,
|
||||
amount: u64,
|
||||
) -> Result<()> {
|
||||
let ix = spl_token_2022::instruction::approve(
|
||||
ctx.program.key,
|
||||
ctx.accounts.to.key,
|
||||
ctx.accounts.delegate.key,
|
||||
ctx.accounts.authority.key,
|
||||
&[],
|
||||
amount,
|
||||
)?;
|
||||
solana_program::program::invoke_signed(
|
||||
&ix,
|
||||
&[
|
||||
ctx.accounts.to.clone(),
|
||||
ctx.accounts.delegate.clone(),
|
||||
ctx.accounts.authority.clone(),
|
||||
],
|
||||
ctx.signer_seeds,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn revoke<'info>(ctx: CpiContext<'_, '_, '_, 'info, Revoke<'info>>) -> Result<()> {
|
||||
let ix = spl_token_2022::instruction::revoke(
|
||||
ctx.program.key,
|
||||
ctx.accounts.source.key,
|
||||
ctx.accounts.authority.key,
|
||||
&[],
|
||||
)?;
|
||||
solana_program::program::invoke_signed(
|
||||
&ix,
|
||||
&[ctx.accounts.source.clone(), ctx.accounts.authority.clone()],
|
||||
ctx.signer_seeds,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn initialize_account<'info>(
|
||||
ctx: CpiContext<'_, '_, '_, 'info, InitializeAccount<'info>>,
|
||||
) -> Result<()> {
|
||||
let ix = spl_token_2022::instruction::initialize_account(
|
||||
ctx.program.key,
|
||||
ctx.accounts.account.key,
|
||||
ctx.accounts.mint.key,
|
||||
ctx.accounts.authority.key,
|
||||
)?;
|
||||
solana_program::program::invoke(
|
||||
&ix,
|
||||
&[
|
||||
ctx.accounts.account.clone(),
|
||||
ctx.accounts.mint.clone(),
|
||||
ctx.accounts.authority.clone(),
|
||||
ctx.accounts.rent.clone(),
|
||||
],
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn initialize_account3<'info>(
|
||||
ctx: CpiContext<'_, '_, '_, 'info, InitializeAccount3<'info>>,
|
||||
) -> Result<()> {
|
||||
let ix = spl_token_2022::instruction::initialize_account3(
|
||||
ctx.program.key,
|
||||
ctx.accounts.account.key,
|
||||
ctx.accounts.mint.key,
|
||||
ctx.accounts.authority.key,
|
||||
)?;
|
||||
solana_program::program::invoke(
|
||||
&ix,
|
||||
&[ctx.accounts.account.clone(), ctx.accounts.mint.clone()],
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn close_account<'info>(ctx: CpiContext<'_, '_, '_, 'info, CloseAccount<'info>>) -> Result<()> {
|
||||
let ix = spl_token_2022::instruction::close_account(
|
||||
ctx.program.key,
|
||||
ctx.accounts.account.key,
|
||||
ctx.accounts.destination.key,
|
||||
ctx.accounts.authority.key,
|
||||
&[], // TODO: support multisig
|
||||
)?;
|
||||
solana_program::program::invoke_signed(
|
||||
&ix,
|
||||
&[
|
||||
ctx.accounts.account.clone(),
|
||||
ctx.accounts.destination.clone(),
|
||||
ctx.accounts.authority.clone(),
|
||||
],
|
||||
ctx.signer_seeds,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn freeze_account<'info>(
|
||||
ctx: CpiContext<'_, '_, '_, 'info, FreezeAccount<'info>>,
|
||||
) -> Result<()> {
|
||||
let ix = spl_token_2022::instruction::freeze_account(
|
||||
ctx.program.key,
|
||||
ctx.accounts.account.key,
|
||||
ctx.accounts.mint.key,
|
||||
ctx.accounts.authority.key,
|
||||
&[], // TODO: Support multisig signers.
|
||||
)?;
|
||||
solana_program::program::invoke_signed(
|
||||
&ix,
|
||||
&[
|
||||
ctx.accounts.account.clone(),
|
||||
ctx.accounts.mint.clone(),
|
||||
ctx.accounts.authority.clone(),
|
||||
],
|
||||
ctx.signer_seeds,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn thaw_account<'info>(ctx: CpiContext<'_, '_, '_, 'info, ThawAccount<'info>>) -> Result<()> {
|
||||
let ix = spl_token_2022::instruction::thaw_account(
|
||||
ctx.program.key,
|
||||
ctx.accounts.account.key,
|
||||
ctx.accounts.mint.key,
|
||||
ctx.accounts.authority.key,
|
||||
&[], // TODO: Support multisig signers.
|
||||
)?;
|
||||
solana_program::program::invoke_signed(
|
||||
&ix,
|
||||
&[
|
||||
ctx.accounts.account.clone(),
|
||||
ctx.accounts.mint.clone(),
|
||||
ctx.accounts.authority.clone(),
|
||||
],
|
||||
ctx.signer_seeds,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn initialize_mint<'info>(
|
||||
ctx: CpiContext<'_, '_, '_, 'info, InitializeMint<'info>>,
|
||||
decimals: u8,
|
||||
authority: &Pubkey,
|
||||
freeze_authority: Option<&Pubkey>,
|
||||
) -> Result<()> {
|
||||
let ix = spl_token_2022::instruction::initialize_mint(
|
||||
ctx.program.key,
|
||||
ctx.accounts.mint.key,
|
||||
authority,
|
||||
freeze_authority,
|
||||
decimals,
|
||||
)?;
|
||||
solana_program::program::invoke(&ix, &[ctx.accounts.mint.clone(), ctx.accounts.rent.clone()])
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn initialize_mint2<'info>(
|
||||
ctx: CpiContext<'_, '_, '_, 'info, InitializeMint2<'info>>,
|
||||
decimals: u8,
|
||||
authority: &Pubkey,
|
||||
freeze_authority: Option<&Pubkey>,
|
||||
) -> Result<()> {
|
||||
let ix = spl_token_2022::instruction::initialize_mint2(
|
||||
ctx.program.key,
|
||||
ctx.accounts.mint.key,
|
||||
authority,
|
||||
freeze_authority,
|
||||
decimals,
|
||||
)?;
|
||||
solana_program::program::invoke(&ix, &[ctx.accounts.mint.clone()]).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn set_authority<'info>(
|
||||
ctx: CpiContext<'_, '_, '_, 'info, SetAuthority<'info>>,
|
||||
authority_type: spl_token_2022::instruction::AuthorityType,
|
||||
new_authority: Option<Pubkey>,
|
||||
) -> Result<()> {
|
||||
let mut spl_new_authority: Option<&Pubkey> = None;
|
||||
if new_authority.is_some() {
|
||||
spl_new_authority = new_authority.as_ref()
|
||||
}
|
||||
|
||||
let ix = spl_token_2022::instruction::set_authority(
|
||||
ctx.program.key,
|
||||
ctx.accounts.account_or_mint.key,
|
||||
spl_new_authority,
|
||||
authority_type,
|
||||
ctx.accounts.current_authority.key,
|
||||
&[], // TODO: Support multisig signers.
|
||||
)?;
|
||||
solana_program::program::invoke_signed(
|
||||
&ix,
|
||||
&[
|
||||
ctx.accounts.account_or_mint.clone(),
|
||||
ctx.accounts.current_authority.clone(),
|
||||
],
|
||||
ctx.signer_seeds,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn sync_native<'info>(ctx: CpiContext<'_, '_, '_, 'info, SyncNative<'info>>) -> Result<()> {
|
||||
let ix = spl_token_2022::instruction::sync_native(ctx.program.key, ctx.accounts.account.key)?;
|
||||
solana_program::program::invoke(&ix, &[ctx.accounts.account.clone()]).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn get_account_data_size<'info>(
|
||||
ctx: CpiContext<'_, '_, '_, 'info, GetAccountDataSize<'info>>,
|
||||
extension_types: &[spl_token_2022::extension::ExtensionType],
|
||||
) -> Result<u64> {
|
||||
let ix = spl_token_2022::instruction::get_account_data_size(
|
||||
ctx.program.key,
|
||||
ctx.accounts.mint.key,
|
||||
extension_types,
|
||||
)?;
|
||||
solana_program::program::invoke(&ix, &[ctx.accounts.mint.clone()])?;
|
||||
solana_program::program::get_return_data()
|
||||
.ok_or(solana_program::program_error::ProgramError::InvalidInstructionData)
|
||||
.and_then(|(key, data)| {
|
||||
if key != *ctx.program.key {
|
||||
Err(solana_program::program_error::ProgramError::IncorrectProgramId)
|
||||
} else {
|
||||
data.try_into().map(u64::from_le_bytes).map_err(|_| {
|
||||
solana_program::program_error::ProgramError::InvalidInstructionData
|
||||
})
|
||||
}
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn initialize_mint_close_authority<'info>(
|
||||
ctx: CpiContext<'_, '_, '_, 'info, InitializeMintCloseAuthority<'info>>,
|
||||
close_authority: Option<&Pubkey>,
|
||||
) -> Result<()> {
|
||||
let ix = spl_token_2022::instruction::initialize_mint_close_authority(
|
||||
ctx.program.key,
|
||||
ctx.accounts.mint.key,
|
||||
close_authority,
|
||||
)?;
|
||||
solana_program::program::invoke(&ix, &[ctx.accounts.mint.clone()]).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn initialize_immutable_owner<'info>(
|
||||
ctx: CpiContext<'_, '_, '_, 'info, InitializeImmutableOwner<'info>>,
|
||||
) -> Result<()> {
|
||||
let ix = spl_token_2022::instruction::initialize_immutable_owner(
|
||||
ctx.program.key,
|
||||
ctx.accounts.account.key,
|
||||
)?;
|
||||
solana_program::program::invoke(&ix, &[ctx.accounts.account.clone()]).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn amount_to_ui_amount<'info>(
|
||||
ctx: CpiContext<'_, '_, '_, 'info, AmountToUiAmount<'info>>,
|
||||
amount: u64,
|
||||
) -> Result<String> {
|
||||
let ix = spl_token_2022::instruction::amount_to_ui_amount(
|
||||
ctx.program.key,
|
||||
ctx.accounts.account.key,
|
||||
amount,
|
||||
)?;
|
||||
solana_program::program::invoke(&ix, &[ctx.accounts.account.clone()])?;
|
||||
solana_program::program::get_return_data()
|
||||
.ok_or(solana_program::program_error::ProgramError::InvalidInstructionData)
|
||||
.and_then(|(key, data)| {
|
||||
if key != *ctx.program.key {
|
||||
Err(solana_program::program_error::ProgramError::IncorrectProgramId)
|
||||
} else {
|
||||
String::from_utf8(data).map_err(|_| {
|
||||
solana_program::program_error::ProgramError::InvalidInstructionData
|
||||
})
|
||||
}
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn ui_amount_to_amount<'info>(
|
||||
ctx: CpiContext<'_, '_, '_, 'info, UiAmountToAmount<'info>>,
|
||||
ui_amount: &str,
|
||||
) -> Result<u64> {
|
||||
let ix = spl_token_2022::instruction::ui_amount_to_amount(
|
||||
ctx.program.key,
|
||||
ctx.accounts.account.key,
|
||||
ui_amount,
|
||||
)?;
|
||||
solana_program::program::invoke(&ix, &[ctx.accounts.account.clone()])?;
|
||||
solana_program::program::get_return_data()
|
||||
.ok_or(solana_program::program_error::ProgramError::InvalidInstructionData)
|
||||
.and_then(|(key, data)| {
|
||||
if key != *ctx.program.key {
|
||||
Err(solana_program::program_error::ProgramError::IncorrectProgramId)
|
||||
} else {
|
||||
data.try_into().map(u64::from_le_bytes).map_err(|_| {
|
||||
solana_program::program_error::ProgramError::InvalidInstructionData
|
||||
})
|
||||
}
|
||||
})
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Transfer<'info> {
|
||||
pub from: AccountInfo<'info>,
|
||||
pub to: AccountInfo<'info>,
|
||||
pub authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TransferChecked<'info> {
|
||||
pub from: AccountInfo<'info>,
|
||||
pub mint: AccountInfo<'info>,
|
||||
pub to: AccountInfo<'info>,
|
||||
pub authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct MintTo<'info> {
|
||||
pub mint: AccountInfo<'info>,
|
||||
pub to: AccountInfo<'info>,
|
||||
pub authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Burn<'info> {
|
||||
pub mint: AccountInfo<'info>,
|
||||
pub from: AccountInfo<'info>,
|
||||
pub authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Approve<'info> {
|
||||
pub to: AccountInfo<'info>,
|
||||
pub delegate: AccountInfo<'info>,
|
||||
pub authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Revoke<'info> {
|
||||
pub source: AccountInfo<'info>,
|
||||
pub authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct InitializeAccount<'info> {
|
||||
pub account: AccountInfo<'info>,
|
||||
pub mint: AccountInfo<'info>,
|
||||
pub authority: AccountInfo<'info>,
|
||||
pub rent: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct InitializeAccount3<'info> {
|
||||
pub account: AccountInfo<'info>,
|
||||
pub mint: AccountInfo<'info>,
|
||||
pub authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct CloseAccount<'info> {
|
||||
pub account: AccountInfo<'info>,
|
||||
pub destination: AccountInfo<'info>,
|
||||
pub authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct FreezeAccount<'info> {
|
||||
pub account: AccountInfo<'info>,
|
||||
pub mint: AccountInfo<'info>,
|
||||
pub authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct ThawAccount<'info> {
|
||||
pub account: AccountInfo<'info>,
|
||||
pub mint: AccountInfo<'info>,
|
||||
pub authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct InitializeMint<'info> {
|
||||
pub mint: AccountInfo<'info>,
|
||||
pub rent: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct InitializeMint2<'info> {
|
||||
pub mint: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct SetAuthority<'info> {
|
||||
pub current_authority: AccountInfo<'info>,
|
||||
pub account_or_mint: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct SyncNative<'info> {
|
||||
pub account: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct GetAccountDataSize<'info> {
|
||||
pub mint: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct InitializeMintCloseAuthority<'info> {
|
||||
pub mint: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct InitializeImmutableOwner<'info> {
|
||||
pub account: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct AmountToUiAmount<'info> {
|
||||
pub account: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct UiAmountToAmount<'info> {
|
||||
pub account: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Token2022;
|
||||
|
||||
impl anchor_lang::Id for Token2022 {
|
||||
fn id() -> Pubkey {
|
||||
ID
|
||||
}
|
||||
}
|
||||
|
||||
// Field parsers to save compute. All account validation is assumed to be done
|
||||
// outside of these methods.
|
||||
pub use crate::token::accessor;
|
|
@ -0,0 +1,71 @@
|
|||
use anchor_lang::solana_program::pubkey::Pubkey;
|
||||
use std::ops::Deref;
|
||||
|
||||
static IDS: [Pubkey; 2] = [spl_token::ID, spl_token_2022::ID];
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct TokenAccount(spl_token_2022::state::Account);
|
||||
|
||||
impl anchor_lang::AccountDeserialize for TokenAccount {
|
||||
fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
|
||||
spl_token_2022::extension::StateWithExtensions::<spl_token_2022::state::Account>::unpack(
|
||||
buf,
|
||||
)
|
||||
.map(|t| TokenAccount(t.base))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl anchor_lang::AccountSerialize for TokenAccount {}
|
||||
|
||||
impl anchor_lang::Owners for TokenAccount {
|
||||
fn owners() -> &'static [Pubkey] {
|
||||
&IDS
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for TokenAccount {
|
||||
type Target = spl_token_2022::state::Account;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct Mint(spl_token_2022::state::Mint);
|
||||
|
||||
impl anchor_lang::AccountDeserialize for Mint {
|
||||
fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
|
||||
spl_token_2022::extension::StateWithExtensions::<spl_token_2022::state::Mint>::unpack(buf)
|
||||
.map(|t| Mint(t.base))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl anchor_lang::AccountSerialize for Mint {}
|
||||
|
||||
impl anchor_lang::Owners for Mint {
|
||||
fn owners() -> &'static [Pubkey] {
|
||||
&IDS
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Mint {
|
||||
type Target = spl_token_2022::state::Mint;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TokenInterface;
|
||||
|
||||
impl anchor_lang::Ids for TokenInterface {
|
||||
fn ids() -> &'static [Pubkey] {
|
||||
&IDS
|
||||
}
|
||||
}
|
||||
|
||||
pub use crate::token_2022::*;
|
|
@ -9,3 +9,9 @@ escrow = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
|
|||
test = "yarn run ts-mocha -t 1000000 tests/*.ts"
|
||||
|
||||
[features]
|
||||
|
||||
[test.validator]
|
||||
url = "https://api.mainnet-beta.solana.com"
|
||||
|
||||
[[test.validator.clone]]
|
||||
address = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
|
||||
|
|
|
@ -18,4 +18,4 @@ default = []
|
|||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
anchor-spl = { path = "../../../../spl" }
|
||||
spl-token = { version = "3.1.1", features = ["no-entrypoint"] }
|
||||
spl-token-2022 = { version = "0.5.0", features = ["no-entrypoint"] }
|
||||
|
|
|
@ -16,8 +16,10 @@
|
|||
//! - Initializer will get back ownership of their token X account
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::token::{self, SetAuthority, Token, TokenAccount, Transfer};
|
||||
use spl_token::instruction::AuthorityType;
|
||||
use anchor_spl::token_interface::{
|
||||
self, Mint, SetAuthority, TokenAccount, TokenInterface, TransferChecked,
|
||||
};
|
||||
use spl_token_2022::instruction::AuthorityType;
|
||||
|
||||
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
|
||||
|
||||
|
@ -51,7 +53,11 @@ pub mod escrow {
|
|||
ctx.accounts.escrow_account.taker_amount = taker_amount;
|
||||
|
||||
let (pda, _bump_seed) = Pubkey::find_program_address(&[ESCROW_PDA_SEED], ctx.program_id);
|
||||
token::set_authority(ctx.accounts.into(), AuthorityType::AccountOwner, Some(pda))?;
|
||||
token_interface::set_authority(
|
||||
ctx.accounts.into(),
|
||||
AuthorityType::AccountOwner,
|
||||
Some(pda),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -59,7 +65,7 @@ pub mod escrow {
|
|||
let (_pda, bump_seed) = Pubkey::find_program_address(&[ESCROW_PDA_SEED], ctx.program_id);
|
||||
let seeds = &[&ESCROW_PDA_SEED[..], &[bump_seed]];
|
||||
|
||||
token::set_authority(
|
||||
token_interface::set_authority(
|
||||
ctx.accounts
|
||||
.into_set_authority_context()
|
||||
.with_signer(&[&seeds[..]]),
|
||||
|
@ -75,19 +81,21 @@ pub mod escrow {
|
|||
let (_pda, bump_seed) = Pubkey::find_program_address(&[ESCROW_PDA_SEED], ctx.program_id);
|
||||
let seeds = &[&ESCROW_PDA_SEED[..], &[bump_seed]];
|
||||
|
||||
token::transfer(
|
||||
token_interface::transfer_checked(
|
||||
ctx.accounts
|
||||
.into_transfer_to_taker_context()
|
||||
.with_signer(&[&seeds[..]]),
|
||||
ctx.accounts.escrow_account.initializer_amount,
|
||||
ctx.accounts.receive_mint.decimals,
|
||||
)?;
|
||||
|
||||
token::transfer(
|
||||
token_interface::transfer_checked(
|
||||
ctx.accounts.into_transfer_to_initializer_context(),
|
||||
ctx.accounts.escrow_account.taker_amount,
|
||||
ctx.accounts.deposit_mint.decimals,
|
||||
)?;
|
||||
|
||||
token::set_authority(
|
||||
token_interface::set_authority(
|
||||
ctx.accounts
|
||||
.into_set_authority_context()
|
||||
.with_signer(&[&seeds[..]]),
|
||||
|
@ -108,27 +116,29 @@ pub struct InitializeEscrow<'info> {
|
|||
mut,
|
||||
constraint = initializer_deposit_token_account.amount >= initializer_amount
|
||||
)]
|
||||
pub initializer_deposit_token_account: Account<'info, TokenAccount>,
|
||||
pub initializer_receive_token_account: Account<'info, TokenAccount>,
|
||||
pub initializer_deposit_token_account: InterfaceAccount<'info, TokenAccount>,
|
||||
pub initializer_receive_token_account: InterfaceAccount<'info, TokenAccount>,
|
||||
#[account(init, payer = initializer, space = 8 + EscrowAccount::LEN)]
|
||||
pub escrow_account: Account<'info, EscrowAccount>,
|
||||
pub system_program: Program<'info, System>,
|
||||
pub token_program: Program<'info, Token>,
|
||||
pub token_program: Interface<'info, TokenInterface>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Exchange<'info> {
|
||||
#[account(signer)]
|
||||
/// CHECK:
|
||||
pub taker: AccountInfo<'info>,
|
||||
#[account(mut, token::mint = deposit_mint)]
|
||||
pub taker_deposit_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
|
||||
#[account(mut, token::mint = receive_mint)]
|
||||
pub taker_receive_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
|
||||
#[account(mut, token::mint = receive_mint)]
|
||||
pub pda_deposit_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
|
||||
#[account(mut, token::mint = deposit_mint)]
|
||||
pub initializer_receive_token_account: Box<InterfaceAccount<'info, TokenAccount>>,
|
||||
#[account(mut)]
|
||||
pub taker_deposit_token_account: Account<'info, TokenAccount>,
|
||||
#[account(mut)]
|
||||
pub taker_receive_token_account: Account<'info, TokenAccount>,
|
||||
#[account(mut)]
|
||||
pub pda_deposit_token_account: Account<'info, TokenAccount>,
|
||||
#[account(mut)]
|
||||
pub initializer_receive_token_account: Account<'info, TokenAccount>,
|
||||
#[account(mut)]
|
||||
/// CHECK:
|
||||
pub initializer_main_account: AccountInfo<'info>,
|
||||
#[account(
|
||||
mut,
|
||||
|
@ -139,15 +149,21 @@ pub struct Exchange<'info> {
|
|||
close = initializer_main_account
|
||||
)]
|
||||
pub escrow_account: Account<'info, EscrowAccount>,
|
||||
/// CHECK:
|
||||
pub pda_account: AccountInfo<'info>,
|
||||
pub token_program: Program<'info, Token>,
|
||||
pub deposit_mint: Box<InterfaceAccount<'info, Mint>>,
|
||||
pub receive_mint: Box<InterfaceAccount<'info, Mint>>,
|
||||
pub deposit_token_program: Interface<'info, TokenInterface>,
|
||||
pub receive_token_program: Interface<'info, TokenInterface>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct CancelEscrow<'info> {
|
||||
/// CHECK:
|
||||
pub initializer: AccountInfo<'info>,
|
||||
#[account(mut)]
|
||||
pub pda_deposit_token_account: Account<'info, TokenAccount>,
|
||||
pub pda_deposit_token_account: InterfaceAccount<'info, TokenAccount>,
|
||||
/// CHECK:
|
||||
pub pda_account: AccountInfo<'info>,
|
||||
#[account(
|
||||
mut,
|
||||
|
@ -156,7 +172,7 @@ pub struct CancelEscrow<'info> {
|
|||
close = initializer
|
||||
)]
|
||||
pub escrow_account: Account<'info, EscrowAccount>,
|
||||
pub token_program: Program<'info, Token>,
|
||||
pub token_program: Interface<'info, TokenInterface>,
|
||||
}
|
||||
|
||||
#[account]
|
||||
|
@ -205,19 +221,22 @@ impl<'info> Exchange<'info> {
|
|||
account_or_mint: self.pda_deposit_token_account.to_account_info().clone(),
|
||||
current_authority: self.pda_account.clone(),
|
||||
};
|
||||
let cpi_program = self.token_program.to_account_info();
|
||||
let cpi_program = self.receive_token_program.to_account_info();
|
||||
CpiContext::new(cpi_program, cpi_accounts)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info> Exchange<'info> {
|
||||
fn into_transfer_to_taker_context(&self) -> CpiContext<'_, '_, '_, 'info, Transfer<'info>> {
|
||||
let cpi_accounts = Transfer {
|
||||
fn into_transfer_to_taker_context(
|
||||
&self,
|
||||
) -> CpiContext<'_, '_, '_, 'info, TransferChecked<'info>> {
|
||||
let cpi_accounts = TransferChecked {
|
||||
from: self.pda_deposit_token_account.to_account_info().clone(),
|
||||
mint: self.receive_mint.to_account_info().clone(),
|
||||
to: self.taker_receive_token_account.to_account_info().clone(),
|
||||
authority: self.pda_account.clone(),
|
||||
};
|
||||
let cpi_program = self.token_program.to_account_info();
|
||||
let cpi_program = self.receive_token_program.to_account_info();
|
||||
CpiContext::new(cpi_program, cpi_accounts)
|
||||
}
|
||||
}
|
||||
|
@ -225,16 +244,17 @@ impl<'info> Exchange<'info> {
|
|||
impl<'info> Exchange<'info> {
|
||||
fn into_transfer_to_initializer_context(
|
||||
&self,
|
||||
) -> CpiContext<'_, '_, '_, 'info, Transfer<'info>> {
|
||||
let cpi_accounts = Transfer {
|
||||
) -> CpiContext<'_, '_, '_, 'info, TransferChecked<'info>> {
|
||||
let cpi_accounts = TransferChecked {
|
||||
from: self.taker_deposit_token_account.to_account_info().clone(),
|
||||
mint: self.deposit_mint.to_account_info().clone(),
|
||||
to: self
|
||||
.initializer_receive_token_account
|
||||
.to_account_info()
|
||||
.clone(),
|
||||
authority: self.taker.clone(),
|
||||
};
|
||||
let cpi_program = self.token_program.to_account_info();
|
||||
let cpi_program = self.deposit_token_program.to_account_info();
|
||||
CpiContext::new(cpi_program, cpi_accounts)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,14 @@ describe("escrow", () => {
|
|||
const provider = anchor.AnchorProvider.env();
|
||||
anchor.setProvider(provider);
|
||||
|
||||
const TOKEN_2022_PROGRAM_ID = new anchor.web3.PublicKey(
|
||||
"TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
|
||||
);
|
||||
const TEST_PROGRAM_IDS = [
|
||||
[TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID],
|
||||
[TOKEN_2022_PROGRAM_ID, TOKEN_2022_PROGRAM_ID],
|
||||
[TOKEN_PROGRAM_ID, TOKEN_2022_PROGRAM_ID],
|
||||
];
|
||||
const program = anchor.workspace.Escrow as Program<Escrow>;
|
||||
|
||||
let mintA: Token = null;
|
||||
|
@ -24,223 +32,252 @@ describe("escrow", () => {
|
|||
const takerAmount = 1000;
|
||||
const initializerAmount = 500;
|
||||
|
||||
const escrowAccount = Keypair.generate();
|
||||
const payer = Keypair.generate();
|
||||
const mintAuthority = Keypair.generate();
|
||||
|
||||
it("Initialise escrow state", async () => {
|
||||
// Airdropping tokens to a payer.
|
||||
await provider.connection.confirmTransaction(
|
||||
await provider.connection.requestAirdrop(payer.publicKey, 10000000000),
|
||||
"confirmed"
|
||||
);
|
||||
TEST_PROGRAM_IDS.forEach((tokenProgramIds) => {
|
||||
const escrowAccount = Keypair.generate();
|
||||
const [tokenProgramIdA, tokenProgramIdB] = tokenProgramIds;
|
||||
let name;
|
||||
if (tokenProgramIdA === tokenProgramIdB) {
|
||||
name = tokenProgramIdA === TOKEN_PROGRAM_ID ? "token" : "token-2022";
|
||||
} else {
|
||||
name = "mixed";
|
||||
}
|
||||
describe(name, () => {
|
||||
it("Initialise escrow state", async () => {
|
||||
// Airdropping tokens to a payer.
|
||||
await provider.connection.confirmTransaction(
|
||||
await provider.connection.requestAirdrop(
|
||||
payer.publicKey,
|
||||
10000000000
|
||||
),
|
||||
"confirmed"
|
||||
);
|
||||
|
||||
mintA = await Token.createMint(
|
||||
provider.connection,
|
||||
payer,
|
||||
mintAuthority.publicKey,
|
||||
null,
|
||||
0,
|
||||
TOKEN_PROGRAM_ID
|
||||
);
|
||||
mintA = await Token.createMint(
|
||||
provider.connection,
|
||||
payer,
|
||||
mintAuthority.publicKey,
|
||||
null,
|
||||
0,
|
||||
tokenProgramIdA
|
||||
);
|
||||
|
||||
mintB = await Token.createMint(
|
||||
provider.connection,
|
||||
payer,
|
||||
mintAuthority.publicKey,
|
||||
null,
|
||||
0,
|
||||
TOKEN_PROGRAM_ID
|
||||
);
|
||||
mintB = await Token.createMint(
|
||||
provider.connection,
|
||||
payer,
|
||||
mintAuthority.publicKey,
|
||||
null,
|
||||
0,
|
||||
tokenProgramIdB
|
||||
);
|
||||
|
||||
initializerTokenAccountA = await mintA.createAccount(
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
takerTokenAccountA = await mintA.createAccount(provider.wallet.publicKey);
|
||||
initializerTokenAccountA = await mintA.createAccount(
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
takerTokenAccountA = await mintA.createAccount(
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
|
||||
initializerTokenAccountB = await mintB.createAccount(
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
takerTokenAccountB = await mintB.createAccount(provider.wallet.publicKey);
|
||||
initializerTokenAccountB = await mintB.createAccount(
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
takerTokenAccountB = await mintB.createAccount(
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
|
||||
await mintA.mintTo(
|
||||
initializerTokenAccountA,
|
||||
mintAuthority.publicKey,
|
||||
[mintAuthority],
|
||||
initializerAmount
|
||||
);
|
||||
await mintA.mintTo(
|
||||
initializerTokenAccountA,
|
||||
mintAuthority.publicKey,
|
||||
[mintAuthority],
|
||||
initializerAmount
|
||||
);
|
||||
|
||||
await mintB.mintTo(
|
||||
takerTokenAccountB,
|
||||
mintAuthority.publicKey,
|
||||
[mintAuthority],
|
||||
takerAmount
|
||||
);
|
||||
await mintB.mintTo(
|
||||
takerTokenAccountB,
|
||||
mintAuthority.publicKey,
|
||||
[mintAuthority],
|
||||
takerAmount
|
||||
);
|
||||
|
||||
let _initializerTokenAccountA = await mintA.getAccountInfo(
|
||||
initializerTokenAccountA
|
||||
);
|
||||
let _takerTokenAccountB = await mintB.getAccountInfo(takerTokenAccountB);
|
||||
let _initializerTokenAccountA = await mintA.getAccountInfo(
|
||||
initializerTokenAccountA
|
||||
);
|
||||
let _takerTokenAccountB = await mintB.getAccountInfo(
|
||||
takerTokenAccountB
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
_initializerTokenAccountA.amount.toNumber(),
|
||||
initializerAmount
|
||||
);
|
||||
assert.strictEqual(_takerTokenAccountB.amount.toNumber(), takerAmount);
|
||||
});
|
||||
assert.strictEqual(
|
||||
_initializerTokenAccountA.amount.toNumber(),
|
||||
initializerAmount
|
||||
);
|
||||
assert.strictEqual(_takerTokenAccountB.amount.toNumber(), takerAmount);
|
||||
});
|
||||
|
||||
it("Initialize escrow", async () => {
|
||||
await program.rpc.initializeEscrow(
|
||||
new BN(initializerAmount),
|
||||
new BN(takerAmount),
|
||||
{
|
||||
accounts: {
|
||||
initializer: provider.wallet.publicKey,
|
||||
initializerDepositTokenAccount: initializerTokenAccountA,
|
||||
initializerReceiveTokenAccount: initializerTokenAccountB,
|
||||
escrowAccount: escrowAccount.publicKey,
|
||||
systemProgram: SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [escrowAccount],
|
||||
}
|
||||
);
|
||||
it("Initialize escrow", async () => {
|
||||
await program.rpc.initializeEscrow(
|
||||
new BN(initializerAmount),
|
||||
new BN(takerAmount),
|
||||
{
|
||||
accounts: {
|
||||
initializer: provider.wallet.publicKey,
|
||||
initializerDepositTokenAccount: initializerTokenAccountA,
|
||||
initializerReceiveTokenAccount: initializerTokenAccountB,
|
||||
escrowAccount: escrowAccount.publicKey,
|
||||
systemProgram: SystemProgram.programId,
|
||||
tokenProgram: tokenProgramIdA,
|
||||
},
|
||||
signers: [escrowAccount],
|
||||
}
|
||||
);
|
||||
|
||||
// Get the PDA that is assigned authority to token account.
|
||||
const [_pda, _nonce] = await PublicKey.findProgramAddress(
|
||||
[Buffer.from(anchor.utils.bytes.utf8.encode("escrow"))],
|
||||
program.programId
|
||||
);
|
||||
// Get the PDA that is assigned authority to token account.
|
||||
const [_pda, _nonce] = await PublicKey.findProgramAddress(
|
||||
[Buffer.from(anchor.utils.bytes.utf8.encode("escrow"))],
|
||||
program.programId
|
||||
);
|
||||
|
||||
pda = _pda;
|
||||
pda = _pda;
|
||||
|
||||
let _initializerTokenAccountA = await mintA.getAccountInfo(
|
||||
initializerTokenAccountA
|
||||
);
|
||||
let _initializerTokenAccountA = await mintA.getAccountInfo(
|
||||
initializerTokenAccountA
|
||||
);
|
||||
|
||||
let _escrowAccount: EscrowAccount =
|
||||
await program.account.escrowAccount.fetch(escrowAccount.publicKey);
|
||||
let _escrowAccount: EscrowAccount =
|
||||
await program.account.escrowAccount.fetch(escrowAccount.publicKey);
|
||||
|
||||
// Check that the new owner is the PDA.
|
||||
assert.isTrue(_initializerTokenAccountA.owner.equals(pda));
|
||||
// Check that the new owner is the PDA.
|
||||
assert.isTrue(_initializerTokenAccountA.owner.equals(pda));
|
||||
|
||||
// Check that the values in the escrow account match what we expect.
|
||||
assert.isTrue(
|
||||
_escrowAccount.initializerKey.equals(provider.wallet.publicKey)
|
||||
);
|
||||
assert.strictEqual(
|
||||
_escrowAccount.initializerAmount.toNumber(),
|
||||
initializerAmount
|
||||
);
|
||||
assert.strictEqual(_escrowAccount.takerAmount.toNumber(), takerAmount);
|
||||
assert.isTrue(
|
||||
_escrowAccount.initializerDepositTokenAccount.equals(
|
||||
initializerTokenAccountA
|
||||
)
|
||||
);
|
||||
assert.isTrue(
|
||||
_escrowAccount.initializerReceiveTokenAccount.equals(
|
||||
initializerTokenAccountB
|
||||
)
|
||||
);
|
||||
});
|
||||
// Check that the values in the escrow account match what we expect.
|
||||
assert.isTrue(
|
||||
_escrowAccount.initializerKey.equals(provider.wallet.publicKey)
|
||||
);
|
||||
assert.strictEqual(
|
||||
_escrowAccount.initializerAmount.toNumber(),
|
||||
initializerAmount
|
||||
);
|
||||
assert.strictEqual(_escrowAccount.takerAmount.toNumber(), takerAmount);
|
||||
assert.isTrue(
|
||||
_escrowAccount.initializerDepositTokenAccount.equals(
|
||||
initializerTokenAccountA
|
||||
)
|
||||
);
|
||||
assert.isTrue(
|
||||
_escrowAccount.initializerReceiveTokenAccount.equals(
|
||||
initializerTokenAccountB
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("Exchange escrow", async () => {
|
||||
await program.rpc.exchange({
|
||||
accounts: {
|
||||
taker: provider.wallet.publicKey,
|
||||
takerDepositTokenAccount: takerTokenAccountB,
|
||||
takerReceiveTokenAccount: takerTokenAccountA,
|
||||
pdaDepositTokenAccount: initializerTokenAccountA,
|
||||
initializerReceiveTokenAccount: initializerTokenAccountB,
|
||||
initializerMainAccount: provider.wallet.publicKey,
|
||||
escrowAccount: escrowAccount.publicKey,
|
||||
pdaAccount: pda,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
it("Exchange escrow", async () => {
|
||||
await program.rpc.exchange({
|
||||
accounts: {
|
||||
taker: provider.wallet.publicKey,
|
||||
takerDepositTokenAccount: takerTokenAccountB,
|
||||
takerReceiveTokenAccount: takerTokenAccountA,
|
||||
pdaDepositTokenAccount: initializerTokenAccountA,
|
||||
initializerReceiveTokenAccount: initializerTokenAccountB,
|
||||
initializerMainAccount: provider.wallet.publicKey,
|
||||
escrowAccount: escrowAccount.publicKey,
|
||||
pdaAccount: pda,
|
||||
depositMint: mintB.publicKey,
|
||||
receiveMint: mintA.publicKey,
|
||||
depositTokenProgram: tokenProgramIdB,
|
||||
receiveTokenProgram: tokenProgramIdA,
|
||||
},
|
||||
});
|
||||
|
||||
let _takerTokenAccountA = await mintA.getAccountInfo(
|
||||
takerTokenAccountA
|
||||
);
|
||||
let _takerTokenAccountB = await mintB.getAccountInfo(
|
||||
takerTokenAccountB
|
||||
);
|
||||
let _initializerTokenAccountA = await mintA.getAccountInfo(
|
||||
initializerTokenAccountA
|
||||
);
|
||||
let _initializerTokenAccountB = await mintB.getAccountInfo(
|
||||
initializerTokenAccountB
|
||||
);
|
||||
|
||||
// Check that the initializer gets back ownership of their token account.
|
||||
assert.isTrue(
|
||||
_takerTokenAccountA.owner.equals(provider.wallet.publicKey)
|
||||
);
|
||||
|
||||
assert.strictEqual(
|
||||
_takerTokenAccountA.amount.toNumber(),
|
||||
initializerAmount
|
||||
);
|
||||
assert.strictEqual(_initializerTokenAccountA.amount.toNumber(), 0);
|
||||
assert.strictEqual(
|
||||
_initializerTokenAccountB.amount.toNumber(),
|
||||
takerAmount
|
||||
);
|
||||
assert.strictEqual(_takerTokenAccountB.amount.toNumber(), 0);
|
||||
});
|
||||
|
||||
let newEscrow = Keypair.generate();
|
||||
|
||||
it("Initialize escrow and cancel escrow", async () => {
|
||||
// Put back tokens into initializer token A account.
|
||||
await mintA.mintTo(
|
||||
initializerTokenAccountA,
|
||||
mintAuthority.publicKey,
|
||||
[mintAuthority],
|
||||
initializerAmount
|
||||
);
|
||||
|
||||
await program.rpc.initializeEscrow(
|
||||
new BN(initializerAmount),
|
||||
new BN(takerAmount),
|
||||
{
|
||||
accounts: {
|
||||
initializer: provider.wallet.publicKey,
|
||||
initializerDepositTokenAccount: initializerTokenAccountA,
|
||||
initializerReceiveTokenAccount: initializerTokenAccountB,
|
||||
escrowAccount: newEscrow.publicKey,
|
||||
systemProgram: SystemProgram.programId,
|
||||
tokenProgram: tokenProgramIdA,
|
||||
},
|
||||
signers: [newEscrow],
|
||||
}
|
||||
);
|
||||
|
||||
let _initializerTokenAccountA = await mintA.getAccountInfo(
|
||||
initializerTokenAccountA
|
||||
);
|
||||
|
||||
// Check that the new owner is the PDA.
|
||||
assert.isTrue(_initializerTokenAccountA.owner.equals(pda));
|
||||
|
||||
// Cancel the escrow.
|
||||
await program.rpc.cancelEscrow({
|
||||
accounts: {
|
||||
initializer: provider.wallet.publicKey,
|
||||
pdaDepositTokenAccount: initializerTokenAccountA,
|
||||
pdaAccount: pda,
|
||||
escrowAccount: newEscrow.publicKey,
|
||||
tokenProgram: tokenProgramIdA,
|
||||
},
|
||||
});
|
||||
|
||||
// Check the final owner should be the provider public key.
|
||||
_initializerTokenAccountA = await mintA.getAccountInfo(
|
||||
initializerTokenAccountA
|
||||
);
|
||||
assert.isTrue(
|
||||
_initializerTokenAccountA.owner.equals(provider.wallet.publicKey)
|
||||
);
|
||||
|
||||
// Check all the funds are still there.
|
||||
assert.strictEqual(
|
||||
_initializerTokenAccountA.amount.toNumber(),
|
||||
initializerAmount
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
let _takerTokenAccountA = await mintA.getAccountInfo(takerTokenAccountA);
|
||||
let _takerTokenAccountB = await mintB.getAccountInfo(takerTokenAccountB);
|
||||
let _initializerTokenAccountA = await mintA.getAccountInfo(
|
||||
initializerTokenAccountA
|
||||
);
|
||||
let _initializerTokenAccountB = await mintB.getAccountInfo(
|
||||
initializerTokenAccountB
|
||||
);
|
||||
|
||||
// Check that the initializer gets back ownership of their token account.
|
||||
assert.isTrue(_takerTokenAccountA.owner.equals(provider.wallet.publicKey));
|
||||
|
||||
assert.strictEqual(
|
||||
_takerTokenAccountA.amount.toNumber(),
|
||||
initializerAmount
|
||||
);
|
||||
assert.strictEqual(_initializerTokenAccountA.amount.toNumber(), 0);
|
||||
assert.strictEqual(
|
||||
_initializerTokenAccountB.amount.toNumber(),
|
||||
takerAmount
|
||||
);
|
||||
assert.strictEqual(_takerTokenAccountB.amount.toNumber(), 0);
|
||||
});
|
||||
|
||||
let newEscrow = Keypair.generate();
|
||||
|
||||
it("Initialize escrow and cancel escrow", async () => {
|
||||
// Put back tokens into initializer token A account.
|
||||
await mintA.mintTo(
|
||||
initializerTokenAccountA,
|
||||
mintAuthority.publicKey,
|
||||
[mintAuthority],
|
||||
initializerAmount
|
||||
);
|
||||
|
||||
await program.rpc.initializeEscrow(
|
||||
new BN(initializerAmount),
|
||||
new BN(takerAmount),
|
||||
{
|
||||
accounts: {
|
||||
initializer: provider.wallet.publicKey,
|
||||
initializerDepositTokenAccount: initializerTokenAccountA,
|
||||
initializerReceiveTokenAccount: initializerTokenAccountB,
|
||||
escrowAccount: newEscrow.publicKey,
|
||||
systemProgram: SystemProgram.programId,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
signers: [newEscrow],
|
||||
}
|
||||
);
|
||||
|
||||
let _initializerTokenAccountA = await mintA.getAccountInfo(
|
||||
initializerTokenAccountA
|
||||
);
|
||||
|
||||
// Check that the new owner is the PDA.
|
||||
assert.isTrue(_initializerTokenAccountA.owner.equals(pda));
|
||||
|
||||
// Cancel the escrow.
|
||||
await program.rpc.cancelEscrow({
|
||||
accounts: {
|
||||
initializer: provider.wallet.publicKey,
|
||||
pdaDepositTokenAccount: initializerTokenAccountA,
|
||||
pdaAccount: pda,
|
||||
escrowAccount: newEscrow.publicKey,
|
||||
tokenProgram: TOKEN_PROGRAM_ID,
|
||||
},
|
||||
});
|
||||
|
||||
// Check the final owner should be the provider public key.
|
||||
_initializerTokenAccountA = await mintA.getAccountInfo(
|
||||
initializerTokenAccountA
|
||||
);
|
||||
assert.isTrue(
|
||||
_initializerTokenAccountA.owner.equals(provider.wallet.publicKey)
|
||||
);
|
||||
|
||||
// Check all the funds are still there.
|
||||
assert.strictEqual(
|
||||
_initializerTokenAccountA.amount.toNumber(),
|
||||
initializerAmount
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,3 +9,9 @@ token_proxy = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
|
|||
test = "yarn run mocha -t 1000000 tests/"
|
||||
|
||||
[features]
|
||||
|
||||
[test.validator]
|
||||
url = "https://api.mainnet-beta.solana.com"
|
||||
|
||||
[[test.validator.clone]]
|
||||
address = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
|
||||
|
|
|
@ -16,4 +16,4 @@ cpi = ["no-entrypoint"]
|
|||
[dependencies]
|
||||
anchor-lang = { path = "../../../../../lang" }
|
||||
anchor-spl = { path = "../../../../../spl" }
|
||||
spl-token = { version = "3.1.1", features = ["no-entrypoint"] }
|
||||
spl-token-2022 = { version = "0.5.0", features = ["no-entrypoint"] }
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
//! This example demonstrates the use of the `anchor_spl::token` CPI client.
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::token::{self, Burn, MintTo, SetAuthority, Transfer};
|
||||
use anchor_spl::associated_token::AssociatedToken;
|
||||
use anchor_spl::token_interface::{
|
||||
self, Burn, Mint, MintTo, SetAuthority, TokenAccount, TokenInterface, Transfer, TransferChecked,
|
||||
};
|
||||
|
||||
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
|
||||
|
||||
|
@ -10,15 +13,44 @@ mod token_proxy {
|
|||
use super::*;
|
||||
|
||||
pub fn proxy_transfer(ctx: Context<ProxyTransfer>, amount: u64) -> Result<()> {
|
||||
token::transfer(ctx.accounts.into(), amount)
|
||||
#[allow(deprecated)]
|
||||
token_interface::transfer(ctx.accounts.into(), amount)
|
||||
}
|
||||
|
||||
pub fn proxy_optional_transfer(ctx: Context<ProxyOptionalTransfer>, amount: u64) -> Result<()> {
|
||||
if let Some(token_program) = &ctx.accounts.token_program {
|
||||
if let Some(mint) = &ctx.accounts.mint {
|
||||
let cpi_accounts = TransferChecked {
|
||||
from: ctx.accounts.from.to_account_info().clone(),
|
||||
mint: mint.to_account_info().clone(),
|
||||
to: ctx.accounts.to.to_account_info().clone(),
|
||||
authority: ctx.accounts.authority.clone(),
|
||||
};
|
||||
let cpi_program = token_program.to_account_info();
|
||||
let cpi_context = CpiContext::new(cpi_program, cpi_accounts);
|
||||
token_interface::transfer_checked(cpi_context, amount, mint.decimals)
|
||||
} else {
|
||||
let cpi_accounts = Transfer {
|
||||
from: ctx.accounts.from.to_account_info().clone(),
|
||||
to: ctx.accounts.to.to_account_info().clone(),
|
||||
authority: ctx.accounts.authority.clone(),
|
||||
};
|
||||
let cpi_program = token_program.to_account_info();
|
||||
let cpi_context = CpiContext::new(cpi_program, cpi_accounts);
|
||||
#[allow(deprecated)]
|
||||
token_interface::transfer(cpi_context, amount)
|
||||
}
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn proxy_mint_to(ctx: Context<ProxyMintTo>, amount: u64) -> Result<()> {
|
||||
token::mint_to(ctx.accounts.into(), amount)
|
||||
token_interface::mint_to(ctx.accounts.into(), amount)
|
||||
}
|
||||
|
||||
pub fn proxy_burn(ctx: Context<ProxyBurn>, amount: u64) -> Result<()> {
|
||||
token::burn(ctx.accounts.into(), amount)
|
||||
token_interface::burn(ctx.accounts.into(), amount)
|
||||
}
|
||||
|
||||
pub fn proxy_set_authority(
|
||||
|
@ -26,7 +58,21 @@ mod token_proxy {
|
|||
authority_type: AuthorityType,
|
||||
new_authority: Option<Pubkey>,
|
||||
) -> Result<()> {
|
||||
token::set_authority(ctx.accounts.into(), authority_type.into(), new_authority)
|
||||
token_interface::set_authority(ctx.accounts.into(), authority_type.into(), new_authority)
|
||||
}
|
||||
|
||||
pub fn proxy_create_token_account(_ctx: Context<ProxyCreateTokenAccount>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn proxy_create_associated_token_account(
|
||||
_ctx: Context<ProxyCreateAssociatedTokenAccount>,
|
||||
) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn proxy_create_mint(_ctx: Context<ProxyCreateMint>, _name: String) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,43 +91,112 @@ pub enum AuthorityType {
|
|||
#[derive(Accounts)]
|
||||
pub struct ProxyTransfer<'info> {
|
||||
#[account(signer)]
|
||||
/// CHECK:
|
||||
pub authority: AccountInfo<'info>,
|
||||
#[account(mut)]
|
||||
pub from: AccountInfo<'info>,
|
||||
pub from: InterfaceAccount<'info, TokenAccount>,
|
||||
#[account(mut)]
|
||||
pub to: AccountInfo<'info>,
|
||||
pub token_program: AccountInfo<'info>,
|
||||
pub to: InterfaceAccount<'info, TokenAccount>,
|
||||
pub token_program: Interface<'info, TokenInterface>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct ProxyOptionalTransfer<'info> {
|
||||
#[account(signer)]
|
||||
/// CHECK:
|
||||
pub authority: AccountInfo<'info>,
|
||||
#[account(mut)]
|
||||
pub from: InterfaceAccount<'info, TokenAccount>,
|
||||
#[account(mut)]
|
||||
pub to: InterfaceAccount<'info, TokenAccount>,
|
||||
pub mint: Option<InterfaceAccount<'info, Mint>>,
|
||||
pub token_program: Option<Interface<'info, TokenInterface>>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct ProxyMintTo<'info> {
|
||||
#[account(signer)]
|
||||
/// CHECK:
|
||||
pub authority: AccountInfo<'info>,
|
||||
#[account(mut)]
|
||||
pub mint: AccountInfo<'info>,
|
||||
pub mint: InterfaceAccount<'info, Mint>,
|
||||
#[account(mut)]
|
||||
pub to: AccountInfo<'info>,
|
||||
pub token_program: AccountInfo<'info>,
|
||||
pub to: InterfaceAccount<'info, TokenAccount>,
|
||||
pub token_program: Interface<'info, TokenInterface>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct ProxyBurn<'info> {
|
||||
#[account(signer)]
|
||||
/// CHECK:
|
||||
pub authority: AccountInfo<'info>,
|
||||
#[account(mut)]
|
||||
pub mint: AccountInfo<'info>,
|
||||
pub mint: InterfaceAccount<'info, Mint>,
|
||||
#[account(mut)]
|
||||
pub from: AccountInfo<'info>,
|
||||
pub token_program: AccountInfo<'info>,
|
||||
pub from: InterfaceAccount<'info, TokenAccount>,
|
||||
pub token_program: Interface<'info, TokenInterface>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct ProxySetAuthority<'info> {
|
||||
#[account(signer)]
|
||||
/// CHECK:
|
||||
pub current_authority: AccountInfo<'info>,
|
||||
#[account(mut)]
|
||||
/// CHECK:
|
||||
pub account_or_mint: AccountInfo<'info>,
|
||||
pub token_program: AccountInfo<'info>,
|
||||
pub token_program: Interface<'info, TokenInterface>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct ProxyCreateTokenAccount<'info> {
|
||||
#[account(mut)]
|
||||
pub authority: Signer<'info>,
|
||||
pub mint: InterfaceAccount<'info, Mint>,
|
||||
#[account(init,
|
||||
token::mint = mint,
|
||||
token::authority = authority,
|
||||
seeds = [authority.key().as_ref(), mint.key().as_ref(), b"token-proxy-account"],
|
||||
bump,
|
||||
payer = authority
|
||||
)]
|
||||
pub token_account: InterfaceAccount<'info, TokenAccount>,
|
||||
pub system_program: Program<'info, System>,
|
||||
pub token_program: Interface<'info, TokenInterface>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct ProxyCreateAssociatedTokenAccount<'info> {
|
||||
#[account(mut)]
|
||||
pub authority: Signer<'info>,
|
||||
#[account(
|
||||
init,
|
||||
associated_token::mint = mint,
|
||||
payer = authority,
|
||||
associated_token::authority = authority,
|
||||
)]
|
||||
pub token_account: InterfaceAccount<'info, TokenAccount>,
|
||||
pub mint: InterfaceAccount<'info, Mint>,
|
||||
pub system_program: Program<'info, System>,
|
||||
pub token_program: Interface<'info, TokenInterface>,
|
||||
pub associated_token_program: Program<'info, AssociatedToken>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(name: String)]
|
||||
pub struct ProxyCreateMint<'info> {
|
||||
#[account(mut)]
|
||||
pub authority: Signer<'info>,
|
||||
#[account(init,
|
||||
mint::decimals = 9,
|
||||
mint::authority = authority,
|
||||
seeds = [authority.key().as_ref(), name.as_bytes(), b"token-proxy-mint"],
|
||||
bump,
|
||||
payer = authority
|
||||
)]
|
||||
pub mint: InterfaceAccount<'info, Mint>,
|
||||
pub system_program: Program<'info, System>,
|
||||
pub token_program: Interface<'info, TokenInterface>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, 'c, 'info> From<&mut ProxyTransfer<'info>>
|
||||
|
@ -89,11 +204,11 @@ impl<'a, 'b, 'c, 'info> From<&mut ProxyTransfer<'info>>
|
|||
{
|
||||
fn from(accounts: &mut ProxyTransfer<'info>) -> CpiContext<'a, 'b, 'c, 'info, Transfer<'info>> {
|
||||
let cpi_accounts = Transfer {
|
||||
from: accounts.from.clone(),
|
||||
to: accounts.to.clone(),
|
||||
from: accounts.from.to_account_info().clone(),
|
||||
to: accounts.to.to_account_info().clone(),
|
||||
authority: accounts.authority.clone(),
|
||||
};
|
||||
let cpi_program = accounts.token_program.clone();
|
||||
let cpi_program = accounts.token_program.to_account_info();
|
||||
CpiContext::new(cpi_program, cpi_accounts)
|
||||
}
|
||||
}
|
||||
|
@ -103,11 +218,11 @@ impl<'a, 'b, 'c, 'info> From<&mut ProxyMintTo<'info>>
|
|||
{
|
||||
fn from(accounts: &mut ProxyMintTo<'info>) -> CpiContext<'a, 'b, 'c, 'info, MintTo<'info>> {
|
||||
let cpi_accounts = MintTo {
|
||||
mint: accounts.mint.clone(),
|
||||
to: accounts.to.clone(),
|
||||
mint: accounts.mint.to_account_info().clone(),
|
||||
to: accounts.to.to_account_info().clone(),
|
||||
authority: accounts.authority.clone(),
|
||||
};
|
||||
let cpi_program = accounts.token_program.clone();
|
||||
let cpi_program = accounts.token_program.to_account_info();
|
||||
CpiContext::new(cpi_program, cpi_accounts)
|
||||
}
|
||||
}
|
||||
|
@ -115,11 +230,11 @@ impl<'a, 'b, 'c, 'info> From<&mut ProxyMintTo<'info>>
|
|||
impl<'a, 'b, 'c, 'info> From<&mut ProxyBurn<'info>> for CpiContext<'a, 'b, 'c, 'info, Burn<'info>> {
|
||||
fn from(accounts: &mut ProxyBurn<'info>) -> CpiContext<'a, 'b, 'c, 'info, Burn<'info>> {
|
||||
let cpi_accounts = Burn {
|
||||
mint: accounts.mint.clone(),
|
||||
from: accounts.from.clone(),
|
||||
mint: accounts.mint.to_account_info().clone(),
|
||||
from: accounts.from.to_account_info().clone(),
|
||||
authority: accounts.authority.clone(),
|
||||
};
|
||||
let cpi_program = accounts.token_program.clone();
|
||||
let cpi_program = accounts.token_program.to_account_info();
|
||||
CpiContext::new(cpi_program, cpi_accounts)
|
||||
}
|
||||
}
|
||||
|
@ -134,18 +249,20 @@ impl<'a, 'b, 'c, 'info> From<&mut ProxySetAuthority<'info>>
|
|||
account_or_mint: accounts.account_or_mint.clone(),
|
||||
current_authority: accounts.current_authority.clone(),
|
||||
}; // TODO: Support multisig signers
|
||||
let cpi_program = accounts.token_program.clone();
|
||||
let cpi_program = accounts.token_program.to_account_info();
|
||||
CpiContext::new(cpi_program, cpi_accounts)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AuthorityType> for spl_token::instruction::AuthorityType {
|
||||
fn from(authority_ty: AuthorityType) -> spl_token::instruction::AuthorityType {
|
||||
impl From<AuthorityType> for spl_token_2022::instruction::AuthorityType {
|
||||
fn from(authority_ty: AuthorityType) -> spl_token_2022::instruction::AuthorityType {
|
||||
match authority_ty {
|
||||
AuthorityType::MintTokens => spl_token::instruction::AuthorityType::MintTokens,
|
||||
AuthorityType::FreezeAccount => spl_token::instruction::AuthorityType::FreezeAccount,
|
||||
AuthorityType::AccountOwner => spl_token::instruction::AuthorityType::AccountOwner,
|
||||
AuthorityType::CloseAccount => spl_token::instruction::AuthorityType::CloseAccount,
|
||||
AuthorityType::MintTokens => spl_token_2022::instruction::AuthorityType::MintTokens,
|
||||
AuthorityType::FreezeAccount => {
|
||||
spl_token_2022::instruction::AuthorityType::FreezeAccount
|
||||
}
|
||||
AuthorityType::AccountOwner => spl_token_2022::instruction::AuthorityType::AccountOwner,
|
||||
AuthorityType::CloseAccount => spl_token_2022::instruction::AuthorityType::CloseAccount,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,86 +1,252 @@
|
|||
const anchor = require("@coral-xyz/anchor");
|
||||
const { assert } = require("chai");
|
||||
const {
|
||||
splTokenProgram,
|
||||
SPL_TOKEN_PROGRAM_ID,
|
||||
} = require("@coral-xyz/spl-token");
|
||||
|
||||
describe("token", () => {
|
||||
describe("program", () => {
|
||||
const provider = anchor.AnchorProvider.local();
|
||||
|
||||
const TEST_PROGRAM_IDS = [
|
||||
SPL_TOKEN_PROGRAM_ID,
|
||||
new anchor.web3.PublicKey("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"),
|
||||
];
|
||||
const TOKEN_PROGRAMS = TEST_PROGRAM_IDS.map((programId) =>
|
||||
splTokenProgram({
|
||||
provider,
|
||||
programId,
|
||||
})
|
||||
);
|
||||
|
||||
// Configure the client to use the local cluster.
|
||||
anchor.setProvider(provider);
|
||||
|
||||
const program = anchor.workspace.TokenProxy;
|
||||
|
||||
let mint = null;
|
||||
let from = null;
|
||||
let to = null;
|
||||
TOKEN_PROGRAMS.forEach((tokenProgram) => {
|
||||
const name =
|
||||
tokenProgram.programId === SPL_TOKEN_PROGRAM_ID ? "token" : "token-2022";
|
||||
describe(name, () => {
|
||||
let mint = null;
|
||||
let from = null;
|
||||
let to = null;
|
||||
|
||||
it("Initializes test state", async () => {
|
||||
mint = await createMint(provider);
|
||||
from = await createTokenAccount(provider, mint, provider.wallet.publicKey);
|
||||
to = await createTokenAccount(provider, mint, provider.wallet.publicKey);
|
||||
});
|
||||
it("Initializes test state", async () => {
|
||||
mint = await createMint(tokenProgram);
|
||||
from = await createTokenAccount(
|
||||
tokenProgram,
|
||||
mint,
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
to = await createTokenAccount(
|
||||
tokenProgram,
|
||||
mint,
|
||||
provider.wallet.publicKey
|
||||
);
|
||||
});
|
||||
|
||||
it("Mints a token", async () => {
|
||||
await program.rpc.proxyMintTo(new anchor.BN(1000), {
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
mint,
|
||||
to: from,
|
||||
tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
|
||||
},
|
||||
it("Creates a token account", async () => {
|
||||
const newMint = await createMint(tokenProgram);
|
||||
const authority = provider.wallet.publicKey;
|
||||
const [tokenAccount] = anchor.web3.PublicKey.findProgramAddressSync(
|
||||
[
|
||||
authority.toBytes(),
|
||||
newMint.toBytes(),
|
||||
Buffer.from("token-proxy-account"),
|
||||
],
|
||||
program.programId
|
||||
);
|
||||
await program.rpc.proxyCreateTokenAccount({
|
||||
accounts: {
|
||||
authority,
|
||||
mint: newMint,
|
||||
tokenAccount,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: tokenProgram.programId,
|
||||
},
|
||||
});
|
||||
const account = await getTokenAccount(provider, tokenAccount);
|
||||
assert.isTrue(account.amount.eq(new anchor.BN(0)));
|
||||
});
|
||||
|
||||
it("Creates an associated token account", async () => {
|
||||
const newMint = await createMint(tokenProgram);
|
||||
const authority = provider.wallet.publicKey;
|
||||
const associatedTokenProgram = new anchor.web3.PublicKey(
|
||||
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
|
||||
);
|
||||
const [tokenAccount] = anchor.web3.PublicKey.findProgramAddressSync(
|
||||
[
|
||||
authority.toBytes(),
|
||||
tokenProgram.programId.toBytes(),
|
||||
newMint.toBytes(),
|
||||
],
|
||||
associatedTokenProgram
|
||||
);
|
||||
|
||||
await program.rpc.proxyCreateAssociatedTokenAccount({
|
||||
accounts: {
|
||||
tokenAccount,
|
||||
mint: newMint,
|
||||
authority,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: tokenProgram.programId,
|
||||
associatedTokenProgram,
|
||||
},
|
||||
});
|
||||
const account = await getTokenAccount(provider, tokenAccount);
|
||||
assert.isTrue(account.amount.eq(new anchor.BN(0)));
|
||||
});
|
||||
|
||||
it("Creates a mint", async () => {
|
||||
const authority = provider.wallet.publicKey;
|
||||
const [newMint] = anchor.web3.PublicKey.findProgramAddressSync(
|
||||
[
|
||||
authority.toBytes(),
|
||||
Buffer.from(name),
|
||||
Buffer.from("token-proxy-mint"),
|
||||
],
|
||||
program.programId
|
||||
);
|
||||
await program.rpc.proxyCreateMint(name, {
|
||||
accounts: {
|
||||
authority,
|
||||
mint: newMint,
|
||||
systemProgram: anchor.web3.SystemProgram.programId,
|
||||
tokenProgram: tokenProgram.programId,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("Mints a token", async () => {
|
||||
await program.rpc.proxyMintTo(new anchor.BN(1000), {
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
mint,
|
||||
to: from,
|
||||
tokenProgram: tokenProgram.programId,
|
||||
},
|
||||
});
|
||||
|
||||
const fromAccount = await getTokenAccount(provider, from);
|
||||
|
||||
assert.isTrue(fromAccount.amount.eq(new anchor.BN(1000)));
|
||||
});
|
||||
|
||||
it("Transfers a token", async () => {
|
||||
const preFromAccount = await getTokenAccount(provider, from);
|
||||
const preToAccount = await getTokenAccount(provider, to);
|
||||
|
||||
const transferAmount = new anchor.BN(400);
|
||||
|
||||
await program.rpc.proxyTransfer(transferAmount, {
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
to,
|
||||
from,
|
||||
tokenProgram: tokenProgram.programId,
|
||||
},
|
||||
});
|
||||
|
||||
const postFromAccount = await getTokenAccount(provider, from);
|
||||
const postToAccount = await getTokenAccount(provider, to);
|
||||
|
||||
assert.isTrue(
|
||||
postFromAccount.amount.eq(preFromAccount.amount.sub(transferAmount))
|
||||
);
|
||||
assert.isTrue(
|
||||
postToAccount.amount.eq(preToAccount.amount.add(transferAmount))
|
||||
);
|
||||
});
|
||||
|
||||
it("Transfers a token with optional accounts", async () => {
|
||||
const preFromAccount = await getTokenAccount(provider, from);
|
||||
const preToAccount = await getTokenAccount(provider, to);
|
||||
|
||||
const transferAmount = new anchor.BN(10);
|
||||
|
||||
await program.rpc.proxyOptionalTransfer(transferAmount, {
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
to,
|
||||
from,
|
||||
mint,
|
||||
tokenProgram: tokenProgram.programId,
|
||||
},
|
||||
});
|
||||
|
||||
const postFromAccount = await getTokenAccount(provider, from);
|
||||
const postToAccount = await getTokenAccount(provider, to);
|
||||
|
||||
assert.isTrue(
|
||||
postFromAccount.amount.eq(preFromAccount.amount.sub(transferAmount))
|
||||
);
|
||||
assert.isTrue(
|
||||
postToAccount.amount.eq(preToAccount.amount.add(transferAmount))
|
||||
);
|
||||
});
|
||||
|
||||
it("Does not transfer a token without optional accounts", async () => {
|
||||
const preFromAccount = await getTokenAccount(provider, from);
|
||||
const preToAccount = await getTokenAccount(provider, to);
|
||||
|
||||
const optionalTransferIx = await program.methods
|
||||
.proxyOptionalTransfer(new anchor.BN(10))
|
||||
.accounts({
|
||||
authority: provider.wallet.publicKey,
|
||||
to,
|
||||
from,
|
||||
mint: null,
|
||||
tokenProgram: null,
|
||||
})
|
||||
.instruction();
|
||||
const tx = new anchor.web3.Transaction().add(optionalTransferIx);
|
||||
await provider.sendAndConfirm(tx);
|
||||
|
||||
const postFromAccount = await getTokenAccount(provider, from);
|
||||
const postToAccount = await getTokenAccount(provider, to);
|
||||
|
||||
assert.isTrue(postFromAccount.amount.eq(preFromAccount.amount));
|
||||
assert.isTrue(postToAccount.amount.eq(preToAccount.amount));
|
||||
});
|
||||
|
||||
it("Burns a token", async () => {
|
||||
const preAccount = await getTokenAccount(provider, to);
|
||||
const burnAmount = new anchor.BN(300);
|
||||
await program.rpc.proxyBurn(burnAmount, {
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
mint,
|
||||
from: to,
|
||||
tokenProgram: tokenProgram.programId,
|
||||
},
|
||||
});
|
||||
|
||||
const postAccount = await getTokenAccount(provider, to);
|
||||
assert.isTrue(postAccount.amount.eq(preAccount.amount.sub(burnAmount)));
|
||||
});
|
||||
|
||||
it("Set new mint authority", async () => {
|
||||
const newMintAuthority = anchor.web3.Keypair.generate();
|
||||
await program.rpc.proxySetAuthority(
|
||||
{ mintTokens: {} },
|
||||
newMintAuthority.publicKey,
|
||||
{
|
||||
accounts: {
|
||||
accountOrMint: mint,
|
||||
currentAuthority: provider.wallet.publicKey,
|
||||
tokenProgram: tokenProgram.programId,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const mintInfo = await getMintInfo(provider, mint);
|
||||
assert.isTrue(
|
||||
mintInfo.mintAuthority.equals(newMintAuthority.publicKey)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const fromAccount = await getTokenAccount(provider, from);
|
||||
|
||||
assert.isTrue(fromAccount.amount.eq(new anchor.BN(1000)));
|
||||
});
|
||||
|
||||
it("Transfers a token", async () => {
|
||||
await program.rpc.proxyTransfer(new anchor.BN(400), {
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
to,
|
||||
from,
|
||||
tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
|
||||
},
|
||||
});
|
||||
|
||||
const fromAccount = await getTokenAccount(provider, from);
|
||||
const toAccount = await getTokenAccount(provider, to);
|
||||
|
||||
assert.isTrue(fromAccount.amount.eq(new anchor.BN(600)));
|
||||
assert.isTrue(toAccount.amount.eq(new anchor.BN(400)));
|
||||
});
|
||||
|
||||
it("Burns a token", async () => {
|
||||
await program.rpc.proxyBurn(new anchor.BN(399), {
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
mint,
|
||||
from: to,
|
||||
tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
|
||||
},
|
||||
});
|
||||
|
||||
const toAccount = await getTokenAccount(provider, to);
|
||||
assert.isTrue(toAccount.amount.eq(new anchor.BN(1)));
|
||||
});
|
||||
|
||||
it("Set new mint authority", async () => {
|
||||
const newMintAuthority = anchor.web3.Keypair.generate();
|
||||
await program.rpc.proxySetAuthority(
|
||||
{ mintTokens: {} },
|
||||
newMintAuthority.publicKey,
|
||||
{
|
||||
accounts: {
|
||||
accountOrMint: mint,
|
||||
currentAuthority: provider.wallet.publicKey,
|
||||
tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const mintInfo = await getMintInfo(provider, mint);
|
||||
assert.isTrue(mintInfo.mintAuthority.equals(newMintAuthority.publicKey));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -88,13 +254,6 @@ describe("token", () => {
|
|||
// mostly irrelevant to the point of the example.
|
||||
|
||||
const serumCmn = require("@project-serum/common");
|
||||
const TokenInstructions = require("@project-serum/serum").TokenInstructions;
|
||||
|
||||
// TODO: remove this constant once @project-serum/serum uses the same version
|
||||
// of @solana/web3.js as anchor (or switch packages).
|
||||
const TOKEN_PROGRAM_ID = new anchor.web3.PublicKey(
|
||||
TokenInstructions.TOKEN_PROGRAM_ID.toString()
|
||||
);
|
||||
|
||||
async function getTokenAccount(provider, addr) {
|
||||
return await serumCmn.getTokenAccount(provider, addr);
|
||||
|
@ -104,75 +263,33 @@ async function getMintInfo(provider, mintAddr) {
|
|||
return await serumCmn.getMintInfo(provider, mintAddr);
|
||||
}
|
||||
|
||||
async function createMint(provider, authority) {
|
||||
if (authority === undefined) {
|
||||
authority = provider.wallet.publicKey;
|
||||
}
|
||||
async function createMint(tokenProgram) {
|
||||
const mint = anchor.web3.Keypair.generate();
|
||||
const instructions = await createMintInstructions(
|
||||
provider,
|
||||
authority,
|
||||
mint.publicKey
|
||||
);
|
||||
const authority = tokenProgram.provider.wallet.publicKey;
|
||||
const createMintIx = await tokenProgram.account.mint.createInstruction(mint);
|
||||
const initMintIx = await tokenProgram.methods
|
||||
.initializeMint2(0, authority, null)
|
||||
.accounts({ mint: mint.publicKey })
|
||||
.instruction();
|
||||
|
||||
const tx = new anchor.web3.Transaction();
|
||||
tx.add(...instructions);
|
||||
tx.add(createMintIx, initMintIx);
|
||||
|
||||
await provider.sendAndConfirm(tx, [mint]);
|
||||
await tokenProgram.provider.sendAndConfirm(tx, [mint]);
|
||||
|
||||
return mint.publicKey;
|
||||
}
|
||||
|
||||
async function createMintInstructions(provider, authority, mint) {
|
||||
let instructions = [
|
||||
anchor.web3.SystemProgram.createAccount({
|
||||
fromPubkey: provider.wallet.publicKey,
|
||||
newAccountPubkey: mint,
|
||||
space: 82,
|
||||
lamports: await provider.connection.getMinimumBalanceForRentExemption(82),
|
||||
programId: TOKEN_PROGRAM_ID,
|
||||
}),
|
||||
TokenInstructions.initializeMint({
|
||||
mint,
|
||||
decimals: 0,
|
||||
mintAuthority: authority,
|
||||
}),
|
||||
];
|
||||
return instructions;
|
||||
}
|
||||
|
||||
async function createTokenAccount(provider, mint, owner) {
|
||||
async function createTokenAccount(tokenProgram, mint, owner) {
|
||||
const vault = anchor.web3.Keypair.generate();
|
||||
const tx = new anchor.web3.Transaction();
|
||||
tx.add(
|
||||
...(await createTokenAccountInstrs(provider, vault.publicKey, mint, owner))
|
||||
);
|
||||
await provider.sendAndConfirm(tx, [vault]);
|
||||
const createTokenAccountIx =
|
||||
await tokenProgram.account.account.createInstruction(vault);
|
||||
const initTokenAccountIx = await tokenProgram.methods
|
||||
.initializeAccount3(owner)
|
||||
.accounts({ account: vault.publicKey, mint })
|
||||
.instruction();
|
||||
tx.add(createTokenAccountIx, initTokenAccountIx);
|
||||
await tokenProgram.provider.sendAndConfirm(tx, [vault]);
|
||||
return vault.publicKey;
|
||||
}
|
||||
|
||||
async function createTokenAccountInstrs(
|
||||
provider,
|
||||
newAccountPubkey,
|
||||
mint,
|
||||
owner,
|
||||
lamports
|
||||
) {
|
||||
if (lamports === undefined) {
|
||||
lamports = await provider.connection.getMinimumBalanceForRentExemption(165);
|
||||
}
|
||||
return [
|
||||
anchor.web3.SystemProgram.createAccount({
|
||||
fromPubkey: provider.wallet.publicKey,
|
||||
newAccountPubkey,
|
||||
space: 165,
|
||||
lamports,
|
||||
programId: TOKEN_PROGRAM_ID,
|
||||
}),
|
||||
TokenInstructions.initializeAccount({
|
||||
account: newAccountPubkey,
|
||||
mint,
|
||||
owner,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue