创建文章

We are looking for publications that demonstrate building dApps or smart contracts!
See the full list of Gitcoin bounties that are eligible for rewards.

Article Thumbnail

智能合约的高级用法:分层阈值

保护资金流是 Algorand 智能合约 (ACS1) 的一个主要设计原则。今天我们来看看典型公司结构中授权资金流的现实用例。尽管部门/角色结构可能是分层级的,但批准流程可以设计成分布式、去中心化且安全的。我们将使用命令行工具构建并测试一个托管合约账户,实现分级阈值批准。您将学到怎样使用 tealsignLogicSig 添加签名,以及如何在合约代码里使用 ed25519verify

我们先从具体示例开始:假设一个典型的公司组织,希望实现财务账户支出控制。这是个由多名雇员组成的层级组织,账户签名权限纷繁复杂,相互重叠。值得庆幸的是,每名雇员都在 Algorand 网络上拥有唯一的账户。通过设计安全的合约实现分级阈值评估,该公司可向自己的支出协议引入监管和冗余。

公司结构如下:

- C-Level Execs
        - CEO
        - COO
        - CFO
- Accounting Department
        - Accounting Manager
                - Accountant1
                - Accountant2
                - Accountant3

所需支出协议必须强制执行由大多数高管法定人数的会计部门人员授权。具体讲,下面两个条件都必须满足:

1. A minimum of 2 out of 3 C-Level Execs approve
2. (At least 1 Accountant AND the Account Manager approve) OR (all 3 Accountants approve)

这为我们使用 TEAL 语言构建财务账户提供了所需细节。我们开始写代码吧!打开您最喜欢的文本编辑器,新建一个名为hierarchical-threshold.tmpl的文件,然后把下面的代码复制粘贴进去,再保存一下:

// The following contract implements hierarchical threshold evaluation with the following semantics:
// ( ( Majority C-Level Execs  ) AND (                  Accounting Department Review                   ) )
// ( ( Majority: CEO, COO, CFO ) AND ( ( Any Accountant  AND Accounting Manager ) OR All Accountants ) ) )
// ( (   2of3: CEO, COO, CFO   ) AND ( ( 1of3: ACCT1,2,3 AND     ACCT_MNGR      ) OR 3of3: ACCT1,2,3 ) ) )
// ( (         2 of 3          ) AND ( (     1 of 3      AND         1          ) OR     3 of 3      ) ) )

// *** SIGNATURE VERIFICATIONS ***
// First, verify all signatures from the C-Level Execs
txn TxID       // "data"
arg 0          // "signature"
addr TMPL_CEO  // "public key"
ed25519verify

txn TxID
arg 1
addr TMPL_COO
ed25519verify
+

txn TxID
arg 2
addr TMPL_CFO
ed25519verify
+

// Store sum in scratch space 0
store 0

// Next, verify signatures from the Accountants
txn TxID
arg 3
addr TMPL_ACCT1
ed25519verify

txn TxID
arg 4
addr TMPL_ACCT2
ed25519verify
+

txn TxID
arg 5
addr TMPL_ACCT3
ed25519verify
+

// Store in scratch space 0
store 1

// Next, verify signature from Accounting Manager
txn TxID
arg 6
addr TMPL_ACCT_MNGR
ed25519verify

// Store sum in scratch space 2
store 2

// *** THRESHOLD CALCULATIONS ***
// Calculate C-Level Execs Approvals (need majority, threshold 2)
load 0
int 2
>=

// Calculate Accounting Department (lhs)
// Calculate Accountants (need any, threshold 1)
load 1
int 1
>=

// Calculate Accounting Manager (need this, threshold 1 implicit)
load 2

// *** FIRST LOGIC EVALUATION ***
// Evaluate Accounting Department (lhs)
&&

// *** RESUME THRESHOLD CALCULATIONS ***
// Calculate Accounting Department (rhs)
// Calculate Accountants (need all, threshold 3)
load 2
int 3
>=

// *** RESUME LOGIC EVALUATIONS ***
// Evaluate Accounting Department (either lhs OR rhs)
||

// Evaluate C-Level Execs AND Accounting Department
&&

// *** FEES ***
txn Fee
int TMPL_FEE
<=
&&

这代码够长的,但不用担心,很好看懂。我们来深入解读一下。

1 至 5 行提供了我们上面定义的语义,我们的授权业务逻辑。

