Skip to content

Commit 3d71257

Browse files
committed
Merge branch 'feature/token-2022-extension' into feature/token-2022
2 parents 8f96118 + 68509d8 commit 3d71257

9 files changed

+350
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import Foundation
2+
3+
public struct AnyToken2022ExtensionState: BorshCodable, Codable, Equatable, Hashable {
4+
// MARK: - Hashable
5+
6+
public func hash(into hasher: inout Hasher) {
7+
type.hash(into: &hasher)
8+
state.hash(into: &hasher)
9+
}
10+
11+
// MARK: - Equatable
12+
13+
public static func == (lhs: AnyToken2022ExtensionState, rhs: AnyToken2022ExtensionState) -> Bool {
14+
lhs.type == rhs.type &&
15+
lhs.state.jsonString == rhs.state.jsonString
16+
}
17+
18+
// MARK: - Properties
19+
20+
public let type: Token2022ExtensionType
21+
public let state: any Token2022ExtensionState
22+
23+
// MARK: - Codable
24+
25+
enum CodingKeys: String, CodingKey {
26+
case type
27+
case state
28+
}
29+
30+
public init(from decoder: Decoder) throws {
31+
let container = try decoder.container(keyedBy: CodingKeys.self)
32+
type = try container.decode(Token2022ExtensionType.self, forKey: .type)
33+
34+
switch type {
35+
case .transferFeeConfig:
36+
state = try container.decode(TransferFeeConfigExtensionState.self, forKey: .state)
37+
default:
38+
state = try container.decode(UnparsedExtensionState.self, forKey: .state)
39+
}
40+
}
41+
42+
public func encode(to encoder: Encoder) throws {
43+
var container = encoder.container(keyedBy: CodingKeys.self)
44+
try container.encode(type, forKey: .type)
45+
try container.encode(state, forKey: .state)
46+
}
47+
48+
// MARK: - BorshCodable
49+
50+
public init(from reader: inout BinaryReader) throws {
51+
guard let type = try Token2022ExtensionType(rawValue: UInt16(from: &reader)) else {
52+
throw BinaryReaderError.dataMismatch
53+
}
54+
self.type = type
55+
switch type {
56+
case .transferFeeConfig:
57+
state = try TransferFeeConfigExtensionState(from: &reader)
58+
case .interestBearingConfig:
59+
state = try InterestBearingConfigExtensionState(from: &reader)
60+
default:
61+
state = try UnparsedExtensionState(from: &reader)
62+
}
63+
}
64+
65+
public func serialize(to data: inout Data) throws {
66+
try type.rawValue.serialize(to: &data)
67+
try state.serialize(to: &data)
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import Foundation
2+
3+
public struct InterestBearingConfigExtensionState: Token2022ExtensionState {
4+
public var length: UInt16
5+
6+
public let rateAuthority: PublicKey
7+
public let initializationTimestamp: Int64
8+
public let preUpdateAverageRate: Int16
9+
public let lastUpdateTimestamp: Int64
10+
public let currentRate: Int16
11+
12+
public init(from reader: inout BinaryReader) throws {
13+
length = try UInt16(from: &reader)
14+
rateAuthority = try PublicKey(from: &reader)
15+
initializationTimestamp = try Int64(from: &reader)
16+
preUpdateAverageRate = try Int16(from: &reader)
17+
lastUpdateTimestamp = try Int64(from: &reader)
18+
currentRate = try Int16(from: &reader)
19+
}
20+
21+
public func serialize(to data: inout Data) throws {
22+
try length.serialize(to: &data)
23+
try rateAuthority.serialize(to: &data)
24+
try initializationTimestamp.serialize(to: &data)
25+
try preUpdateAverageRate.serialize(to: &data)
26+
try lastUpdateTimestamp.serialize(to: &data)
27+
try currentRate.serialize(to: &data)
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import Foundation
2+
3+
public struct TransferFeeConfigExtensionState: Token2022ExtensionState {
4+
public struct TransferFee: BorshCodable, Codable, Equatable, Hashable {
5+
public let epoch: UInt64
6+
public let maximumFee: UInt64
7+
public let transferFeeBasisPoints: UInt16
8+
9+
public init(from reader: inout BinaryReader) throws {
10+
epoch = try UInt64(from: &reader)
11+
maximumFee = try UInt64(from: &reader)
12+
transferFeeBasisPoints = try UInt16(from: &reader)
13+
}
14+
15+
public func serialize(to data: inout Data) throws {
16+
try epoch.serialize(to: &data)
17+
try maximumFee.serialize(to: &data)
18+
try transferFeeBasisPoints.serialize(to: &data)
19+
}
20+
}
21+
22+
public let length: UInt16
23+
/// Optional authority to set the fee
24+
public let transferFeeConfigAuthority: PublicKey
25+
/// Withdraw from mint instructions must be signed by this key
26+
public let withdrawWithHeldAuthority: PublicKey
27+
/// Withheld transfer fee tokens that have been moved to the mint for
28+
/// withdrawal
29+
public let withheldAmount: UInt64
30+
/// Older transfer fee, used if the current epoch < new_transfer_fee.epoch
31+
public let olderTransferFee: TransferFee
32+
/// Newer transfer fee, used if the current epoch >= new_transfer_fee.epoch
33+
public let newerTransferFee: TransferFee
34+
35+
public init(from reader: inout BinaryReader) throws {
36+
length = try UInt16(from: &reader)
37+
transferFeeConfigAuthority = try PublicKey(from: &reader)
38+
withdrawWithHeldAuthority = try PublicKey(from: &reader)
39+
withheldAmount = try UInt64(from: &reader)
40+
olderTransferFee = try TransferFee(from: &reader)
41+
newerTransferFee = try TransferFee(from: &reader)
42+
}
43+
44+
public func serialize(to data: inout Data) throws {
45+
try length.serialize(to: &data)
46+
try transferFeeConfigAuthority.serialize(to: &data)
47+
try withdrawWithHeldAuthority.serialize(to: &data)
48+
try withheldAmount.serialize(to: &data)
49+
try olderTransferFee.serialize(to: &data)
50+
try newerTransferFee.serialize(to: &data)
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import Foundation
2+
3+
struct UnparsedExtensionState: Token2022ExtensionState {
4+
let length: UInt16
5+
let data: Data
6+
init(from reader: inout BinaryReader) throws {
7+
length = try UInt16(from: &reader)
8+
data = try Data(reader.read(count: Int(length)))
9+
}
10+
11+
func serialize(to data: inout Data) throws {
12+
try length.serialize(to: &data)
13+
try data.serialize(to: &data)
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import Foundation
2+
3+
public protocol Token2022ExtensionState: BorshCodable, Codable, Equatable, Hashable {
4+
var length: UInt16 { get }
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import Foundation
2+
3+
public enum Token2022ExtensionType: UInt16, Codable, Hashable {
4+
/// Used as padding if the account size would otherwise be 355, same as a
5+
/// multisig
6+
case uninitialized
7+
/// Includes transfer fee rate info and accompanying authorities to withdraw
8+
/// and set the fee
9+
case transferFeeConfig
10+
/// Includes withheld transfer fees
11+
case transferFeeAmount
12+
/// Includes an optional mint close authority
13+
case mintCloseAuthority
14+
/// Auditor configuration for confidential transfers
15+
case confidentialTransferMint
16+
/// State for confidential transfers
17+
case confidentialTransferAccount
18+
/// Specifies the default Account::state for new Accounts
19+
case defaultAccountState
20+
/// Indicates that the Account owner authority cannot be changed
21+
case immutableOwner
22+
/// Require inbound transfers to have memo
23+
case memoTransfer
24+
/// Indicates that the tokens from this mint can't be transferred
25+
case nonTransferable
26+
/// Tokens accrue interest over time,
27+
case interestBearingConfig
28+
/// Locks privileged token operations from happening via CPI
29+
case cpiGuard
30+
/// Includes an optional permanent delegate
31+
case permanentDelegate
32+
/// Indicates that the tokens in this account belong to a non-transferable
33+
/// mint
34+
case nonTransferableAccount
35+
/// Mint requires a CPI to a program implementing the "transfer hook"
36+
/// interface
37+
case transferHook
38+
/// Indicates that the tokens in this account belong to a mint with a
39+
/// transfer hook
40+
case transferHookAccount
41+
/// Includes encrypted withheld fees and the encryption public that they are
42+
/// encrypted under
43+
case confidentialTransferFeeConfig
44+
/// Includes confidential withheld transfer fees
45+
case confidentialTransferFeeAmount
46+
/// Mint contains a pointer to another account (or the same account) that
47+
/// holds metadata
48+
case metadataPointer
49+
/// Mint contains token-metadata
50+
case tokenMetadata
51+
/// Mint contains a pointer to another account (or the same account) that
52+
/// holds group configurations
53+
case groupPointer
54+
/// Mint contains token group configurations
55+
case tokenGroup
56+
/// Mint contains a pointer to another account (or the same account) that
57+
/// holds group member configurations
58+
case groupMemberPointer
59+
/// Mint contains token group member configurations
60+
case tokenGroupMember
61+
62+
// MARK: - Test only
63+
64+
// /// Test variable-length mint extension
65+
// case variableLenMintTest = UInt16.max - 2,
66+
// /// Padding extension used to make an account exactly Multisig::LEN, used
67+
// /// for testing
68+
// case accountPaddingTest = UInt16.max - 1
69+
// /// Padding extension used to make a mint exactly Multisig::LEN, used for
70+
// /// testing
71+
// case mintPaddingTest = UInt16.max
72+
}

Sources/SolanaSwift/Programs/TokenPrograms/Token2022Program/Token2022AccountState.swift

+23-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public struct Token2022AccountState: TokenAccountState {
1717
public let closeAuthorityOption: UInt32
1818
public var closeAuthority: PublicKey?
1919

20+
public var extensions: [AnyToken2022ExtensionState]
21+
2022
public init(
2123
mint: PublicKey,
2224
owner: PublicKey,
@@ -32,7 +34,8 @@ public struct Token2022AccountState: TokenAccountState {
3234
isNative: Bool,
3335
delegatedAmount: UInt64,
3436
closeAuthorityOption: UInt32,
35-
closeAuthority: PublicKey? = nil
37+
closeAuthority: PublicKey? = nil,
38+
extensions: [AnyToken2022ExtensionState] = []
3639
) {
3740
self.mint = mint
3841
self.owner = owner
@@ -49,13 +52,16 @@ public struct Token2022AccountState: TokenAccountState {
4952
self.delegatedAmount = delegatedAmount
5053
self.closeAuthorityOption = closeAuthorityOption
5154
self.closeAuthority = closeAuthority
55+
self.extensions = extensions
5256
}
5357
}
5458

5559
extension Token2022AccountState: BorshCodable {
5660
public func serialize(to writer: inout Data) throws {
5761
try serializeCommonProperties(to: &writer)
58-
// TODO: - Serialize token-2022 extensions here
62+
for ext in extensions {
63+
try ext.serialize(to: &writer)
64+
}
5965
}
6066

6167
public init(from reader: inout BinaryReader) throws {
@@ -75,5 +81,20 @@ extension Token2022AccountState: BorshCodable {
7581
delegatedAmount = oldTokenProgramData.delegatedAmount
7682
closeAuthorityOption = oldTokenProgramData.closeAuthorityOption
7783
closeAuthority = oldTokenProgramData.closeAuthority
84+
85+
guard reader.cursor < reader.bytes.count else {
86+
extensions = []
87+
return
88+
}
89+
90+
_ = try reader.read(count: 1) // account type
91+
92+
var extensions = [AnyToken2022ExtensionState]()
93+
repeat {
94+
let ext = try AnyToken2022ExtensionState(from: &reader)
95+
extensions.append(ext)
96+
} while reader.cursor < reader.bytes.count
97+
98+
self.extensions = extensions
7899
}
79100
}

Sources/SolanaSwift/Programs/TokenPrograms/Token2022Program/Token2022MintState.swift

+26-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ public struct Token2022MintState: TokenMintState {
1010
public let isInitialized: Bool
1111
public let freezeAuthorityOption: UInt32
1212
public let freezeAuthority: PublicKey?
13+
14+
public var extensions: [AnyToken2022ExtensionState]
15+
16+
public func getParsedExtension<T: Token2022ExtensionState>(ofType _: T.Type) -> T? {
17+
assert(T.self != UnparsedExtensionState.self)
18+
return extensions.first(where: { $0.state is T })?.state as? T
19+
}
1320
}
1421

1522
extension Token2022MintState: BorshCodable {
@@ -22,10 +29,28 @@ extension Token2022MintState: BorshCodable {
2229
isInitialized = oldTokenMintState.isInitialized
2330
freezeAuthorityOption = oldTokenMintState.freezeAuthorityOption
2431
freezeAuthority = oldTokenMintState.freezeAuthority
32+
33+
guard reader.cursor < reader.bytes.count else {
34+
extensions = []
35+
return
36+
}
37+
38+
_ = try reader.read(count: 83) // padding
39+
_ = try reader.read(count: 1) // mint type
40+
41+
var extensions = [AnyToken2022ExtensionState]()
42+
repeat {
43+
let ext = try AnyToken2022ExtensionState(from: &reader)
44+
extensions.append(ext)
45+
} while reader.cursor < reader.bytes.count
46+
47+
self.extensions = extensions
2548
}
2649

2750
public func serialize(to writer: inout Data) throws {
2851
try serializeCommonProperties(to: &writer)
29-
// TODO: - Serialize token-2022 extensions here
52+
for ext in extensions {
53+
try ext.serialize(to: &writer)
54+
}
3055
}
3156
}

0 commit comments

Comments
 (0)