3.1 合约(Contracts)

什么是智能合约?(What is a Smart Contract?)

A smart contract is no different than a script or predicate in that it is a piece of bytecode that is deployed to the blockchain via a transaction. The main features of a smart contract that differentiate it from scripts or predicates are that it is callable and stateful. Put another way, a smart contract is analogous to a deployed API with some database state. The interface of a smart contract, also just called a contract, must be defined strictly with an ABI declaration. See this contract for an example.

智能合约与脚本或谓词没有什么不同,因为它是一段字节码,通过一笔 交易部署到区块链上.智能合约区别于脚本或谓词的主要特征是它是 callablestateful。换句话说,智能合约类似于具有某些数据库状态的已部署 API。智能合约的接口,也简称为合约,必须使用 [ABI 声明](https://fuellabs.github.io/sway/v0.38.0/book/sway-program-types/smart_contracts. html#the-abi-声明)。有关示例,请参见本合同

智能合约的语法 (Syntax of a Smart Contract)

As with any Sway program, the program starts with a declaration of what program type it is. A contract must also either define or import an ABI declaration and implement it. It is considered good practice to define your ABI in a separate library and import it into your contract. This allows callers of your contract to simply import the ABI directly and use it in their scripts to call your contract. Let's take a look at an ABI declaration in a library:

与任何 Sway 程序一样,该程序用程序类型 的声明开始。合约还必须定义或导入 ABI 声明 并实施。一种很好的做法是在单独的库中定义 ABI 并将其导入到您的合约中。这允许您的合约调用者直接导入 ABI 并在他们的脚本中使用其来调用合约。让我们看一下库中的 ABI 声明:

library;

abi Wallet {
    #[storage(read, write), payable]
    fn receive_funds();

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

Let's focus on the ABI declaration and inspect it line-by-line.

让我们关注 ABI 声明并逐行检查。

ABI 声明 (The ABI Declaration)

abi Wallet {
    #[storage(read, write), payable]
    fn receive_funds();

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

In the first line, abi Wallet {, we declare the name of this Application Binary Interface, or ABI. We are naming this ABI Wallet. To import this ABI into either a script for calling or a contract for implementing, you would use

在第一行,abi Wallet {,我们声明了这个 Application Binary Interface 或 ABI 的名称。我们将此 ABI 命名为“钱包”。要将此 ABI 导入到调用脚本或实施合同中,您可以使用

use wallet_abi::Wallet;

In the second line, 在第二行,

    #[storage(read, write), payable]
    fn receive_funds();

we are declaring an ABI method called receive_funds which, when called, should receive funds into this wallet. Note that we are simply defining an interface here, so there is no function body or implementation of the function. We only need to define the interface itself. In this way, ABI declarations are similar to trait declarations. This particular ABI method does not take any parameters.

我们正在声明一个名为 receive_funds 的 ABI 方法,该方法在被调用时应该将资金接收到钱包。注意,我们在这里只是定义了一个接口,因此没有 function body 函数主体 或函数的实现。我们只需要定义接口本身。这样,ABI 声明类似于 特性声明。这个特殊的 ABI 方法不接受任何参数。


In the third line, 在第三行,

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

we are declaring another ABI method, this time called send_funds. It takes two parameters: the amount to send, and the address to send the funds to.

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

Note: The ABI methods receive_funds and send_funds also require the annotation #[storage(read, write)] because their implementations require reading and writing a storage variable that keeps track of the wallet balance, as we will see shortly. Refer to Purity for more information on storage annotations. 注意:ABI 方法receive_fundssend_funds 也需要注释#[storage(read, write)] 因为它们的实现需要读取和写入一个存储变量来跟踪钱包余额,我们很快就会看到。有关存储注释的更多信息,请参阅 Purity

给智能合约实施 ABI (Implementing an ABI for a Smart Contract)

Now that we've discussed how to define the interface, let's discuss how to use it. We will start by implementing the above ABI for a specific contract.

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

Implementing an ABI for a contract is accomplished with impl <ABI name> for Contract syntax. The for Contract syntax can only be used to implement an ABI for a contract; implementing methods for a struct should use impl Foo syntax.

为合同实施 ABI 是通过 impl <ABI name> for Contract 语法完成的。 for Contract 语法只能用于为合约实现 ABI;结构的实现方法应该使用 impl Foo 语法。

impl Wallet for Contract {
    #[storage(read, write), payable]
    fn receive_funds() {
        if msg_asset_id() == BASE_ASSET_ID {
            // If we received `BASE_ASSET_ID` then keep track of the balance.
            // Otherwise, we're receiving other native assets and don't care
            // about our balance of tokens.
            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_to_address()` is not a call and thus not an
        // interaction. Regardless, this code conforms to
        // checks-effects-interactions to avoid re-entrancy.
        transfer_to_address(amount_to_send, BASE_ASSET_ID, recipient_address);
    }
}

You may notice once again the similarities between traits and ABIs. And, indeed, as a bonus, you can specify methods in addition to the interface surface of an ABI, just like a trait. By implementing the methods in the interface surface, you get the extra method implementations For Free™.

您可能会再次注意到 traits 特征 和 ABI 之间的相似之处。而且,事实上,作为奖励,除了 ABI 的接口表面之外,您还可以指定方法,就像特征一样。通过在接口表面中实现方法,您可以获得额外的方法实现 For Free™。

Note that the above implementation of the ABI follows the Checks, Effects, Interactions pattern.

请注意,ABI 的上述实现遵循检查、效果、交互 模式。

从脚本调用智能合约 (Calling a Smart Contract from a Script)

Note: In most cases, calling a contract should be done from the Rust SDK or the TypeScript SDK which provide a more ergonomic UI for interacting with a contract. However, there are situations where manually writing a script to call a contract is required. 注意:在大多数情况下,应从 [Rust SDK](https://fuellabs.github.io/sway/v0.38.0/book/testing/testing-with-rust. html) 或 TypeScript SDK 提供更符合人体工学的用户界面,来与合约交互。但是,有些情况需要手动编写脚本来调用合约。

Now that we have defined our interface and implemented it for our contract, we need to know how to actually call our contract. Let's take a look at a contract call:

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

script;

use std::constants::ZERO_B256;
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: ZERO_B256,
    }(amount_to_send, recipient_address);
}

The main new concept is the abi cast: abi(AbiName, contract_address). This returns a ContractCaller type which can be used to call contracts. The methods of the ABI become the methods available on this contract caller: send_funds and receive_funds. We then directly call the contract ABI method as if it was just a regular method. You also have the option of specifying the following special parameters inside curly braces right before the main list of parameters:

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

  1. gas: a u64 that represents the gas being forwarded to the contract when it is called. gas:a u64 表示调用时转发给合约的 gas

  2. coins: a u64 that represents how many coins are being forwarded with this call. coins:一个 u64 表示通过此调用转发了多少代币

  3. asset_id: a b256 that represents the ID of the asset type of the coins being forwarded. asset_id:a b256 表示被转发的币的 资产类型 的 ID

Each special parameter is optional and assumes a default value when skipped:

每个特殊参数都是可选的,并在跳过时采用默认值:

  1. The default value for gas is the context gas (i.e. the content of the special register $cgas). Refer to the FuelVM specifications for more information about context gas. gas 的默认值是上下文气体(即特殊寄存器 $cgas 的内容)。有关上下文气体的更多信息,请参阅 FuelVM 规范

  2. The default value for coins is 0. coins 的默认值为 0

  3. The default value for asset_id is ZERO_B256. asset_id 的默认值为 ZERO_B256

Last updated