Skip to content

Instantly share code, notes, and snippets.

@smondet
Last active January 4, 2021 15:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save smondet/0ea3f22375a309892fa5855cb7d7c1d2 to your computer and use it in GitHub Desktop.
Save smondet/0ea3f22375a309892fa5855cb7d7c1d2 to your computer and use it in GitHub Desktop.
## 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