# Solana合约的错误处理

### 错误处理

Solana合约开发中错误处理是一个重要的部分，它帮助开发者和用户理解合约执行过程中发生的问题。Solana使用Rust编程语言开发智能合约（称为程序），并提供了一套丰富的错误处理机制，其中包括定义和使用错误类型。

在合约开发中，我们需要返回ProgramResult类型，其定义为：

```
 use {
     std::{
         result::Result as ResultGeneric,
     },
 };
 
 pub type ProgramResult = ResultGeneric<(), ProgramError>;
```

其实就是一个使用了ProgramError作为Err部分的Result类型枚举。

### 错误类型

上面提到的ProgramError的定义为：

```
/// Reasons the program may fail
 #[derive(Clone, Debug, Deserialize, Eq, Error, PartialEq, Serialize)]
 pub enum ProgramError {
    /// Allows on-chain programs to implement program-specific error types and see them returned
    /// by the Solana runtime. A program-specific error may be any type that is represented as
    /// or serialized to a u32 integer.
    #[error("Custom program error: {0:#x}")]
    Custom(u32),
    #[error("The arguments provided to a program instruction were invalid")]
    InvalidArgument,
    #[error("An instruction's data contents was invalid")]
    InvalidInstructionData,
    #[error("An account's data contents was invalid")]
    InvalidAccountData,
    #[error("An account's data was too small")]
    AccountDataTooSmall,
    #[error("An account's balance was too small to complete the instruction")]
    InsufficientFunds,
    #[error("The account did not have the expected program id")]
    IncorrectProgramId,
    #[error("A signature was required but not found")]
    MissingRequiredSignature,
    #[error("An initialize instruction was sent to an account that has already been initialized")]
    AccountAlreadyInitialized,
    #[error("An attempt to operate on an account that hasn't been initialized")]
    UninitializedAccount,
    #[error("The instruction expected additional account keys")]
    NotEnoughAccountKeys,
    #[error("Failed to borrow a reference to account data, already borrowed")]
    AccountBorrowFailed,
    #[error("Length of the seed is too long for address generation")]
    MaxSeedLengthExceeded,
    #[error("Provided seeds do not result in a valid address")]
    InvalidSeeds,
    #[error("IO Error: {0}")]
    BorshIoError(String),
    #[error("An account does not have enough lamports to be rent-exempt")]
    AccountNotRentExempt,
    #[error("Unsupported sysvar")]
    UnsupportedSysvar,
    #[error("Provided owner is not allowed")]
    IllegalOwner,
    #[error("Accounts data allocations exceeded the maximum allowed per transaction")]
    MaxAccountsDataAllocationsExceeded,
    #[error("Account data reallocation was invalid")]
    InvalidRealloc,
    #[error("Instruction trace length exceeded the maximum allowed per transaction")]
    MaxInstructionTraceLengthExceeded,
    #[error("Builtin programs must consume compute units")]
    BuiltinProgramsMustConsumeComputeUnits,
}
```

#### **`Custom(u32)`** ：

自定义错误。允许合约开发者定义具体的错误码。当运行时返回错误时，这些自定义的错误码可以帮助确定错误发生的具体位置或原因。 既然是用u32类区别合约错误，自然我们想到了Rust里面的enum来定义错误：

```
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
 pub enum HelloWorldError {
     #[error("Not owned by HelloWolrd Program")]
     NotOwnedByHelloWrold,
 }

```

但是这是我们自定义的枚举，要如何让他变成上面的Custom(u32) 呢？因此需要定义转换函数：

```
impl From<HelloWorldError> for ProgramError {
     fn from(e: HelloWorldError) -> Self {
         ProgramError::Custom(e as u32)
     }
 }
```

直接将枚举的值，转换成了Custom里面的错误码。

错误码有了，但是错误码对应的意义是什么呢？

```
impl PrintProgramError for HelloWorldError {
    fn print<E>(&self)
    where
        E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
    {
        match self {
            HelloWorldError::NotOwnedByHelloWrold => msg!("Error: Greeted account does not have the correct program id!"),
        }
    }
 }
```

