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

Commit ad823a1

Browse files
authored
Add revoke instruction (#85)
1 parent 767741c commit ad823a1

File tree

5 files changed

+182
-44
lines changed

5 files changed

+182
-44
lines changed

token/inc/token.h

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,23 +103,36 @@ typedef enum Token_TokenInstruction_Tag {
103103
Transfer,
104104
/**
105105
* Approves a delegate. A delegate is given the authority over
106-
* tokens on behalf of the source account's owner. If the amount to
107-
* delegate is zero then delegation is rescinded
108-
*
106+
* tokens on behalf of the source account's owner.
109107
* Accounts expected by this instruction:
110108
*
111-
* * Single owner/delegate
109+
* * Single owner
112110
* 0. `[writable]` The source account.
113-
* 1. `[]` (optional) The delegate if amount is non-zero.
114-
* 2. `[signer]` The source account owner/delegate.
111+
* 1. `[]` The delegate.
112+
* 2. `[signer]` The source account owner.
115113
*
116-
* * Multisignature owner/delegate
114+
* * Multisignature owner
117115
* 0. `[writable]` The source account.
118-
* 1. `[]` (optional) The delegate if amount is non-zero.
119-
* 2. '[]' The source account's multisignature owner/delegate.
116+
* 1. `[]` The delegate.
117+
* 2. '[]' The source account's multisignature owner.
120118
* 3. ..3+M '[signer]' M signer accounts
121119
*/
122120
Approve,
121+
/**
122+
* Revokes the delegate's authority.
123+
*
124+
* Accounts expected by this instruction:
125+
*
126+
* * Single owner
127+
* 0. `[writable]` The source account.
128+
* 2. `[signer]` The source account owner.
129+
*
130+
* * Multisignature owner
131+
* 0. `[writable]` The source account.
132+
* 2. '[]' The source account's multisignature owner.
133+
* 3. ..3+M '[signer]' M signer accounts
134+
*/
135+
Revoke,
123136
/**
124137
* Sets a new owner of a mint or account.
125138
*

token/js/cli/token-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ export async function approveRevoke(): Promise<void> {
175175
assert(testAccountInfo.delegate.equals(delegate));
176176
}
177177

178-
await testToken.revoke(testAccount, delegate, testAccountOwner, []);
178+
await testToken.revoke(testAccount, testAccountOwner, []);
179179
testAccountInfo = await testToken.getAccountInfo(testAccount);
180180
assert(testAccountInfo.delegatedAmount.toNumber() == 0);
181181
if (testAccountInfo.delegate != null) {

token/js/client/token.js

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -647,17 +647,32 @@ export class Token {
647647
* Remove approval for the transfer of any remaining tokens
648648
*
649649
* @param account Public key of the account
650-
* @param delegate Account to revoke authorization from
651650
* @param owner Owner of the source account
652651
* @param multiSigners Signing accounts if `owner` is a multiSig
653652
*/
654-
revoke(
653+
async revoke(
655654
account: PublicKey,
656-
delegate: PublicKey,
657655
owner: Account | PublicKey,
658656
multiSigners: Array<Account>,
659657
): Promise<void> {
660-
return this.approve(account, delegate, owner, multiSigners, 0);
658+
let ownerPublicKey;
659+
let signers;
660+
if (owner instanceof Account) {
661+
ownerPublicKey = owner.publicKey;
662+
signers = [owner];
663+
} else {
664+
ownerPublicKey = owner;
665+
signers = multiSigners;
666+
}
667+
await sendAndConfirmTransaction(
668+
'revoke',
669+
this.connection,
670+
new Transaction().add(
671+
this.revokeInstruction(account, ownerPublicKey, multiSigners),
672+
),
673+
this.payer,
674+
...signers
675+
);
661676
}
662677

663678
/**
@@ -836,10 +851,51 @@ export class Token {
836851
data,
837852
);
838853

839-
let keys = [{pubkey: account, isSigner: false, isWritable: true}];
840-
if (new TokenAmount(amount).toNumber() > 0) {
841-
keys.push({pubkey: delegate, isSigner: false, isWritable: false});
854+
let keys = [
855+
{pubkey: account, isSigner: false, isWritable: true},
856+
{pubkey: delegate, isSigner: false, isWritable: false}
857+
];
858+
if (owner instanceof Account) {
859+
keys.push({pubkey: owner.publicKey, isSigner: true, isWritable: false});
860+
} else {
861+
keys.push({pubkey: owner, isSigner: false, isWritable: false});
862+
multiSigners.forEach(signer => keys.push({pubkey: signer.publicKey, isSigner: true, isWritable: false}));
842863
}
864+
865+
return new TransactionInstruction({
866+
keys,
867+
programId: this.programId,
868+
data,
869+
});
870+
}
871+
872+
/**
873+
* Construct an Approve instruction
874+
*
875+
* @param account Public key of the account
876+
* @param delegate Account authorized to perform a transfer of tokens from the source account
877+
* @param owner Owner of the source account
878+
* @param multiSigners Signing accounts if `owner` is a multiSig
879+
* @param amount Maximum number of tokens the delegate may transfer
880+
*/
881+
revokeInstruction(
882+
account: PublicKey,
883+
owner: Account | PublicKey,
884+
multiSigners: Array<Account>,
885+
): TransactionInstruction {
886+
const dataLayout = BufferLayout.struct([
887+
BufferLayout.u8('instruction'),
888+
]);
889+
890+
const data = Buffer.alloc(dataLayout.span);
891+
dataLayout.encode(
892+
{
893+
instruction: 5, // Approve instruction
894+
},
895+
data,
896+
);
897+
898+
let keys = [{pubkey: account, isSigner: false, isWritable: true}];
843899
if (owner instanceof Account) {
844900
keys.push({pubkey: owner.publicKey, isSigner: true, isWritable: false});
845901
} else {
@@ -873,7 +929,7 @@ export class Token {
873929
const data = Buffer.alloc(dataLayout.span);
874930
dataLayout.encode(
875931
{
876-
instruction: 5, // SetOwner instruction
932+
instruction: 6, // SetOwner instruction
877933
},
878934
data,
879935
);
@@ -919,7 +975,7 @@ export class Token {
919975
const data = Buffer.alloc(dataLayout.span);
920976
dataLayout.encode(
921977
{
922-
instruction: 6, // MintTo instruction
978+
instruction: 7, // MintTo instruction
923979
amount: new TokenAmount(amount).toBuffer(),
924980
},
925981
data,
@@ -965,7 +1021,7 @@ export class Token {
9651021
const data = Buffer.alloc(dataLayout.span);
9661022
dataLayout.encode(
9671023
{
968-
instruction: 7, // Burn instruction
1024+
instruction: 8, // Burn instruction
9691025
amount: new TokenAmount(amount).toBuffer(),
9701026
},
9711027
data,

token/src/instruction.rs

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -86,22 +86,34 @@ pub enum TokenInstruction {
8686
/// 3. ..3+M '[signer]' M signer accounts.
8787
Transfer(u64),
8888
/// Approves a delegate. A delegate is given the authority over
89-
/// tokens on behalf of the source account's owner. If the amount to
90-
/// delegate is zero then delegation is rescinded
91-
///
89+
/// tokens on behalf of the source account's owner.
90+
9291
/// Accounts expected by this instruction:
9392
///
94-
/// * Single owner/delegate
93+
/// * Single owner
9594
/// 0. `[writable]` The source account.
96-
/// 1. `[]` (optional) The delegate if amount is non-zero.
97-
/// 2. `[signer]` The source account owner/delegate.
95+
/// 1. `[]` The delegate.
96+
/// 2. `[signer]` The source account owner.
9897
///
99-
/// * Multisignature owner/delegate
98+
/// * Multisignature owner
10099
/// 0. `[writable]` The source account.
101-
/// 1. `[]` (optional) The delegate if amount is non-zero.
102-
/// 2. '[]' The source account's multisignature owner/delegate.
100+
/// 1. `[]` The delegate.
101+
/// 2. '[]' The source account's multisignature owner.
103102
/// 3. ..3+M '[signer]' M signer accounts
104103
Approve(u64),
104+
/// Revokes the delegate's authority.
105+
///
106+
/// Accounts expected by this instruction:
107+
///
108+
/// * Single owner
109+
/// 0. `[writable]` The source account.
110+
/// 2. `[signer]` The source account owner.
111+
///
112+
/// * Multisignature owner
113+
/// 0. `[writable]` The source account.
114+
/// 2. '[]' The source account's multisignature owner.
115+
/// 3. ..3+M '[signer]' M signer accounts
116+
Revoke,
105117
/// Sets a new owner of a mint or account.
106118
///
107119
/// Accounts expected by this instruction:
@@ -188,16 +200,17 @@ impl TokenInstruction {
188200
let amount: &u64 = unsafe { &*(&input[1] as *const u8 as *const u64) };
189201
Self::Approve(*amount)
190202
}
191-
5 => Self::SetOwner,
192-
6 => {
203+
5 => Self::Revoke,
204+
6 => Self::SetOwner,
205+
7 => {
193206
if input.len() < size_of::<u8>() + size_of::<u64>() {
194207
return Err(ProgramError::InvalidAccountData);
195208
}
196209
#[allow(clippy::cast_ptr_alignment)]
197210
let amount: &u64 = unsafe { &*(&input[1] as *const u8 as *const u64) };
198211
Self::MintTo(*amount)
199212
}
200-
7 => {
213+
8 => {
201214
if input.len() < size_of::<u8>() + size_of::<u64>() {
202215
return Err(ProgramError::InvalidAccountData);
203216
}
@@ -238,15 +251,16 @@ impl TokenInstruction {
238251
let value = unsafe { &mut *(&mut output[1] as *mut u8 as *mut u64) };
239252
*value = *amount;
240253
}
241-
Self::SetOwner => output[0] = 5,
254+
Self::Revoke => output[0] = 5,
255+
Self::SetOwner => output[0] = 6,
242256
Self::MintTo(amount) => {
243-
output[0] = 6;
257+
output[0] = 7;
244258
#[allow(clippy::cast_ptr_alignment)]
245259
let value = unsafe { &mut *(&mut output[1] as *mut u8 as *mut u64) };
246260
*value = *amount;
247261
}
248262
Self::Burn(amount) => {
249-
output[0] = 7;
263+
output[0] = 8;
250264
#[allow(clippy::cast_ptr_alignment)]
251265
let value = unsafe { &mut *(&mut output[1] as *mut u8 as *mut u64) };
252266
*value = *amount;
@@ -383,9 +397,33 @@ pub fn approve(
383397

384398
let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len());
385399
accounts.push(AccountMeta::new_readonly(*source_pubkey, false));
386-
if amount > 0 {
387-
accounts.push(AccountMeta::new(*delegate_pubkey, false));
400+
accounts.push(AccountMeta::new(*delegate_pubkey, false));
401+
accounts.push(AccountMeta::new_readonly(
402+
*owner_pubkey,
403+
signer_pubkeys.is_empty(),
404+
));
405+
for signer_pubkey in signer_pubkeys.iter() {
406+
accounts.push(AccountMeta::new(**signer_pubkey, true));
388407
}
408+
409+
Ok(Instruction {
410+
program_id: *token_program_id,
411+
accounts,
412+
data,
413+
})
414+
}
415+
416+
/// Creates an `Approve` instruction.
417+
pub fn revoke(
418+
token_program_id: &Pubkey,
419+
source_pubkey: &Pubkey,
420+
owner_pubkey: &Pubkey,
421+
signer_pubkeys: &[&Pubkey],
422+
) -> Result<Instruction, ProgramError> {
423+
let data = TokenInstruction::Revoke.serialize()?;
424+
425+
let mut accounts = Vec::with_capacity(2 + signer_pubkeys.len());
426+
accounts.push(AccountMeta::new_readonly(*source_pubkey, false));
389427
accounts.push(AccountMeta::new_readonly(
390428
*owner_pubkey,
391429
signer_pubkeys.is_empty(),

token/src/state.rs

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -243,14 +243,31 @@ impl State {
243243

244244
let mut source_data = source_account_info.data.borrow_mut();
245245
if let State::Account(mut source_account) = State::deserialize(&source_data)? {
246-
source_account.delegate = if amount > 0 {
247-
let delegate_info = next_account_info(account_info_iter)?;
248-
COption::Some(*delegate_info.key)
249-
} else {
250-
COption::None
251-
};
246+
let delegate_info = next_account_info(account_info_iter)?;
247+
let owner_info = next_account_info(account_info_iter)?;
248+
Self::validate_owner(
249+
program_id,
250+
&source_account.owner,
251+
owner_info,
252+
account_info_iter.as_slice(),
253+
)?;
254+
255+
source_account.delegate = COption::Some(*delegate_info.key);
252256
source_account.delegated_amount = amount;
253257

258+
State::Account(source_account).serialize(&mut source_data)
259+
} else {
260+
Err(ProgramError::InvalidArgument)
261+
}
262+
}
263+
264+
/// Processes an [Revoke](enum.TokenInstruction.html) instruction.
265+
pub fn process_revoke(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
266+
let account_info_iter = &mut accounts.iter();
267+
let source_account_info = next_account_info(account_info_iter)?;
268+
269+
let mut source_data = source_account_info.data.borrow_mut();
270+
if let State::Account(mut source_account) = State::deserialize(&source_data)? {
254271
let owner_info = next_account_info(account_info_iter)?;
255272
Self::validate_owner(
256273
program_id,
@@ -259,6 +276,9 @@ impl State {
259276
account_info_iter.as_slice(),
260277
)?;
261278

279+
source_account.delegate = COption::None;
280+
source_account.delegated_amount = 0;
281+
262282
State::Account(source_account).serialize(&mut source_data)
263283
} else {
264284
Err(ProgramError::InvalidArgument)
@@ -445,6 +465,10 @@ impl State {
445465
info!("Instruction: Approve");
446466
Self::process_approve(program_id, accounts, amount)
447467
}
468+
TokenInstruction::Revoke => {
469+
info!("Instruction: Revoke");
470+
Self::process_revoke(program_id, accounts)
471+
}
448472
TokenInstruction::SetOwner => {
449473
info!("Instruction: SetOwner");
450474
Self::process_set_owner(program_id, accounts)
@@ -558,7 +582,7 @@ solana_sdk_bpf_test::stubs!();
558582
mod tests {
559583
use super::*;
560584
use crate::instruction::{
561-
approve, burn, initialize_account, initialize_mint, initialize_multisig, mint_to,
585+
approve, burn, initialize_account, initialize_mint, initialize_multisig, mint_to, revoke,
562586
set_owner, transfer,
563587
};
564588
use solana_sdk::{
@@ -1245,6 +1269,13 @@ mod tests {
12451269
],
12461270
)
12471271
.unwrap();
1272+
1273+
// revoke delegate
1274+
do_process_instruction(
1275+
revoke(&program_id, &account_key, &owner_key, &[]).unwrap(),
1276+
vec![&mut account_account, &mut owner_account],
1277+
)
1278+
.unwrap();
12481279
}
12491280

12501281
#[test]

0 commit comments

Comments
 (0)