Calling The Generic Multisig In Pure Shell
This is a quite low-level step-by-step example of calling a contract with
arguments from a generic-multisig contract — hence building a Michelson lambda,
serializing it, and signing it. All of it in pure (POSIX) shell with
tezos-client.
There are higher-level, and likely more usable versions, like
github.com/TessellatedGeometry/multisig-command-compiler
using SmartPy, or the merge-request
tezos/tezos!1857
adding commands to tezos-client.
The full shell script is in the following gist:
gist.github.com/smondet/0ea3f22375a309892fa5855cb7d7c1d2,
it includes the Markdown prose as comments too.
⁂
Let's setup a silent sandbox,
and configure tezos-client for it:
docker run --rm --name my-sandbox --detach -p 20000:20000 \
tqtezos/flextesa:20201214 delphibox start
tezos-client --endpoint http://localhost:20000 config update
tezos-client import secret \
key alice unencrypted:edsk3QoqBuvdamxouPhin7swCvkQNgq4jP5KZPbwWNnwdZpSpJiEbq --force
tezos-client import secret key \
bob unencrypted:edsk3RFfvaFaxbHx8BMtEW1rKQcPtDML3LXjNqMNLCzC3wLC1bWbAt --force (don't forget to docker kill my-sandbox when you're done).
This is the contract that we want to call from the multisig:
it just stores the sender and the argument it is called with, let's
call it “target”:
where:
aliceis the whale account from the sandbox, andNoneis the initialization of the storage.
The multisig contract we want to use is there:
Check-out also generic_multisig.v, its formalization and correctness proofs in Coq.
Let's download it:
And originate it:
where:
- The storage looks like:
(Pair <initialize-counter> (Pair <signature-threshold> {<public-keys>})). - We set the threshold to
1for simplicity. "edpkvGfYw3LyB1UcCahKQk4rF2tvbMUk8GFiTuMjL75uGXrpvKXhjn"isbob's public key (does not have to have any balance on chain, it is just a signer).
Now the fun part, let's build, pack, and sign a Michelson expression that instructs the multisig to call the “target” contract.
We need Base58 (“KT1”) addresses, because we cannot just use
tezos-client aliases in Michelson expressions:
The first time we “know” that the replay-protection counter is 0:
But in general we want to get it from the contract storage, so,
very elegantly, we sed it out:
OK, here is the meat^Wprotein — a Michelson expression of type
(lambda unit (list operation)):
cat > /tmp/lambda.tz <<EOF
{
DROP; # This is the Unit value the lambda is called on.
# We build a list with CONS, so we start with the empty one:
NIL operation;
# One call to TRANSFER_TOKENS to build an operation:
{ # ← this pair of braces is just for esthetics.
PUSH address "$target_kt1"; # The target address,
CONTRACT string; # transformed into a contract of target's type.
ASSERT_SOME; # CONTRACT returns an option, we want the value.
PUSH mutez 0; # The transfer amount is 0 mutez.
PUSH string "hello-$counter"; # The argument passed to the target contract.
TRANSFER_TOKENS;
};
CONS; # Finally, we build the list of one operation, leave it on the stack.
}
EOF We remove the comments, and “flatten” all of the above to avoid
dealing with tezos-client's extreme pedantism about the indentation
of Michelson:
The argument passed to the multisig's main entrypoint is the
counter + the lambda as an action
(the action is of type (or <action> <change-keys>)):
To avoid replay attacks on the test-chain during the 3rd voting period, the multisig also requires the chain-id to be signed:
The thing to serialize and sign is hence, the chain-id, the contract address, and the payload:
You can check that echo "$topack" shows something like:
(Pair (Pair "NetXMFJWfpUBox7" "KT1Mfv7qCR9zfQZJcG8Bx7n6XygiWN3fHVNG") (Pair 1 (Left {DROP; NIL operation;{PUSH address "KT1KT6smv2ivadEMPiYS2fDWVKdApsVpffts"; CONTRACT string; ASSERT_SOME; PUSH mutez 0; PUSH string "hello-1"; TRANSFER_TOKENS;};CONS; })))
(here NetXMFJWfpUBox7 is the vanity chain-id of the sandbox).
Now we serialize this. We need to give the type of the expression
but we can cheat a bit: since we only use the left side of the (or _ _)
we can put unit on the right-side, feel free to copy the real type from
generic.tz:
The command tezos-client hash data throws a lot of output,
we only care about the line that looks like:
Raw packed data: 0x050707070……
We grab the hexadecimal blob into a file and feed it to the signer:
The output is the Base58check-encoded signature, exactly what we need to build a Michelson literal to call the contract:
We see that alice passes the payload together with a list of
(option signature) values,
in our case a singleton corresponding to bob's signature.
If everything goes as planned, we can see in the target-contract's
storage that the address of the multisig has been recorded:
(should return the same as
echo "Some (Pair \"$msig_kt1\" \"hello-$counter\")").
After 8 years of blograstination, this is post #11 of my attempt at using the #100DaysToOffload completely unrealistic “challenge” to remind me to write stuff once in a while … Let's see where this goes.
