使用payable将NEAR从predecessor账户存入合约的异常排查
问题描述
我使用如下方法时运行正常,NEAR余额会从调用者账户扣除并划入合约账户:
#[payable] pub fn buy_tokens(&mut self, number_of_tokens: U128) { let number_of_tokens: u128 = number_of_tokens.into(); assert!(self.on_crowdsale == true, "Crowdsale has stalled"); let amount = env::attached_deposit(); let mut required_deposit = number_of_tokens * self.token_price; assert!( amount >= required_deposit + self.ft.storage_balance_bounds().min.0, "Requires attached deposit {}", required_deposit + self.ft.storage_balance_bounds().min.0 ); let account_id = env::predecessor_account_id(); if !self.ft.accounts.contains_key(&account_id) { // 未注册,若用户附加的NEAR足够则完成注册 // 从用户余额中扣除注册费用 self.ft.internal_register_account(&account_id); required_deposit = required_deposit + self.ft.storage_balance_bounds().min.0; } self.token_sold = (self.token_sold) .checked_add(number_of_tokens) .expect("Overflow"); assert!( self.token_sold <= self.total_available_tokens, "No more tokens to sale" ); assert!( self.token_sold <= self.phase_available_tokens, "No more tokens to sale" ); self.mint_myft(&account_id, number_of_tokens); if amount - required_deposit > 0 { Promise::new(account_id).transfer(amount - required_deposit); } }
但我使用如下方法时,合约虽提示执行成功,但调用者账户和合约地址的NEAR余额均无变化:
#[payable] pub fn buy_nft(&mut self, token_id: U128) { let product_id: u128 = token_id.into(); let token_owner_id = env::predecessor_account_id(); let product = self.get_product(product_id); let price = self.get_nft_price(product_id); let amount = env::attached_deposit(); assert!(amount == price, "Price should be equal to deposit"); let product_owner_id: u128 = product.user_id; self.increment_owner_incentives(product_owner_id, amount); self.check_and_increment_nft_token_mint_count(product_id); let countname = self.get_nft_count_name(product_id); let names = [product_id.to_owned().to_string(), countname]; let joined_name = names.join("_"); log!("NFT token id {}", joined_name); log!("Amount {}: Price {}", amount, price); let token_metadata = TokenMetadata { title: Some(product.product_details_hash.into()), description: None, media: None, media_hash: None, copies: Some(1u64), issued_at: None, expires_at: None, starts_at: None, updated_at: None, extra: None, reference: None, reference_hash: None, }; self.tokens.mint( joined_name, token_owner_id.try_into().unwrap(), Some(token_metadata), ); }
调用命令参考:near call dev-1625891373202-46622873112534 buy_nft '{"token_id":"5"}' --accountId amiyatulu.testnet --amount 10 --gas 300000000000000
请问我在第二个方法的实现中存在什么错误?
问题原因
- 核心错误是未支付NFT mint所需的存储押金,导致交易隐式回滚。
按照NEP-171 NFT标准要求,每个NFT的持有者需要占用对应的链上存储空间,必须支付对应存储押金才能完成mint操作。你在调用self.tokens.mint时没有附带任何NEAR作为存储押金,也没有提前为调用者注册NFT存储账户,因此mint操作直接失败,触发整个交易的状态回滚,用户支付的NEAR原路退回,所以双方余额都没有变化。你看到的"执行成功"只是当前合约方法执行完成的提示,没有捕获后续NFT mint操作的失败结果。 - 若你调用的是外部NFT合约的mint接口,当前写法没有对跨合约Promise的执行结果做校验,也会出现方法执行提示成功、但实际状态全部回滚的情况。
- 现有代码仅将收到的金额计入了
owner_incentives的记账字段,没有做实际的资金划转逻辑,后续如果没有额外提取规则,资金会一直滞留在合约账户中。
修复建议
- 在mint NFT前检查调用者是否已经注册NFT存储账户,未注册则先从用户的attached_deposit中扣除存储押金完成注册,或者在调用mint接口时附带对应的存储押金。
- 如果是调用外部NFT合约,需要给mint的Promise添加回调函数,根据mint的成功/失败结果处理资金:如果mint成功则留存/划转资金,如果失败则全额退回用户的押金。
- 补充资金划转逻辑,在确认mint成功后,按照业务规则将收到的NEAR转给对应收益方。
内容的提问来源于stack exchange,提问作者Amiya Behera
相关产品推荐
相关产品推荐