// The following contract implements hierarchical threshold evaluation with the following semantics:
// ( ( Majority C-Level Execs  ) AND (                  Accounting Department Review                   ) )
// ( ( Majority: CEO, COO, CFO ) AND ( ( Any Accountant  AND Accounting Manager ) OR All Accountants ) ) )
// ( (   2of3: CEO, COO, CFO   ) AND ( ( 1of3: ACCT1,2,3 AND     ACCT_MNGR      ) OR 3of3: ACCT1,2,3 ) ) )
// ( (         2 of 3          ) AND ( (     1 of 3      AND         1          ) OR     3 of 3      ) ) )

注意所有的 // *** SECTION HEADER *** 行。此合约代码中有四个基本代码节:
- 签名验证
- 阈值计算
- 逻辑评估
- 费用

信息

这张图示很直观地展现了程序参数暂存空间交易之间的关系。这些术语贯穿本文,最好能切实掌握。

签名验证代码节中包含用于高管、会计和会计主管的代码块。这些代码块功能类似,所以我们从 8 至 12 行看起。

// *** SIGNATURE VERIFICATIONS ***
// First, verify all signatures from the C-Level Execs
txn TxID       // "data"
arg 0          // "signature"
addr TMPL_CEO  // "public key"
ed25519verify

txn TxID 读取 transactionID 并压栈。接下来是 arg 0 读取第一个交易参数并压栈(我们将使用 tealsign 将此参数加至下面的交易)。然后 addr TMPL_CEO 也向栈中压入一个账户,令栈上总共存放着 3 项数据。TMPL_CEO 是个模板占位符,将被下面生成的 CEO 实际账户地址替代。注意这几行代码的注释:数据、签名和公钥。ed25519verify 需按顺序取这三个值,所以将之从栈中弹出。现在执行加密签名验证,并将作为结果的 true 或 false(1 或 0)压入栈中。综上,这四行代码告诉我们 CEO 是否在评估过程中为此交易提供了有效签名。

txn TxID
arg 2
addr TMPL_CFO
ed25519verify
+

// Store sum in scratch space 0
store 0

接下来,签名验证继续到 COO。注意,18 行有个 +,会从栈顶弹出两项数据(CEO 和 COO 的签名验证结果),两值相加后结果压入栈顶,后面对 CFO 的操作与之类似。27 行,store 0 将总和从栈中弹出,移至暂存空间以供后用。此时栈是空的,程序继续对会计部门人员执行签名验证,结果存入暂存空间。

下一个代码节头是阈值计算。61 行 load 0 从暂存空间取出高管验证结果总和,将之压入栈中。我们需要阈值 2,所以 int 2 将此值压栈。>= 出栈这两个值,执行逻辑判断,然后返回 true 或 false(1 或 0)压入栈中,其余人员照此处理。这些计算结果都留在栈中,供评估代码节使用,确保所需人员达到所需签名阈值。

// Calculate C-Level Execs Approvals (need majority, threshold 2)
load 0
int 2
>=

首个逻辑评估出现在 76 行。这个代码块检查会计部门评估的左侧是否至少有一名会计和主管签名。&& 从栈顶弹出这两个已计算出的值,返回逻辑运算结果。

// *** FIRST LOGIC EVALUATION ***
// Evaluate Accounting Department (lhs)
&&

接下来计算针对所有会计(无需主管)的右侧。load 2 是已计算出的值,int 3 是阈值。>= 返回逻辑判断结果并压栈。

// *** RESUME THRESHOLD CALCULATIONS ***
// Calculate Accounting Department (rhs)
// Calculate Accountants (need all, threshold 3)
load 2
int 3
>=

87 行继续逻辑评估 || 运算符出栈会计部门计算的两侧,执行 or 运算,然后返回结果。各部门最后的逻辑评估,是对 63 行压栈的高管评估结果和上面的会计部门评估结果执行 and 运算。此时,我们已成功评估了合约代码内预期的业务逻辑。

// *** RESUME LOGIC EVALUATIONS ***
// Evaluate Accounting Department (either lhs OR rhs)
||

// Evaluate C-Level Execs AND Accounting Department
&&

92 至 96 行(下面我们会替换掉模板占位符)以 && 快速检查费用。合约执行结束后栈中仅留 0 或 1,相当于交易评估中的 REJECT(拒绝)或 PASS(通过)。