这里通过PrintProgramError trate的实现，来定义其错误消息。

这样在出错的时候，返回相应错误。

```
if greeting_account.owner != program_id {
         msg!("Greeted account does not have the correct program id");
         return Err(HelloWorldError::NotOwnedByHelloWrold.into());
     }
```

通过into直接转换。

### **`InvalidArgument`**：

参数无效。当传递给合约指令的参数不符合预期时，会返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
) -> ProgramResult {
    msg!("Hello World Rust program entrypoint");
    //碰到参数无效错误，返回错误
     Err(ProgramError::InvalidArgument)  
} 
```

### **`InvalidInstructionData`**：

指令数据无效。如果指令的数据字段包含了非法或不可解析的数据，将返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
 ) -> ProgramResult {
     msg!("Hello World Rust program entrypoint");
     //碰到 指令数据无效，返回错误
      Err(ProgramError::InvalidInstructionData)  
 } 
```

### **`InvalidAccountData`**：

账户数据无效。账户的数据内容不符合合约要求时，返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
) -> ProgramResult {
  msg!("Hello World Rust program entrypoint");
  //碰到账户数据无效错误，返回错误
   Err(ProgramError::InvalidAccountData)  
} 
```

1. **`AccountDataTooSmall`**：

账户数据过小。账户提供的存储空间不足以存储所需的数据时，将返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
 ) -> ProgramResult {
     msg!("Hello World Rust program entrypoint");
     //碰到 账户数据过小，返回错误
      Err(ProgramError::AccountDataTooSmall)  
 } 
```

1. **`InsufficientFunds`**：

资金不足。账户的余额不足以完成交易时，返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
 ) -> ProgramResult {
     msg!("Hello World Rust program entrypoint");
     //碰到账户的余额不足，返回错误
      Err(ProgramError::InsufficientFunds)  
 } 
```

### **`IncorrectProgramId`**：

程序ID不正确。执行交易时，如果账户的程序ID与预期的不一致，返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
 ) -> ProgramResult {
     msg!("Hello World Rust program entrypoint");
     //碰到 程序ID不正确，返回错误
      Err(ProgramError::IncorrectProgramId)  
 } 
```

### **`MissingRequiredSignature`**：

缺少必要的签名。如果交易需要的签名没有被提供，返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
 ) -> ProgramResult {
     msg!("Hello World Rust program entrypoint");
     //碰到缺少必要的签名，返回错误
      Err(ProgramError::MissingRequiredSignature)  
 } 
```

### **`AccountAlreadyInitialized`**：

账户已经初始化。向已初始化的账户发送初始化指令时，返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
 ) -> ProgramResult {
     msg!("Hello World Rust program entrypoint");
     //碰到向已初始化的账户发送初始化指令，返回错误
      Err(ProgramError::AccountAlreadyInitialized)  
 } 
```

### **`UninitializedAccount`**：

未初始化的账户。尝试操作一个未被初始化的账户时，将返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
 ) -> ProgramResult {
     msg!("Hello World Rust program entrypoint");
     //碰到 未初始化的账户，返回错误
      Err(ProgramError::UninitializedAccount)  
 } 
```

### **`NotEnoughAccountKeys`**：

账户密钥不足。指令需要更多的账户密钥，但没有提供足够的时，返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
 ) -> ProgramResult {
     msg!("Hello World Rust program entrypoint");
     //碰到账户密钥不足，返回错误
      Err(ProgramError::NotEnoughAccountKeys)  
 } 
```

### **`AccountBorrowFailed`**：

账户借用失败。无法借用对账户数据的引用时（可能是由于它已经被借用），返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
 ) -> ProgramResult {
     msg!("Hello World Rust program entrypoint");
     //碰到账户借用失败，返回错误
      Err(ProgramError::AccountBorrowFailed)  
 } 
```

### **`MaxSeedLengthExceeded`**：

种子长度超出最大限制。在地址生成时提供的种子长度过长，将返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
 ) -> ProgramResult {
     msg!("Hello World Rust program entrypoint");
     //碰到种子长度超出最大限制，返回错误
      Err(ProgramError::MaxSeedLengthExceeded)  
 } 
```

