智能合约交互
原文:https://docs.elrond.com/sdk-and-tools/erdpy/smart-contract-interactions
让我们更深入地了解智能合约交互,以及当您需要与 SC 交互时,您需要知道什么。如果您遵循了之前的 erdpy 相关文档,您应该能够设置您的先决条件,如代理 URL、链 ID 和 PEM 文件。为此,我们需要一个交互文件。通常,我们在合约的文件夹中找到这个文件,在一个交互文件夹中。交互文件通常有一个提示性的名字,与已经完成设置的链相关。比如: devnet.snippets.sh 。
重要
为了能够从交互文件中调用方法,我们需要在终端中将 shell 文件指定为源文件。我们可以通过运行source devnet.snippets.sh
命令来做到这一点。此外,在每次更改交互文件结构之后,我们需要重复 source 命令。
让我们举下面的例子:
- 我们想在 Devnet 上部署一个新的 SC
- 然后,我们需要升级合约,使其可支付
- 我们调用端点而不转移任何资产
- 我们进行一个 ESDTTransfer,以便调用一个可支付端点
- 我们称之为视图函数
部署&升级
重要的事情先来。为了部署一个新的合约,我们需要使用 erdpy 通过使用erdpy contract build
命令来构建它。这将创建输出 wasm 文件,在交互文件中,我们可以将其保存为固定参数:
WASM_PATH="~/my-contract/output/my-contract.wasm"
现在,为了部署合约,我们使用 erdpy 的特殊的 deploy 函数,它在指定的链上部署合约,并运行合约的 init 函数。
deploySC() {
erdpy --verbose contract deploy --recall-nonce \
--bytecode=${WASM_PATH} \
--pem=${WALLET_PEM} \
--gas-limit=60000000 \
--proxy=${PROXY} --chain=${CHAIN_ID} \
--arguments $1 $2 \
--send || return }
现在让我们看看交互的结构。它接收 wasm 文件的路径,我们以前在这里构建了合约。它还接收 PEM 文件的路径、代理 url 和链 id,在那里将部署合约。另一个重要的参数是燃气限额,我们在这里规定了我们愿意在这次交易中花费的最大燃气量。每个交易成本取决于其复杂性和它处理的数据存储量。
我们必须仔细研究的另一个论点是回忆-现时。正如我们所知,每个帐户都有自己的 nonce,它随着每次发送的交易而增加。也就是说,当调用端点或部署函数等时,为了正确处理交易,我们必须传递下一行 nonce。而 recall-nonce 就是这么做的。它通过查询最后一个随机数的区块链来给我们正确的随机数。
除此之外,我们还有关键字 arguments ,它允许我们传递所需的参数。如前所述,部署智能合约意味着我们运行 init 函数,它可能会也可能不会请求一些参数。在我们的例子中, init 函数有两个不同的参数,我们在调用 deploy 函数时传递它们。在这一节的后面,我们将讨论如何在函数调用中传递参数。
发送交易后,erdpy 将根据交易的类型输出诸如交易散列、数据和任何其他重要信息之类的信息。在合约部署的情况下,它还将输出新部署的合约地址。
现在让我们假设我们需要使合约可支付,以防它需要接收资金。我们可以重新部署合约,但这将意味着两份不同的合约,更不用说我们将失去任何现有存储。为此,我们可以使用 upgrade 命令,用新构建的合约版本替换现有的 SC 字节码。
警告
升级智能合约时,务必小心处理数据存储。必须保留数据结构,尤其是对于复杂的数据类型,否则数据可能会损坏。
升级函数将如下所示:
upgradeSC() {
erdpy --verbose contract upgrade ${CONTRACT_ADDRESS} --recall-nonce --payable \
--bytecode=${WASM_PATH} \
--pem=${WALLET_PEM} \
--gas-limit=60000000 \
--proxy=${PROXY} --chain=${CHAIN_ID} \
--arguments $1 $2 \
--send || return
}
重要
当我们运行升级函数时,我们再次调用 SC 的初始化函数。这意味着我们必须再次传递函数的参数,不管它们是改变了还是保持不变。
这里我们有两个新的不同的元素需要观察。首先,我们用升级函数更改了部署函数,这反过来需要先前部署的 SC 地址的地址,以便能够识别要升级的 SC。需要注意的是,该函数只能由 SC 的所有者调用。我们需要观察的第二个元素是 payable 关键字,它表示允许 SC 接收付款的代码元数据标志。
提示
关于代码元数据的更多信息可以在这里找到。
非应付端点交互
假设我们想要调用下面的端点,它以这种特定的顺序接收一个地址和三个不同的 BigUint 参数。
###PARAMS
#1 - FirstBigUintArgument
#2 - SecondBigUintArgument
ADDRESS_ARGUMENT="erd14nw9pukqyqu75gj0shm8upsegjft8l0awjefp877phfx74775dsq49swp3"
THIRD_BIGUINT_ARGUMENT=0x0f4240
myNonPayableEndpoint() {
address_argument="0x$(erdpy wallet bech32 --decode ${ADDRESS_ARGUMENT})"
erdpy --verbose contract call ${CONTRACT_ADDRESS} --recall-nonce \
--pem=${WALLET_PEM} \
--gas-limit=6000000 \
--proxy=${PROXY} --chain=${CHAIN_ID} \
--function="myNonPayableEndpoint" \
--arguments $address_argument $1 $2 ${THIRD_BIGUINT_ARGUMENT}\
--send || return
}
那么,在这种相互作用中发生了什么,我们怎么称呼它呢?除了函数和参数部分之外,该代码片段与部署或升级合约时的代码片段大致相同。当调用一个不可支付的函数时,我们需要提供端点的名称作为函数参数。至于参数,它们必须与 SC 中的顺序相同,包括在调用参数数量可变的端点时。现在,为了举例,我们以多种方式提供了论点。每个开发者都可以选择自己喜欢的布局,但是有几点需要强调:
- 大多数提供的参数需要十六进制格式(0x...).
- 当把一个值转换成十六进制格式时,我们需要确保它有偶数个字符。如果不是,我们需要提供一个额外的 0,以使它均匀。(例如十六进制编码中的数字 911 ->等于:38f ->所以我们需要提供参数 0x038f)。
- 参数既可以作为固定参数提供(通常用于不可更改的参数,如合约的地址或固定数字),也可以在与代码片段交互时作为终端中的输入提供(主要用于经常变化的参数,如数字)。
在我们的例子中,我们提供地址参数作为固定参数。然后我们将它转换成十六进制格式(默认情况下是 bech32 格式),之后我们才将其作为参数传递。至于 BigUint 参数,我们在终端中直接提供前两个参数,最后一个作为固定参数,十六进制编码。
提示
Erdpy 为我们提供了一些编码约定,包括:
- 我们可以使用 str: 对字符串进行编码。例如:str:MYTOKEN-123456
- 以 erd1 开头的区块链地址是自动编码的,所以不需要进一步十六进制编码
- 值真或假被自动转换为布尔值
- 默认情况下,标识为数字的值是十六进制编码的
- 类似于 0x 的参数...保持不变,因为它们被解释为已经编码的十六进制值
因此,在我们的mynonpaybleendpoint交互中,我们可以这样写:
###PARAMS
#1 - FirstBigUintArgument
#2 - SecondBigUintArgument
ADDRESS_ARGUMENT="erd14nw9pukqyqu75gj0shm8upsegjft8l0awjefp877phfx74775dsq49swp3"
THIRD_BIGUINT_ARGUMENT=1000000
myNonPayableEndpoint() {
erdpy --verbose contract call ${CONTRACT_ADDRESS} --recall-nonce \
--pem=${WALLET_PEM} \
--gas-limit=6000000 \
--proxy=${PROXY} --chain=${CHAIN_ID} \
--function="myNonPayableEndpoint" \
--arguments $address_argument $1 $2 ${THIRD_BIGUINT_ARGUMENT}\
--send || return
}
此端点的呼叫示例如下所示:
myNonPayableEndpoint 10000 100000
这将转化为(使用未编码的值以便于阅读):
myNonPayableEndpoint erd14nw9pukqyqu75gj0shm8upsegjft8l0awjefp877phfx74775dsq49swp3 10000 100000 1000000
警告
确保所有参数都有正确的编码是很重要的。否则,交易将失败。
应付端点交互
可替换 ESDT 转移
现在让我们看看下面的例子,我们想调用一个可支付端点。
myPayableEndpoint() {
method_name=str:myPayableEndpoint
my_token=str:$1
token_amount=$2
erdpy --verbose contract call ${CONTRACT_ADDRESS} --recall-nonce \
--pem=${WALLET_PEM} \
--gas-limit=6000000 \
--proxy=${PROXY} --chain=${CHAIN_ID} \
--function="ESDTTransfer" \
--arguments $my_token $token_amount $method_name\
--send || return
}
正如我们所看到的,我们调用 payable 端点的方式是调用 ESDTTransfer 函数(或任何其他传递资产和支持合约调用的函数)并提供方法名作为参数。每个传递函数的自变量顺序不同。在我们的例子中,我们在终端中指定我们想要传输的代币类型和数量,然后我们提供一个固定的输入,作为我们想要调用的 SC 函数。
不可替代的 ESDT 转会(NFT、SFT 和元 ESDT)
现在让我们假设我们想要调用一个接受 NFT 或 SFT 作为支付的端点。
# $1 = NFT/SFT Token Identifier,
# $2 = NFT/SFT Token Nonce,
# $3 = NFT/SFT Token Amount,
# $4 = Destination Address,
FIRST_BIGUINT_ARGUMENT=1000
SECOND_BIGUINT_ARGUMENT=10000
myESDTNFTPayableEndpoint() {
user_address="$(erdpy wallet pem-address $WALLET_PEM)"
method_name=str:myESDTNFTPayableEndpoint
sft_token=str:$1
sft_token_nonce=$2
sft_token_amount=$3
destination_address=$4
erdpy --verbose contract call $user_address --recall-nonce \
--pem=${WALLET_PEM} \
--gas-limit=100000000 \
--proxy=${PROXY} --chain=${CHAIN_ID} \
--function="ESDTNFTTransfer" \
--arguments $sft_token
$sft_token_nonce
$sft_token_amount
$destination_address
$method_name
${FIRST_BIGUINT_ARGUMENT}
${SECOND_BIGUINT_ARGUMENT} \
--send || return
}
首先,要调用这种类型的传递函数,我们需要传递与发送方地址相同的接收方地址。因此,在这个例子中,我们根据指定的 PEM 文件转换呼叫者的地址。现在,和ESDTTransfer
的情况一样,被调用函数的名字是ESDTNFTTransfer
。所有其他必需的数据都作为参数传递(包括目标合约的地址和端点)。在这种 NFT/SFT 传输的情况下,我们首先传递代币(标识符、随机数和数量),然后传递目的地地址和端点的名称。最后,我们传递指定方法需要的任何参数。
多 ESDT 转移
如果我们需要调用一个接受多个代币的端点(比如 2 个可替换代币和一个 NFT)。让我们看看下面的例子:
###PARAMS
# $1 = Destination Address,
# $2 = First Token Identifier,
# $3 = First Token Amount,
# $4 = Second Token Identifier,
# $5 = Second Token Amount,
# $6 = Third Token Identifier,
# $7 = Third Token Nonce,
# $8 = Third Token Identifier,
FIRST_BIGUINT_ARGUMENT=1000
SECOND_BIGUINT_ARGUMENT=10000
myMultiESDTNFTPayableEndpoint() {
user_address="$(erdpy wallet pem-address $WALLET_PEM)"
method_name=str:myMultiESDTPayableEndpoint
destination_address=$1
number_of_tokens=3
first_token=str:$2
first_token_nonce=0
first_token_amount=$3
second_token=str:$4
second_token_nonce=0
second_token_amount=$5
third_token=str:$6
third_token_nonce=$7
third_token_amount=$8
erdpy --verbose contract call $user_address --recall-nonce \
--pem=${WALLET_PEM} \
--gas-limit=100000000 \
--proxy=${PROXY} --chain=${CHAIN_ID} \
--function="MultiESDTNFTTransfer" \
--arguments $destination_address
$number_of_tokens
$first_token
$first_token_nonce
$first_token_amount
$second_token
$second_token_nonce
$second_token_amount
$third_token
$third_token_nonce
$third_token_amount
$method_name
${FIRST_BIGUINT_ARGUMENT}
${SECOND_BIGUINT_ARGUMENT} \
--send || return
}
在本例中,我们通过传输 3 个不同的代币(前两个是可替换代币,最后一个是 NFT)来调用myMultiESDTPayableEndpoint
端点。端点接受两个 BigUInt 参数。该片段的布局与 ESDTNFTTransfer 几乎相同(包括发送者与接收者相同的事实),但具有不同的参数。我们现在首先传递目的地地址和我们想要发送的 ESDT/NFT 代币的数量。然后,对于每个发送的代币,我们指定标识符、nonce(在我们的示例中,可替换代币为 0,NFT 为特定值)和数量。最后,像使用 ESDTTransfer 一样,我们传递我们想要调用的方法的名称以及该特定方法的其余参数。
提示
更多关于 ESDT 转会的信息点击这里。
查看交互
如果我们想调用一个视图函数,我们可以使用查询关键字。
###PARAMS
#1 - First argument
#2 - Second argument
myView() {
erdpy --verbose contract query ${CONTRACT_ADDRESS} \
--proxy=${PROXY} \
--function="myView"
--arguments $1 $2
}
当调用视图函数时,erdpy 将在终端中输出标准信息,以及根据所请求的数据类型格式化的结果。参数的指定方式与端点相同。