用户定义的智能合约
原文:https://docs.elrond.com/developers/gas-and-fees/user-defined-smart-contracts
对于用户定义的智能合约部署和函数调用,处理的实际气体消耗包含前面提到的两个成本组成部分——尽管值移动和数据处理组成部分很容易计算(使用前面描述的公式),但是合约执行组成部分很难精确确定先验。因此,对于该组件,我们必须依靠模拟和估计。
对于模拟,我们将使用erdpy
启动一个本地测试网(详细的设置说明可以在这里找到)。因此,在继续之前,确保您的本地 testnet 已经启动并运行。
合约部署
为了获得部署交易所需的gasLimit
(实际天然气成本),应该使用众所周知的erdpy contract deploy
命令,但是设置了--simulate
标志。
首先,为gas-limit
传递最大可能量(无猜测)。
$ erdpy --verbose contract deploy --bytecode=./counter.wasm \
--recall-nonce --gas-limit=600000000 \
--pem=~/elrondsdk/testwallets/latest/users/alice.pem \
--simulate
在输出中,寻找txGasUnits
。例如:
"txSimulation": {
...
"cost": {
"txGasUnits": 1849711,
...
}
}
注
模拟成本txGasUnits
包含成本的两个组成部分。
之后,通过再次运行模拟来检查成本模拟,但这次使用精确的gas-limit
:
$ erdpy --verbose contract deploy --bytecode=./counter.wasm \
--recall-nonce --gas-limit=1849711 \
--pem=~/elrondsdk/testwallets/latest/users/alice.pem \
--simulate
在输出中,查找status
——应该是success
:
"txSimulation": {
"execution": {
"result": {
"status": "success",
...
},
...
}
最后,让我们实际部署合约:
$ erdpy --verbose contract deploy --bytecode=./counter.wasm \
--recall-nonce --gas-limit=1849711 \
--pem=~/elrondsdk/testwallets/latest/users/alice.pem \
--send --wait-result
重要
对于部署,成本的执行部分与实例化智能合约和调用其init()
函数相关联。
如果init()
的流程依赖于输入参数或者它引用了区块链数据,那么成本也会根据这些变量而变化。确保您模拟了足够的部署场景,并增加(减少)了gas-limit
。
合约调用
为了获得合约调用所需的gasLimit
(实际天然气成本)应该首先部署合约,然后使用erdpy contract call
命令,并设置--simulate
标志。
**##### 重要
如果该合约进一步调用其他合约,请阅读下一节。
假设我们已经部署了合约(见上文),让我们获得调用它的一个端点的成本:
$ erdpy --verbose contract call erd1qqqqqqqqqqqqqpgqygvvtlty3v7cad507v5z793duw9jjmlxd8sszs8a2y \
--pem=~/elrondsdk/testwallets/latest/users/alice.pem \
--function=increment\
--recall-nonce --gas-limit=600000000\
--simulate
在输出中,寻找txGasUnits
。例如:
"txSimulation": {
...
"cost": {
"txGasUnits": 1225515,
...
}
}
最后,让我们称之为合约:
$ erdpy --verbose contract call erd1qqqqqqqqqqqqqpgqygvvtlty3v7cad507v5z793duw9jjmlxd8sszs8a2y \
--pem=~/elrondsdk/testwallets/latest/users/alice.pem \
--function=increment\
--recall-nonce --gas-limit=1225515\
--send --wait-result
重要
如果被调用函数的流程依赖于输入参数或者它引用区块链数据,那么成本也会根据这些变量而变化。确保您为合约呼叫模拟了足够多的场景,并增加(减少)了gas-limit
。
合约调用(异步)其他合约
重要
本节中的文档是初步的,可能会更改。此外,仅涵盖异步调用。
在继续之前,请确保您首先浏览了以下内容:
假设我们有两个合约:A
和B
,其中A::foo(addressOfB)
异步调用B::bar()
(例如使用asyncCall()
)。
让我们部署合约A
和B
:
$ erdpy --verbose contract deploy --bytecode=./a.wasm \
--recall-nonce --gas-limit=5000000 \
--pem=~/elrondsdk/testwallets/latest/users/alice.pem \
--send --wait-result --outfile=a.json
$ erdpy --verbose contract deploy --bytecode=./b.wasm \
--recall-nonce --gas-limit=5000000 \
--pem=~/elrondsdk/testwallets/latest/users/alice.pem \
--send --wait-result --outfile=b.json
假设A
在erd1qqqqqqqqqqqqqpgqfzydqmdw7m2vazsp6u5p95yxz76t2p9rd8ss0zp9ts
展开,B
在erd1qqqqqqqqqqqqqpgqj5zftf3ef3gqm3gklcetpmxwg43rh8z2d8ss2e49aq
展开,让模拟 A::foo(addressOfB)
(首先通过一个足够大的或最大的gas-limit
):
$ export hexAddressOfB=0x$(erdpy wallet bech32 --decode erd1qqqqqqqqqqqqqpgqj5zftf3ef3gqm3gklcetpmxwg43rh8z2d8ss2e49aq)
$ erdpy --verbose contract call erd1qqqqqqqqqqqqqpgqfzydqmdw7m2vazsp6u5p95yxz76t2p9rd8ss0zp9ts \
--pem=~/elrondsdk/testwallets/latest/users/alice.pem \
--function=foo\
--recall-nonce --gas-limit=50000000\
--arguments ${hexAddressOfB}\
--simulate
在输出中,查找模拟成本(如上所示):
"txSimulation": {
...
"cost": {
"txGasUnits": 3473900,
...
}
}
模拟成本代表调用A::foo()
、B::bar()
和A::callBack()
的实际天然气成本。
然而,上面的模拟成本并不是我们要用作gasLimit
的值。如果我们这样做,就会出现错误not enough gas
。
在到达对A::foo()
内部B::bar()
的调用时,Elrond VM 在运行时检查剩余气体和临时锁定(保留)其一部分,以允许一旦对B::bar()
的调用返回就执行A::callBack()
。
关于 VM 气体调度,前述运行时 的剩余气体必须满足以下条件,以使临时气锁保留成功:***
onTheSpotRemainingGas > gasToLockForCallback
gasToLockForCallback =
costOf(AsyncCallStep) +
costOf(AsyncCallbackGasLock) +
codeSizeOf(callingContract) * costOf(AoTPreparePerByte)
注
后续的异步调用(由异步调用合约执行的异步调用)也需要临时气锁。
在我们的例子中,A
有 453 个字节,那么gasToLockForCallback
就是(截至 2022 年 2 月):
gasToLockForCallback = 100000 + 4000000 + 100 * 453 = 4145300
因此,gasLimit
的值应该是:
simulatedCost < gasLimit < simulatedCost + gasToLockForCallback
对于我们的示例,这将是:
3473900 < gasLimit < 7619200
重要
截至 2022 年 2 月,对于调用其他合约的合约,使用 erdpy 不容易确定成功执行所需的最低gasLimit
。虽然这个值可以通过仔细检查本地 testnet 日志来确定,但目前推荐的方法是从上面不等式的右边(simulatedCost + gasToLockForCallback
)开始,并在模拟调用和寻找成功输出的同时逐渐减小值。
对于我们的示例,让我们使用以下gasLimit
: 7619200, 7000000, 6000000
值进行模拟:
$ erdpy --verbose contract call erd1qqqqqqqqqqqqqpgqfzydqmdw7m2vazsp6u5p95yxz76t2p9rd8ss0zp9ts \
--pem=~/elrondsdk/testwallets/latest/users/alice.pem \
--function=foo\
--recall-nonce --gas-limit=7619200\
--arguments ${hexAddressOfB}\
--simulate
... inspect output (possibly testnet logs); execution is successful
erdpy --verbose contract call erd1qqqqqqqqqqqqqpgqfzydqmdw7m2vazsp6u5p95yxz76t2p9rd8ss0zp9ts \
--pem=~/elrondsdk/testwallets/latest/users/alice.pem \
--function=foo\
--recall-nonce --gas-limit=7000000\
--arguments ${hexAddressOfB}\
--simulate
... inspect output (possibly testnet logs); execution is successful
erdpy --verbose contract call erd1qqqqqqqqqqqqqpgqfzydqmdw7m2vazsp6u5p95yxz76t2p9rd8ss0zp9ts \
--pem=~/elrondsdk/testwallets/latest/users/alice.pem \
--function=foo\
--recall-nonce --gas-limit=6000000\
--arguments ${hexAddressOfB}\
--simulate
... inspect output (possibly testnet logs); ERROR: out of gas when executing B::bar()
因此,在我们的例子中,gasLimit
的合理值应该是 7000000。
重要
如果需要消耗超过gasToLockForCallback
的高油耗回叫(不推荐),应通过检查合约调用输出和测试网络日志,适当增加上述gasLimit
不等式的右边。***