### **`InvalidSeeds`**：

种子无效。提供的种子未能生成有效地址时，返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
 ) -> ProgramResult {
     msg!("Hello World Rust program entrypoint");
     //碰到 种子无效错误，返回错误
      Err(ProgramError::InvalidSeeds)  
 } 
```

### **`BorshIoError(String)`** ：

Borsh序列化/反序列化错误。在进行Borsh数据编码或解码时发生错误，返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
 ) -> ProgramResult {
     msg!("Hello World Rust program entrypoint");
     //碰到Borsh序列化/反序列化错，返回错误
      Err(ProgramError::`BorshIoError("string".to_string()))  
 } 
```

### **`AccountNotRentExempt`**：

账户不免租金。账户的余额不足以维持其在区块链上存储的状态，返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
 ) -> ProgramResult {
     msg!("Hello World Rust program entrypoint");
     //碰到账户不免租金，返回错误
      Err(ProgramError::AccountNotRentExempt)  
 } 
```

### **`UnsupportedSysvar`**：

不支持的系统变量。尝试使用不支持的系统变量时，将返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
 ) -> ProgramResult {
     msg!("Hello World Rust program entrypoint");
     //碰到不支持的系统变量错误，返回错误
      Err(ProgramError::UnsupportedSysvar)  
 } 
```

### **`IllegalOwner`**：

非法的所有者。尝试将账户的所有权设置为非法的程序ID时，返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
 ) -> ProgramResult {
     msg!("Hello World Rust program entrypoint");
     //碰到非法的所有，返回错误
      Err(ProgramError::IllegalOwner)  
 } 
```

### **`MaxAccountsDataAllocationsExceeded`**：

账户数据分配超过每笔交易的最大允许量，返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
 ) -> ProgramResult {
     msg!("Hello World Rust program entrypoint");
     //碰到 账户数据分配超过每笔交易的最大允许量，返回错误
      Err(ProgramError::MaxAccountsDataAllocationsExceeded)  
 } 
```

### **`InvalidRealloc`**：

无效的重新分配。在尝试对账户数据空间进行重新分配时发生错误，返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
 ) -> ProgramResult {
     msg!("Hello World Rust program entrypoint");
     //碰到无效的重新分配，返回错误
      Err(ProgramError::InvalidRealloc)  
 } 
```

### **`MaxInstructionTraceLengthExceeded`**：

指令追踪长度超出每笔交易允许的最大值，返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
 ) -> ProgramResult {
     msg!("Hello World Rust program entrypoint");
     //碰到指令追踪长度超出每笔交易允许的最大值，返回错误
      Err(ProgramError::MaxInstructionTraceLengthExceeded)  
 } 
```

### **`BuiltinProgramsMustConsumeComputeUnits`**：

内置程序必须消耗计算单位。如果内置程序未消耗计算单位，返回此错误。

```
pub fn process_instruction(  _program_id: &Pubkey, _accounts: &[AccountInfo],_instruction_data: &[u8],  
 ) -> ProgramResult {
     msg!("Hello World Rust program entrypoint");
     //碰到内置程序必须消耗计算单位，返回错误
      Err(ProgramError::BuiltinProgramsMustConsumeComputeUnits)  
 } 
```

### 错误处理的好处

1. **明确性**：自定义错误使错误条件更加明确，便于开发者理解代码失败的原因。
2. **可维护性**：集中管理错误类型有助于代码的维护和更新。
3. **用户体验**：为终端用户提供更具体的错误信息，有助于他们理解发生了什么，尤其是在需要调试或解决问题时。

上面的错误基本涵盖了所有写合约时可能出现的错误类型，即使有漏网之鱼不匹配的情况，也可以通过字段定义错误创建适合自己合约的错误。

如有不明白或者不清楚的地方，请加入官方电报群：<https://t.me/gtokentool>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.gtokentool.com/qu-kuai-lian/solana-he-yue-de-cuo-wu-chu-li.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
