# Gear 合约开发案例学习: Escrow 钱包 上节回顾: [为 Gear 合约添加自定义状态查询函数](https://hackmd.io/@btwiuse/gear-metafns) ## Escrow 合约 担保 (Escrow) 是指交易双方不能够互相信任的条件下,通过第三方达成交易目标的一种制度。 买方: 担心卖家货物不合格 卖家: 担心买家不付钱 担保服务要求买方先将货款存到第三方,在买方确认货品品质后,第三方再将货款转给卖方。 <hr/> https://github.com/gear-dapps/escrow https://wiki.gear-tech.io/docs/examples/escrow/ 担保服务: Escrow 合约 货款: Fungible Token 货物: 任意物品 买方: Alice 卖方: Bob 第三方: Escrow 钱包 ## 合约 Metadata ./io/src/lib.rs ``` pub struct EscrowMetadata; impl Metadata for EscrowMetadata { type Init = In<InitEscrow>; type Handle = InOut<EscrowAction, EscrowEvent>; type Others = (); type Reply = (); type Signal = (); type State = EscrowState; } /// Initializes an escrow program. #[derive(Decode, Encode, TypeInfo)] pub struct InitEscrow { /// Address of a fungible token program. pub ft_program_id: ActorId, } #[derive(Clone, Decode, Encode, TypeInfo)] pub enum EscrowAction { Create { buyer: ActorId, seller: ActorId, amount: u128, }, Deposit( WalletId, ), Confirm( WalletId, ), Refund( WalletId, ), Cancel( WalletId, ), Continue( u64, ), } /// An enum that contains a result of processed [`EscrowAction`]. #[derive(Decode, Encode, TypeInfo)] pub enum EscrowEvent { ... } #[derive(Default, Encode, Decode, Clone, TypeInfo)] pub struct EscrowState { pub ft_program_id: ActorId, pub wallets: Vec<(WalletId, Wallet)>, pub id_nonce: WalletId, pub transaction_id: u64, pub transactions: Vec<(u64, Option<EscrowAction>)>, } /// Escrow wallet information. #[derive(Decode, Encode, TypeInfo, Clone, Copy, Debug, PartialEq, Eq)] pub struct Wallet { /// A buyer. pub buyer: ActorId, /// A seller. pub seller: ActorId, /// A wallet state. pub state: WalletState, /// An amount of tokens that a wallet can have. **Not** a current amount on a wallet balance! pub amount: u128, } /// An escrow wallet state. #[derive(Decode, Encode, TypeInfo, PartialEq, Eq, Clone, Copy, Debug)] pub enum WalletState { AwaitingDeposit, AwaitingConfirmation, Closed, } ``` EscrowAction定义了一个枚举,用于指导程序的执行。根据这个枚举的处理结果,程序会返回 EscrowEvent EscrowAction包含以下几种操作: Create:创建一个托管钱包,并返回钱包ID。要求消息来源必须是买家或卖家,且地址不能为零。成功时返回EscrowEvent::Created Deposit:买家向托管钱包存款,将钱包状态更改为AwaitingConfirmation。要求消息来源必须是买家,钱包不能已付款或关闭。成功时返回 EscrowEvent::Deposited Confirm:确认交易,将托管钱包的资金转给卖家,并将钱包状态更改为Closed。要求消息来源必须是买家,钱包必须已付款且未关闭。成功时返回 EscrowEvent::Confirmed Refund:将托管钱包的资金退还给买家,并将钱包状态更改回AwaitingDeposit。要求消息来源必须是卖家,钱包必须已付款且未关闭。成功时返回 EscrowEvent::Refunded Cancel:取消交易并关闭托管钱包,将钱包状态更改为Closed。要求消息来源必须是买家或卖家,钱包不能已付款或关闭。成功时返回 EscrowEvent::Cancelled Continue:在交易因燃料不足或代币合约错误而失败时,继续执行交易。要求transaction_id必须存在于transactions表中。当交易已处理时,返回 EscrowEvent::TransactionProcessed ## 消息处理 ./src/contract.rs ``` #[async_main] async fn main() { let action: EscrowAction = msg::load().expect("Unable to decode EscrowAction"); let escrow = unsafe { ESCROW.get_or_insert(Default::default()) }; match action { EscrowAction::Create { buyer, seller, amount, } => escrow.create(buyer, seller, amount), EscrowAction::Deposit(wallet_id) => { escrow .transactions .insert(escrow.transaction_id, Some(action)); escrow.deposit(None, wallet_id).await } EscrowAction::Confirm(wallet_id) => { escrow .transactions .insert(escrow.transaction_id, Some(action)); escrow.confirm(None, wallet_id).await } EscrowAction::Refund(wallet_id) => { escrow .transactions .insert(escrow.transaction_id, Some(action)); escrow.refund(None, wallet_id).await } EscrowAction::Cancel(wallet_id) => escrow.cancel(wallet_id).await, EscrowAction::Continue(transaction_id) => escrow.continue_transaction(transaction_id).await, } } ``` ![](https://i.imgur.com/n2PuuDO.png) ## 自定义状态查询函数 ./state/src/lib.rs ``` #[metawasm] pub mod metafns { pub type State = <EscrowMetadata as Metadata>::State; pub fn info(state: State, wallet_id: U256) -> Wallet { let (_, wallet) = *state .wallets .iter() .find(|(id, _)| id == &wallet_id) .unwrap_or_else(|| panic!("Wallet with the {wallet_id} ID doesn't exist")); wallet } pub fn created_wallets(state: State) -> Vec<(WalletId, Wallet)> { state .wallets .iter() .map(|(wallet_id, wallet)| (*wallet_id, *wallet)) .collect() } } ``` # 作业 在 GitHub 上打开 https://github.com/gear-dapps/escrow, 编译合约, 并在 [Gear IDEA](https://idea.gear-tech.io) 上部署,(初始化 `ft_program_id` 参数可使用任意地址), 最后提交你的合约访问连接 作业提交频道: https://t.me/Gear_CN