// *** FEES ***
txn Fee
int TMPL_FEE
<=
&&

我们来看代码运行。我们将使用 bashgoalalgokey 离线测试,然后一旦您确信一切定义安全,就可以部署这个智能合约了。首先,设置本地环境。

# set the path for your temporary directory used to store account keys
TEMPDIR="/TEMPDIR"

# set your wallet name
WALLET_NAME="Your Wallet Name"

# Set the appropriate algod values for your environment. These are the defaults for Sandbox on TestNet.
ALGOD_HOST_PORT="localhost:4001"
ALGOD_HEADER="x-algo-api-token:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"

# ensure your PATH is set properly, else modify the following and set -d /path/to/goal and -d /path/to/algokey
GOAL_CMD="goal -w ${WALLET_NAME}"
ALGOKEY_CMD="algokey"

首先用 algokey 为全体人员设立新账户。复制粘贴每行代码到您的终端中执行。

# C-Level Execs
export CEO=$(algokey generate -f $TEMPDIR/CEO | tail -1 | awk '{ print $3 }')
export COO=$(algokey generate -f $TEMPDIR/COO | tail -1 | awk '{ print $3 }')
export CFO=$(algokey generate -f $TEMPDIR/CFO | tail -1 | awk '{ print $3 }')

# Accounting Department
export ACCT1=$(algokey generate -f $TEMPDIR/ACCT1 | tail -1 | awk '{ print $3 }')
export ACCT2=$(algokey generate -f $TEMPDIR/ACCT2 | tail -1 | awk '{ print $3 }')
export ACCT3=$(algokey generate -f $TEMPDIR/ACCT3 | tail -1 | awk '{ print $3 }')
export ACCT_MNGR=$(algokey generate -f $TEMPDIR/ACCT_MNGR | tail -1 | awk '{ print $3 }')

export RECIPIENT="GD64YIY3TWGDMCNPP553DZPPR6LDUSFQOIJVFDPPXWEG3FVOJCCDBBHU5A" # TestNet Faucet

屏幕上什么都没有出现,因为我们将产生的账户标识赋值给环境变量了。您可以 echo $CEO 查看 CEO 账户,$ACCT_MNGR 等账户同理。值得注意的是,每个账户的私钥密钥文件存储在您的 $TEMPDIR 目录下。下面的 tealsign 将访问这些密钥文件。这里我们只是测试,生产环境中您应妥善保管这些密钥文件。

现在我们需要更新模板代码,用上面设置的相关本地变量替换掉 TMPL_ 值。例如,用来自 echo $CEO 的值替换掉 TMPL_CEO。别忘了代码末尾的 TMPL_FEE。需要更新 8 个值。另存为 hierarchical-threshold.teal 以编写出最终的 TEAL 程序。

接下来,我们编译代码,将结果地址赋值给 $CONTRACT

# Compile contract
export CONTRACT=$(${GOAL_CMD} clerk compile hierarchical-threshold.teal | awk '{ print $2 }')

是时候构建交易了。我们用 goal 构建一个交易,其中 LogicSig 从合约源代码发出,写出 tosign.tx 文件。

# Build the unsigned transaction, write out to file:
${GOAL_CMD} clerk send --from-program hierarchical-threshold.teal --to $RECIPIENT --amount 100000 --out tosign.tx

查看这个交易:

${GOAL_CMD} clerk inspect tosign.tx

结果是:

