This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## Let's setup a silent [sandbox](https://assets.tqtezos.com/docs/setup/2-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: | |
target_code=' | |
parameter string; | |
storage (option (pair address string)); | |
code { CAR; SENDER; PAIR; SOME; NIL operation; PAIR }; | |
' | |
## it just stores the sender and the argument it is called with, let's | |
## call it “`target`”: | |
tezos-client originate contract target transferring 0 from alice \ | |
running "$target_code" --init None --burn-cap 1 --force | |
## where: | |
## - `alice` is the whale account from the sandbox, and | |
## - `None` is the initialization of the storage. | |
## | |
## The multisig contract we want to use is there: | |
multisig_code='https://github.com/murbard/smart-contracts/raw/master/multisig/michelson/generic.tz' | |
## Check-out also | |
## [generic_multisig.v](https://gitlab.com/nomadic-labs/mi-cho-coq/-/blob/master/src/contracts_coq/generic_multisig.v), | |
## its formalization and correctness proofs in Coq. | |
## | |
## Let's download it: | |
wget "$multisig_code" | |
## And originate it: | |
tezos-client originate contract msig transferring 0 from alice \ | |
running generic.tz \ | |
--init '(Pair 0 (Pair 1 {"edpkurPsQ8eUApnLUJ9ZPDvu98E8VNj4KtJa1aZr16Cr5ow5VHKnz4"}))' \ | |
--burn-cap 1 \ | |
--force | |
## where: | |
## - The storage looks like: `(Pair <initialize-counter> (Pair <signature-threshold> {<public-keys>}))`. | |
## - We set the threshold to `1` for simplicity. | |
## - `"edpkvGfYw3LyB1UcCahKQk4rF2tvbMUk8GFiTuMjL75uGXrpvKXhjn"` is | |
## `bob`'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: | |
target_kt1=$(tezos-client show known contract target) | |
msig_kt1=$(tezos-client show known contract msig) | |
## | |
## The first time we “know” that the replay-protection counter is 0: | |
counter=0 | |
## But in general we want to get it from the contract storage, so, | |
## very elegantly, we `sed` it out: | |
counter=$( | |
tezos-client get contract storage for msig \ | |
| sed 's/Pair \([0-9]*\).*$/\1/' | |
) | |
## | |
## 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: | |
lambda="$(sed 's/#.*//' /tmp/lambda.tz | tr -d '\n')" | |
## 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>)`): | |
payload="(Pair $counter (Left $lambda))" | |
## To avoid replay attacks on the | |
## [test-chain](https://medium.com/tezos/amending-tezos-b77949d97e1e) during | |
## the 3rd voting period, the multisig also requires the chain-id to be | |
## signed: | |
chain_id=$(tezos-client rpc get /chains/main/chain_id | tr -d '"') | |
## The thing to serialize and sign is hence, the chain-id, the contract address, | |
## and the payload: | |
topack="(Pair (Pair \"$chain_id\" \"$msig_kt1\") $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`: | |
tezos-client hash data "$topack" of type \ | |
'(pair (pair chain_id address) (pair nat (or (lambda unit (list operation)) unit)))' \ | |
| tee /dev/stderr \ | |
| awk -F' ' ' /Raw packed/ { print $4 }' \ | |
> /tmp/bytes.hex | |
## 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: | |
tezos-client sign bytes $(cat /tmp/bytes.hex) for bob \ | |
| tee /dev/stderr \ | |
| cut -d' ' -f 2 > /tmp/sig.b58 | |
## The output is the Base58check-encoded signature, exactly what we need to | |
## build a Michelson literal to call the contract: | |
tezos-client transfer 0 from alice to msig \ | |
--entrypoint main \ | |
--arg "(Pair $payload { Some \"$(cat /tmp/sig.b58 )\" } )" \ | |
--burn-cap 2 | |
## 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: | |
tezos-client get contract storage for target | |
## (should return the same as | |
## `echo "Some (Pair \"$msig_kt1\" \"hello-$counter\")"`). | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment