Icon Link什么是智能合约?

智能合约与脚本或断言没有区别,它是一段字节码,通过交易 部署到区块链上。智能合约与脚本或断言的主要特点是它可以被调用且具有状态。换句话说,智能合约类似于部署了一些数据库状态的 API。

智能合约的接口,也称为合约,必须严格地使用 ABI 声明 定义。查看此合约 以获取示例。

Icon Link智能合约的语法

与任何 Sway 程序一样,程序始于声明其程序类型 。合约必须定义或导入一个ABI 声明 并实现它。

ABI 最佳实践

在单独的库中定义 ABI 并将其导入到合约中被认为是一个良好的做法。这样,调用方只需直接导入 ABI,并在其脚本中使用它来调用您的合约。

让我们来看一个库中的 ABI 声明:

{{\#include ../../../../examples/wallet_abi/src/main.sw:abi_library}}

让我们专注于 ABI 声明并逐行检查它。

Icon LinkThe ABI Declaration

{{\#include ../../../../examples/wallet_abi/src/main.sw:abi}}

在第一行中,`abi Wallet {`,我们声明这个应用程序二进制接口(ABI)的名称。我们将这个 ABI 命名为 Wallet。要将此 ABI 导入到脚本以进行调用或导入到合约以进行实现,您将使用

{{\#include ../../../../examples/wallet_smart_contract/src/main.sw:abi_import}}

在第二行中,

{{\#include ../../../../examples/wallet_abi/src/main.sw:receive_funds}}

我们声明了一个名为receive_funds的 ABI 方法,当调用时,应该将资金存入这个钱包。请注意,我们在这里只是定义了一个接口,所以没有函数体或函数的实现。我们只需要定义接口本身。这种方式,ABI 声明类似于特征声明 。这个特定的 ABI 方法不接受任何参数。


在第三行中,

#[storage(read, write)]
fn send_funds(amount_to_send: u64, recipient_address: Address);

我们声明了另一个 ABI 方法,这次称为send_funds。它接受两个参数:要发送的金额和要发送资金的地址。

Icon InfoCircle

注意: ABI 方法receive_fundssend_funds还需要注释#[storage(read, write)],因为它们的实现需要读取和写入跟踪钱包余额的存储变量。有关存储注释的更多信息,请参见Purity

Icon Link为智能合约实现 ABI

既然我们讨论了如何定义接口,让我们讨论如何使用它。我们将从为特定合约实现上述 ABI 开始。

通过impl <ABI名称> for Contract语法实现合约的 ABI。for Contract语法只能用于实现合约的 ABI;为结构体实现方法应该使用impl Foo语法。

impl Wallet for Contract {
    #[storage(read, write), payable]
    fn receive_funds() {
        if msg_asset_id() == AssetId::base() {
            // If we received the base asset then keep track of the balance.
            // Otherwise, we're receiving other native assets and don't care
            // about our balance of coins.
            storage.balance.write(storage.balance.read() + msg_amount());
        }
    }
 
    #[storage(read, write)]
    fn send_funds(amount_to_send: u64, recipient_address: Address) {
        let sender = msg_sender().unwrap();
        match sender {
            Identity::Address(addr) => assert(addr == OWNER_ADDRESS),
            _ => revert(0),
        };
 
        let current_balance = storage.balance.read();
        assert(current_balance >= amount_to_send);
 
        storage.balance.write(current_balance - amount_to_send);
 
        // Note: `transfer()` is not a call and thus not an
        // interaction. Regardless, this code conforms to
        // checks-effects-interactions to avoid re-entrancy.
        transfer(
            Identity::Address(recipient_address),
            AssetId::base(),
            amount_to_send,
        );
    }
}

您可能再次注意到特征 和 ABI 之间的相似之处。而且,作为额外的奖励,您可以定义除了 ABI 接口表面之外的方法,就像特征一样。这些预实现的 ABI 方法自动成为实现相应 ABI 的合约接口的一部分。

请注意,上述 ABI 的实现遵循Checks, Effects, Interactions Icon Link模式。

Icon LinkContractId类型

合约在 Sway 中有一个关联的ContractId类型。ContractId类型允许 Sway 程序在 Sway 语言中引用合约。有关ContractId的更多信息,请参见书中的ContractId 部分。

Icon Link从脚本调用智能合约

Icon InfoCircle

注意: 在大多数情况下,调用合约应该使用Rust SDK TypeScript SDK Icon Link,因为它们为与合约交互提供了更人性化的界面。然而,有些情况下需要手动编写脚本来调用合约。

既然我们已经为合约定义了接口并实现了它,我们需要知道如何实际调用我们的合约。让我们看一下合约调用:

script;
 
use wallet_abi::Wallet;
 
fn main() {
    let contract_address = 0x9299da6c73e6dc03eeabcce242bb347de3f5f56cd1c70926d76526d7ed199b8b;
    let caller = abi(Wallet, contract_address);
    let amount_to_send = 200;
    let recipient_address = Address::from(0x9299da6c73e6dc03eeabcce242bb347de3f5f56cd1c70926d76526d7ed199b8b);
    caller
        .send_funds {
            gas: 10000,
            coins: 0,
            asset_id: b256::zero(),
        }(amount_to_send, recipient_address);
}
 

主要的新概念是abi cast: abi(AbiName, contract_address)。这返回一个ContractCaller类型,可用于调用合约。ABI 的方法成为此合约调用者可用的方法:send_fundsreceive_funds。然后我们直接调用合约 ABI 方法,就像它是常规方法一样。您还可以在主参数列表之前在花括号中指定以下特殊参数:

  1. gas: 一个u64,表示调用合约时转发的 gas。
  2. coins: 一个u64,表示此调用中转发的硬币数量。
  3. asset_id: 一个b256,表示转发的硬币的资产类型的 ID。

每个特殊参数都是可选的,如果省略,则假定默认值:

  1. gas的默认值是上下文 gas(即特殊寄存器 $cgas 中的内容)。有关上下文 gas 的更多信息,请参阅FuelVM 规范
  2. coins的默认值为 0。
  3. asset_id的默认值是b256::zero()