tosign.tx[0]
{
  "lsig": {
    "l": "// version 1\nintcblock 2 1 3\nbytecblock 0x4c33ba3ebce473aa2e17bf35587c4c7279b1c6676aeec4a12efdba689442398e 0xb1f66c928cae970ffc419241f579ae340530d0e1c88cac4bf17cc5b3023eb364 0x09714edc7199e3677755994b38286b11f9e10be0fa5712a596ea99d0aa38f378 0x2ff0d5f30d23cd8ca89120704b3aa9f3d74bbaeeebe1cd6ab94b62117ddefc49 0xd6def622ae4415ec24b8819aa6d86cadd9c1366783b0259a3bed447b67a87a83 0xa569ce551fc700418dc8e62217c384b1feedadbe7673094778eb20c6a6ed7b0c 0xf3eee3bf0d798824187c24e60d70590b5325c11a3c88fcefdb5c83eea08a62aa\ntxn TxID\narg_0\nbytec_0\ned25519verify\ntxn TxID\narg_1\nbytec_1\ned25519verify\n+\ntxn TxID\narg_2\nbytec_2\ned25519verify\n+\nstore 0\ntxn TxID\narg_3\nbytec_3\ned25519verify\ntxn TxID\narg 4\nbytec 4\ned25519verify\n+\ntxn TxID\narg 5\nbytec 5\ned25519verify\n+\nstore 1\ntxn TxID\narg 6\nbytec 6\ned25519verify\nstore 2\nload 0\nintc_0\n>=\nload 1\nintc_1\n>=\nload 2\n&&\nload 2\nintc_2\n>=\n||\n&&\n"
  },
  "txn": {
    "amt": 100000,
    "fee": 1000,
    "fv": 151598,
    "gen": "REKEY-v1",
    "gh": "aT+oawSj/oxSWPNILM7O7EkuVE1rPxD5lLTgY+kvVCA=",
    "lv": 152598,
    "note": "ec5eeJ2d0aI=",
    "rcv": "GD64YIY3TWGDMCNPP553DZPPR6LDUSFQOIJVFDPPXWEG3FVOJCCDBBHU5A",
    "snd": "AFLMTJGTZW2K7BZMNSEX6EP2DUQ7L4GAAIAEBRG5QVM4ONL4QUOPDZ5MYU",
    "type": "pay"
  }
}

注意,“lsig”节包含此 teal 程序,但不含任何参数。我们用 goal clerk dryrun 进行离线测试:

${GOAL_CMD} clerk dryrun --txfile tosign.tx

结果:

tx[0] cost=13343 trace:
  1 intcblock => <empty stack>
  6 bytecblock => <empty stack>
239 txn 0x17 => (85b1fd66c2fca6df093e46936d00b378dbebaad31261a325c81aaa52c7b3cf34) 
241 arg_0 => (85b1fd66c2fca6df093e46936d00b378dbebaad31261a325c81aaa52c7b3cf34) 
241 cannot load arg[0] of 0

REJECT
ERROR: cannot load arg[0] of 0

由于未将任何签名作为参数传入,合约无法评估。我们用带参数(在下面描述)的 goal clerk tealsign 向未签名的交易添加第一个签名:

${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/CEO --lsig-txn tosign.tx --set-lsig-arg-idx 0
${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/CEO --lsig-txn tosign.tx --set-lsig-arg-idx 1
${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/CEO --lsig-txn tosign.tx --set-lsig-arg-idx 2
${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/CEO --lsig-txn tosign.tx --set-lsig-arg-idx 3
${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/CEO --lsig-txn tosign.tx --set-lsig-arg-idx 4
${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/CEO --lsig-txn tosign.tx --set-lsig-arg-idx 5
${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/CEO --lsig-txn tosign.tx --set-lsig-arg-idx 6

第一个参数是 --signed-txid,表明我们打算签名这个交易的 ID(而不是什么任意数据)。ed25519verify 在 teal 代码中评估时会将之用作“数据”。第二个参数 –keyfile $TEMPDIR/CEO 是 CEO 持有的私钥,此处用于签名。参数 –lsig-txn tosign.tx 定义要添加签名的交易文件。最后,参数 –set-lsig-arg-idx 0 告知签名将在哪个索引位置出现。注意,CEO 将向全部 7 个索引位置添加签名。我们再次用 ${GOAL_CMD} clerk dryrun –txfile tosign.tx 测试仅 CEO 签名的交易。

结果:

tx[0] cost=13343 trace:
  1 intcblock => <empty stack>
  6 bytecblock => <empty stack>
239 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
241 arg_0 => (f8c137934ea4b498bfa8f87b0c426d46fb47dcb31a900b6641ebbf01c70d139db3f5c75705e939543185dc2778e885aba773c1b217f9534963bf7c2ddafd2403) 
242 bytec_0 => (4c33ba3ebce473aa2e17bf35587c4c7279b1c6676aeec4a12efdba689442398e) 
243 ed25519verify => (1 0x1) 
244 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
246 arg_1 => (f8c137934ea4b498bfa8f87b0c426d46fb47dcb31a900b6641ebbf01c70d139db3f5c75705e939543185dc2778e885aba773c1b217f9534963bf7c2ddafd2403) 
247 bytec_1 => (b1f66c928cae970ffc419241f579ae340530d0e1c88cac4bf17cc5b3023eb364) 
248 ed25519verify => (0 0x0) 
249 + => (1 0x1) 
250 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
252 arg_2 => (f8c137934ea4b498bfa8f87b0c426d46fb47dcb31a900b6641ebbf01c70d139db3f5c75705e939543185dc2778e885aba773c1b217f9534963bf7c2ddafd2403) 
253 bytec_2 => (09714edc7199e3677755994b38286b11f9e10be0fa5712a596ea99d0aa38f378) 
254 ed25519verify => (0 0x0) 
255 + => (1 0x1) 
256 store 0x00 => <empty stack>
258 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
260 arg_3 => (f8c137934ea4b498bfa8f87b0c426d46fb47dcb31a900b6641ebbf01c70d139db3f5c75705e939543185dc2778e885aba773c1b217f9534963bf7c2ddafd2403) 
261 bytec_3 => (2ff0d5f30d23cd8ca89120704b3aa9f3d74bbaeeebe1cd6ab94b62117ddefc49) 
262 ed25519verify => (0 0x0) 
263 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
265 arg 0x04 => (f8c137934ea4b498bfa8f87b0c426d46fb47dcb31a900b6641ebbf01c70d139db3f5c75705e939543185dc2778e885aba773c1b217f9534963bf7c2ddafd2403) 
267 bytec 0x04 => (d6def622ae4415ec24b8819aa6d86cadd9c1366783b0259a3bed447b67a87a83) 
269 ed25519verify => (0 0x0) 
270 + => (0 0x0) 
271 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
273 arg 0x05 => (f8c137934ea4b498bfa8f87b0c426d46fb47dcb31a900b6641ebbf01c70d139db3f5c75705e939543185dc2778e885aba773c1b217f9534963bf7c2ddafd2403) 
275 bytec 0x05 => (a569ce551fc700418dc8e62217c384b1feedadbe7673094778eb20c6a6ed7b0c) 
277 ed25519verify => (0 0x0) 
278 + => (0 0x0) 
279 store 0x01 => <empty stack>
281 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
283 arg 0x06 => (f8c137934ea4b498bfa8f87b0c426d46fb47dcb31a900b6641ebbf01c70d139db3f5c75705e939543185dc2778e885aba773c1b217f9534963bf7c2ddafd2403) 
285 bytec 0x06 => (f3eee3bf0d798824187c24e60d70590b5325c11a3c88fcefdb5c83eea08a62aa) 
287 ed25519verify => (0 0x0) 
288 store 0x02 => <empty stack>
290 load 0x00 => (1 0x1) 
292 intc_0 => (2 0x2) 
293 >= => (0 0x0) 
294 load 0x01 => (0 0x0) 
296 intc_1 => (1 0x1) 
297 >= => (0 0x0) 
298 load 0x02 => (0 0x0) 
300 && => (0 0x0) 
301 load 0x02 => (0 0x0) 
303 intc_2 => (3 0x3) 
304 >= => (0 0x0) 
305 || => (0 0x0) 
306 && => (0 0x0) 

REJECT

总之,评估不通过,但至少我们看到程序代码完整执行了。注意程序顶部直至 ed25519verify 的高亮代码行。这几行判断为真,所以 arg 0 是 CEO 有效签名。几行之后您会发现针对 COO 的签名检查,但结果将会是假,且所有其他签名检查的结果都将是假。正如预期的那样。

接下来,我们模拟向这个部分签名的交易文件传递另一位高管。goal clerk tealsign 将覆盖此索引位置上 --set-lsig-arg-idx n 指定的签名,从而替换掉无效 CEO 签名。

# Add the signature from the CFO, then dryrun
${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/CFO --lsig-txn tosign.tx --set-lsig-arg-idx 2
${GOAL_CMD} clerk dryrun --txfile tosign.tx

部分结果:

tx[0] cost=13343 trace:
  1 intcblock => <empty stack>
  6 bytecblock => <empty stack>
239 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
241 arg_0 => (f8c137934ea4b498bfa8f87b0c426d46fb47dcb31a900b6641ebbf01c70d139db3f5c75705e939543185dc2778e885aba773c1b217f9534963bf7c2ddafd2403) 
242 bytec_0 => (4c33ba3ebce473aa2e17bf35587c4c7279b1c6676aeec4a12efdba689442398e) 
243 ed25519verify => (1 0x1) 
244 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
246 arg_1 => (f8c137934ea4b498bfa8f87b0c426d46fb47dcb31a900b6641ebbf01c70d139db3f5c75705e939543185dc2778e885aba773c1b217f9534963bf7c2ddafd2403) 
247 bytec_1 => (b1f66c928cae970ffc419241f579ae340530d0e1c88cac4bf17cc5b3023eb364) 
248 ed25519verify => (0 0x0) 
249 + => (1 0x1) 
250 txn 0x17 => (bdf1688e1afa7e2bebfd8f6ca84d6b2043950edcf88bb49a32ac52e2c428fcb7) 
252 arg_2 => (44b24a60aaa259c4833131e54d585b7e45cf625032707f65089c2b714d00ccd86d2df98990a06482ef9a95f70f81c3c08709a23f1ff8edd716ba16a4aed43c01) 
253 bytec_2 => (09714edc7199e3677755994b38286b11f9e10be0fa5712a596ea99d0aa38f378) 
254 ed25519verify => (1 0x1) 
255 + => (2 0x2) 
256 store 0x00 => <empty stack>
[...]

现在 CEO 和 CFO 都正确签名了。查找来自 CFO 的 arg_2,看到确实通过了验证。在这下面,注意对高管的计算结果之和为 2,且存储到了暂存空间(清空了栈)。

我会将下一代码块都注释掉,这样您就可以添加自己想要的各个签名了。直接删除 # 符号并执行代码行以添加特定签名。

# Keep adding signatures
#${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/CFO --lsig-txn tosign.tx --set-lsig-arg-idx 2
#${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/ACCT1 --lsig-txn tosign.tx --set-lsig-arg-idx 3
#${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/ACCT2 --lsig-txn tosign.tx --set-lsig-arg-idx 4
#${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/ACCT3 --lsig-txn tosign.tx --set-lsig-arg-idx 5
#${GOAL_CMD}  clerk tealsign --sign-txid --keyfile $TEMPDIR/ACCT_MNGR --lsig-txn tosign.tx --set-lsig-arg-idx 6

${GOAL_CMD} clerk dryrun --txfile tosign.tx 测试该评估,然后查看输出。继续在恰当的参数索引位置添加有效签名,直至您 PASS(通过):

tx[0] cost=13343 trace:
  1 intcblock => <empty stack>
  6 bytecblock => <empty stack>
239 txn 0x17 => (8d450db9be2e9b923ffe1a98360e55325e59f85d2e366468eab4215135412cc7) 
241 arg_0 => (8aede83c97785491e53b74c40599dd246a2c91074965cbabea1941585cc2e7735936201579c9e83a0274f5ce9ea41b0f64f02ff4f49933ee49ff29e8b9cebe07) 
242 bytec_0 => (4c33ba3ebce473aa2e17bf35587c4c7279b1c6676aeec4a12efdba689442398e) 
243 ed25519verify => (0 0x0) 
244 txn 0x17 => (8d450db9be2e9b923ffe1a98360e55325e59f85d2e366468eab4215135412cc7) 
246 arg_1 => (8aede83c97785491e53b74c40599dd246a2c91074965cbabea1941585cc2e7735936201579c9e83a0274f5ce9ea41b0f64f02ff4f49933ee49ff29e8b9cebe07) 
247 bytec_1 => (b1f66c928cae970ffc419241f579ae340530d0e1c88cac4bf17cc5b3023eb364) 
248 ed25519verify => (1 0x1) 
249 + => (1 0x1) 
250 txn 0x17 => (8d450db9be2e9b923ffe1a98360e55325e59f85d2e366468eab4215135412cc7) 
252 arg_2 => (acd856a4996ef84cfd123920adf874a2e34ff4a9fccc7ea021c934f6fd2096a26f0ac8ed1a194412283f6f35e62a6b766459cbff7737736e5534169d66bb8c0a) 
253 bytec_2 => (09714edc7199e3677755994b38286b11f9e10be0fa5712a596ea99d0aa38f378) 
254 ed25519verify => (1 0x1) 
255 + => (2 0x2) 
256 store 0x00 => <empty stack>
258 txn 0x17 => (8d450db9be2e9b923ffe1a98360e55325e59f85d2e366468eab4215135412cc7) 
260 arg_3 => (8aede83c97785491e53b74c40599dd246a2c91074965cbabea1941585cc2e7735936201579c9e83a0274f5ce9ea41b0f64f02ff4f49933ee49ff29e8b9cebe07) 
261 bytec_3 => (2ff0d5f30d23cd8ca89120704b3aa9f3d74bbaeeebe1cd6ab94b62117ddefc49) 
262 ed25519verify => (0 0x0) 
263 txn 0x17 => (8d450db9be2e9b923ffe1a98360e55325e59f85d2e366468eab4215135412cc7) 
265 arg 0x04 => (8aede83c97785491e53b74c40599dd246a2c91074965cbabea1941585cc2e7735936201579c9e83a0274f5ce9ea41b0f64f02ff4f49933ee49ff29e8b9cebe07) 
267 bytec 0x04 => (d6def622ae4415ec24b8819aa6d86cadd9c1366783b0259a3bed447b67a87a83) 
269 ed25519verify => (0 0x0) 
270 + => (0 0x0) 
271 txn 0x17 => (8d450db9be2e9b923ffe1a98360e55325e59f85d2e366468eab4215135412cc7) 
273 arg 0x05 => (c4a63f2d93ff7c39ba4468626d3e7f41035c1041081fe07d45e1b64a62c84908a042d018aebc0100faea188cdf67aaac912b10124df5cef3c67b3aa05dee7a0c) 
275 bytec 0x05 => (a569ce551fc700418dc8e62217c384b1feedadbe7673094778eb20c6a6ed7b0c) 
277 ed25519verify => (1 0x1) 
278 + => (1 0x1) 
279 store 0x01 => <empty stack>
281 txn 0x17 => (8d450db9be2e9b923ffe1a98360e55325e59f85d2e366468eab4215135412cc7) 
283 arg 0x06 => (6af41a3dbcefe6cb3fa4975e2d4883f53a94c0fcced5f299344966ef36a1cb66eb15a34508cf0e5e5ab82f9f063c4258eed8bf6672c92e77b010a5fbfa0c060d) 
285 bytec 0x06 => (f3eee3bf0d798824187c24e60d70590b5325c11a3c88fcefdb5c83eea08a62aa) 
287 ed25519verify => (1 0x1) 
288 store 0x02 => <empty stack>
290 load 0x00 => (2 0x2) 
292 intc_0 => (2 0x2) 
293 >= => (1 0x1) 
294 load 0x01 => (1 0x1) 
296 intc_1 => (1 0x1) 
297 >= => (1 0x1) 
298 load 0x02 => (1 0x1) 
300 && => (1 0x1) 
301 load 0x02 => (1 0x1) 
303 intc_2 => (3 0x3) 
304 >= => (0 0x0) 
305 || => (1 0x1) 
306 && => (1 0x1) 

 - pass -
 ```

Now that your contract is passing, you may fund the escrow account and begin using it as the company treasury. Keep those keyfiles secure, as they will be required to approve transaction on the network.

```bash
# TODO: Fund the $CONTRACT account so sending really works, else just continue use `goal clerk dryrun` for testing
${GOAL_CMD} clerk send --from $FUNDED_ACCOUNT --to $CONTRACT --amount $AMOUNT

准备好广播通过的交易了吗?确保托管账户中存有资金,然后:

${GOAL_CMD} clerk rawsend --filename tosign.tx

任何时候您都可以删除部分签名的交易文件,然后构建新的交易文件重新开始:

rm tosign.tx*
${GOAL_CMD} clerk send --from-program hierarchical-threshold.teal --to $RECIPIENT --amount 100000 --out tosign.tx

可以从这个托管合约账户参与批准交易的人员组合很多。此 TEAL 程序能保证公司仅商定的人员能够授权财务账户支出交易。程序提供了角色冗余,可以应对某个签名者联系不到或弄丢了密钥的情况。如果某个密钥遗失,应生成定义有相应替代密钥的新 teal 程序,其他授权签名者应批准关闭旧合约账户,改用新合约账户。

本文展示了如何设计和实现能够在真实公司中运作的 TEAL 语言智能合约账户。合约使用分级阈值签名评估,强制既定人员协同授权交易。请在 Algorand 开发者论坛上反馈您的意见和建议,并订阅开发者周报,获取 Algorand 最新功能更新的资讯。