Skip to content

Commit

Permalink
governance update for testnet
Browse files Browse the repository at this point in the history
- requires cardanocli 9.3.0.0
- update in governance functions for light mode
- cip129 support addons
- live proposal percentage calculation (online-mode)
- a ton of other updates
  • Loading branch information
gitmachtl committed Aug 26, 2024
1 parent 1db5ce7 commit 9511773
Show file tree
Hide file tree
Showing 18 changed files with 1,435 additions and 378 deletions.
439 changes: 409 additions & 30 deletions cardano/testnet/00_common.sh

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions cardano/testnet/01_queryAddress.sh
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,6 @@ elif [[ ${typeOfAddr} == ${addrTypeStake} ]]; then #Staking Address

esac

# jq -r . <<< ${rewardsJSON}

rewardsEntryCnt=$(jq -r 'length' <<< ${rewardsJSON})

if [[ ${rewardsEntryCnt} == 0 ]]; then echo -e "\e[35mStaking Address is not on the chain, register it first !\e[0m\n"; exit 1;
Expand Down Expand Up @@ -336,6 +334,9 @@ elif [[ ${typeOfAddr} == ${addrTypeStake} ]]; then #Staking Address
"scriptHash") drepDelegationID=$(${bech32_bin} "drep_script" <<< "${drepDelegationHASH##*-}" 2> /dev/null)
echo -e " \t\e[0mVoting-Power of Staking Address is delegated to DRep-Script-ID(HASH): \e[32m${drepDelegationID}\e[0m (\e[94m${drepDelegationHASH##*-}\e[0m)\n";
;;
"null") #not delegated
echo -e " \t\e[0mVoting-Power of Staking Address is not delegated to a DRep !\e[0m\n";
;;
*) #unknown type
echo -e " \t\e[0mVoting-Power of Staking Address is delegated to DRep-HASH: \e[32m${drepDelegationHASH}\e[0m\n";
;;
Expand Down
2 changes: 1 addition & 1 deletion cardano/testnet/01_sendLovelaces.sh
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ fi

#Read ProtocolParameters
case ${workMode} in
"online") protocolParametersJSON=$(${cardanocli} ${cliEra} query protocol-parameters | jq -r ".extraPraosEntropy+= null");; #onlinemode
"online") protocolParametersJSON=$(${cardanocli} ${cliEra} query protocol-parameters );; #onlinemode
"light") protocolParametersJSON=${lightModeParametersJSON};; #lightmode
"offline") protocolParametersJSON=$(jq ".protocol.parameters" <<< ${offlineJSON});; #offlinemode
esac
Expand Down
49 changes: 24 additions & 25 deletions cardano/testnet/01_workOffline.sh
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,13 @@ case ${action} in
"online") #onlinemode
#get the normal parameters
protocolParametersJSON=$(${cardanocli} ${cliEra} query protocol-parameters )
#get the previous actions ids for the various action types and the constitution state
prevActionIDsJSON=$(${cardanocli} ${cliEra} query gov-state 2> /dev/null | jq -r ".nextRatifyState.nextEnactState.prevGovActionIds" 2> /dev/null)
if [[ ${prevActionIDsJSON} == "" ]]; then prevActionIDsJSON='{}'; fi
constitutionParametersJSON=$(${cardanocli} ${cliEra} query constitution 2> /dev/null | jq -r "." 2> /dev/null)
if [[ ${constitutionParametersJSON} == "" ]]; then constitutionParametersJSON='{}'; fi

#Governance Stuff
governanceParametersJSON=$(${cardanocli} ${cliEra} query gov-state 2> /dev/null | jq -r '{ committee : (.committee), constitution : (.constitution), prevActionIDs : (.nextRatifyState.nextEnactState.prevGovActionIds) }' 2> /dev/null)
if [[ "${governanceParametersJSON}" == "" ]]; then governanceParametersJSON="{}"; fi

#merge them together
protocolParametersJSON=$( jq --sort-keys ".constitution += ${constitutionParametersJSON} | .prevActionIDs += ${prevActionIDsJSON}" <<< ${protocolParametersJSON})
protocolParametersJSON=$( jq --sort-keys ". += ${governanceParametersJSON}" <<< ${protocolParametersJSON})
;;

"light") #lightmode
Expand Down Expand Up @@ -188,13 +188,13 @@ case ${action} in
"online") #onlinemode
#get the normal parameters
protocolParametersJSON=$(${cardanocli} ${cliEra} query protocol-parameters )
#get the previous actions ids for the various action types and the constitution state
prevActionIDsJSON=$(${cardanocli} ${cliEra} query gov-state 2> /dev/null | jq -r ".nextRatifyState.nextEnactState.prevGovActionIds" 2> /dev/null)
if [[ ${prevActionIDsJSON} == "" ]]; then prevActionIDsJSON='{}'; fi
constitutionParametersJSON=$(${cardanocli} ${cliEra} query constitution 2> /dev/null | jq -r "." 2> /dev/null)
if [[ ${constitutionParametersJSON} == "" ]]; then constitutionParametersJSON='{}'; fi

#Governance Stuff
governanceParametersJSON=$(${cardanocli} ${cliEra} query gov-state 2> /dev/null | jq -r '{ committee : (.committee), constitution : (.constitution), prevActionIDs : (.nextRatifyState.nextEnactState.prevGovActionIds) }' 2> /dev/null)
if [[ "${governanceParametersJSON}" == "" ]]; then governanceParametersJSON="{}"; fi

#merge them together
protocolParametersJSON=$( jq --sort-keys ".constitution += ${constitutionParametersJSON} | .prevActionIDs += ${prevActionIDsJSON}" <<< ${protocolParametersJSON})
protocolParametersJSON=$( jq --sort-keys ". += ${governanceParametersJSON}" <<< ${protocolParametersJSON})
;;

"light") #lightmode
Expand Down Expand Up @@ -545,30 +545,29 @@ if [[ "${action}" == "add-drep" ]]; then

drepID=$(cat ${drepName}.id) #we already checked that the file is present, so load the id from it

#do a short check if its a valid bech string
tmp=$(${bech32_bin} <<< ${drepID} 2> /dev/null)
#calculate the drep hash, and also do a bech validity check
drepHASH=$(${bech32_bin} 2> /dev/null <<< "${drepID}")
if [ $? -ne 0 ]; then echo -e "\e[35mERROR - The content of '${drepName}.id' is not a valid DRep-Bech-ID.\e[0m\n"; exit 1; fi;

echo -e "\e[0mChecking current Status about the DRep-ID:\e[32m ${drepID}\e[0m\n"

#Get state data for the drepID. When in online mode of course from the node and the chain, in light mode via koios
case ${workMode} in

"online") showProcessAnimation "Query DRep-ID Info: " &
drepStateJSON=$(${cardanocli} ${cliEra} query drep-state --drep-key-hash ${drepID} --include-stake 2> /dev/stdout )
if [ $? -ne 0 ]; then stopProcessAnimation; echo -e "\e[35mERROR - ${drepStateJSON}\e[0m\n"; exit $?; else stopProcessAnimation; fi;
drepStateJSON=$(jq -r ".[0] // []" <<< "${drepStateJSON}") #get rid of the outer array. if null, make an empty array
;;

# "light") showProcessAnimation "Query DRep-ID-Info-LightMode: " &
# drepStateJSON=$(queryLight_drepInfo "${drepID}")
# if [ $? -ne 0 ]; then stopProcessAnimation; echo -e "\e[35mERROR - ${drepStateJSON}\e[0m\n"; exit $?; else stopProcessAnimation; fi;
# ;;
"online") showProcessAnimation "Query DRep-ID Info: " &
drepStateJSON=$(${cardanocli} ${cliEra} query drep-state --drep-key-hash ${drepHASH} --drep-script-hash ${drepHASH} --include-stake 2> /dev/stdout )
if [ $? -ne 0 ]; then stopProcessAnimation; echo -e "\e[35mERROR - ${drepStateJSON}\e[0m\n"; exit $?; else stopProcessAnimation; fi;
drepStateJSON=$(jq -r ".[0] // []" <<< "${drepStateJSON}") #get rid of the outer array
;;

"light") showProcessAnimation "Query DRep-ID-Info-LightMode: " &
drepStateJSON=$(queryLight_drepInfo "${drepID}")
if [ $? -ne 0 ]; then stopProcessAnimation; echo -e "\e[35mERROR - ${drepStateJSON}\e[0m\n"; exit $?; else stopProcessAnimation; fi;
drepStateJSON=$(jq -r ".[0] // []" <<< "${drepStateJSON}") #get rid of the outer array
;;
esac

# { read drepEntryCnt; read drepDepositAmount; read drepAnchorURL; read drepAnchorHASH; } <<< $(jq -r 'length, .[1].deposit, .[1].anchor.url // "empty", .[1].anchor.dataHash // "no hash"' <<< ${drepStateJSON})

{ read drepEntryCnt;
read drepDepositAmount;
read drepAnchorURL;
Expand Down
3 changes: 3 additions & 0 deletions cardano/testnet/03c_checkStakingAddrOnChain.sh
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ if [[ ${typeOfAddr} == ${addrTypeStake} ]]; then #Staking Address
"scriptHash") drepDelegationID=$(${bech32_bin} "drep_script" <<< "${drepDelegationHASH##*-}" 2> /dev/null)
echo -e "\e[0mVoting-Power of Staking Address is delegated to DRep-Script-ID(HASH): \e[32m${drepDelegationID}\e[0m (\e[94m${drepDelegationHASH##*-}\e[0m)\n";
;;
"null") #not delegated
echo -e "\e[0mVoting-Power of Staking Address is not delegated to a DRep !\e[0m\n";
;;
*) drepDelegationID="" #unknown type
echo -e "\e[0mVoting-Power of Staking Address is delegated to DRep-HASH: \e[32m${drepDelegationHASH}\e[0m\n";
;;
Expand Down
2 changes: 1 addition & 1 deletion cardano/testnet/08b_deregStakingAddrCert.sh
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,7 @@ stakingName=$(basename ${stakeAddr} .staking) #contains the name before the .sta
if [[ -f "${fromAddr}.hwsfile" && -f "${stakeAddr}.hwsfile" && "${paymentName}" == "${stakingName}" ]]; then

#remove the tag(258) from the txBodyFile
sed -si 's/04d90102818308/04818308/g' "${txBodyFile}"
# sed -si 's/04d90102818308/04818308/g' "${txBodyFile}"

echo -ne "\e[0mAutocorrect the TxBody for canonical order: "
tmp=$(autocorrect_TxBodyFile "${txBodyFile}"); if [ $? -ne 0 ]; then echo -e "\e[35m${tmp}\e[0m\n\n"; exit 1; fi
Expand Down
12 changes: 4 additions & 8 deletions cardano/testnet/21b_regDRepCert.sh
Original file line number Diff line number Diff line change
Expand Up @@ -194,9 +194,6 @@ echo
case ${workMode} in
"online") #onlinemode
protocolParametersJSON=$(${cardanocli} ${cliEra} query protocol-parameters)
#not needed anymore since cli 8.24.0.0
# governanceParametersJSON=$(${cardanocli} ${cliEra} query gov-state 2> /dev/null | jq -r ".currentPParams")
# protocolParametersJSON=$( jq ". += ${governanceParametersJSON} " <<< ${protocolParametersJSON}) #embedding the governance parameters into the normal protocolParameters
;;

"light") #lightmode
Expand Down Expand Up @@ -227,7 +224,6 @@ checkError "$?"; if [ $? -ne 0 ]; then exit $?; fi
#If in online/light mode, check the anchorURL
if ${onlineMode}; then


#get Anchor-URL content and calculate the Anchor-Hash
if [[ ${anchorURL} != "" ]]; then

Expand Down Expand Up @@ -300,10 +296,10 @@ case ${workMode} in
drepStateJSON=$(jq -r .[0] <<< "${drepStateJSON}") #get rid of the outer array
;;

"light") #showProcessAnimation "Query DRep-ID-Info-LightMode: " &
echo -e "\n\e[91mINFORMATION - This script does not support Light-Mode yet, waiting for Koios support!\n\e[0m"; exit;
# drepStateJSON=$(queryLight_drepInfo "${drepID}")
# if [ $? -ne 0 ]; then stopProcessAnimation; echo -e "\e[35mERROR - ${drepStateJSON}\e[0m\n"; exit $?; else stopProcessAnimation; fi;
"light") showProcessAnimation "Query DRep-ID-Info-LightMode: " &
drepStateJSON=$(queryLight_drepInfo "${drepID}")
if [ $? -ne 0 ]; then stopProcessAnimation; echo -e "\e[35mERROR - ${drepStateJSON}\e[0m\n"; exit $?; else stopProcessAnimation; fi;
drepStateJSON=$(jq -r .[0] <<< "${drepStateJSON}") #get rid of the outer array
;;

"offline") readOfflineFile; #Reads the offlinefile into the offlineJSON variable
Expand Down
68 changes: 44 additions & 24 deletions cardano/testnet/21c_checkDRepOnChain.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,41 @@ if [[ "${checkDRepID//[![:xdigit:]]}" == "${checkDRepID}" && ${#checkDRepID} -eq
drepID=$(${bech32_bin} "drep" <<< "${checkDRepID}" 2> /dev/null)
if [ $? -ne 0 ]; then echo -e "\n\n\e[35mERROR - Couldn't convert HEX-ID into Bech-ID.\e[0m"; exit 1; fi
echo -e "\e[32m OK\e[0m\n"

echo -e "\e[0mRegular DRep-ID: \e[32m${drepID}\e[0m"
echo -e "\e[0m CIP129 DRep-ID: \e[33m$(convert_actionBech2CIP129 "${drepID}")\e[0m"

elif [[ "${checkDRepID:0:5}" == "drep1" && ${#checkDRepID} -eq 56 ]]; then #parameter is most likely a bech32-drepid

echo -ne "\e[0mCheck if given Bech-ID\e[32m ${checkDRepID}\e[0m is valid ..."
echo -ne "\e[0mCheck if given Bech-ID is valid ..."
#lets do some further testing by converting the bech32 DRep-id into a Hex-DRep-ID
tmp=$(${bech32_bin} 2> /dev/null <<< "${checkDRepID}") #will have returncode 0 if the bech was valid
if [ $? -ne 0 ]; then echo -e "\n\n\e[35mERROR - \"${checkDRepID}\" is not a valid Bech32 DRep-ID.\e[0m"; exit 1; fi
echo -e "\e[32m OK\e[0m\n"
drepID=${checkDRepID}
echo -e "\e[0mRegular DRep-ID: \e[32m${drepID}\e[0m"
echo -e "\e[0m CIP129 DRep-ID: \e[33m$(convert_actionBech2CIP129 "${drepID}")\e[0m"

elif [[ "${checkDRepID:0:5}" == "drep1" && ${#checkDRepID} -eq 58 ]]; then #parameter is most likely a CIP129 bech32-drepid

echo -ne "\e[0mCheck if given CIP129 Bech-ID is valid ..."
#lets do some further testing by converting the bech32 DRep-id into a Hex-DRep-ID
tmp=$(${bech32_bin} 2> /dev/null <<< "${checkDRepID}") #will have returncode 0 if the bech was valid
if [ $? -ne 0 ]; then echo -e "\n\n\e[35mERROR - \"${checkDRepID}\" is not a valid Bech32 DRep-ID.\e[0m"; exit 1; fi
echo -e "\e[32m OK\e[0m\n"
drepID=${checkDRepID}
echo -e "\e[0m CIP129 DRep-ID: \e[33m${drepID}\e[0m"
echo -e "\e[0mRegular DRep-ID: \e[32m$(convert_actionCIP1292Bech "${drepID}")\e[0m"

elif [[ "${checkDRepID:0:12}" == "drep_script1" && ${#checkDRepID} -eq 63 ]]; then #parameter is most likely a bech32-drep-script-id

echo -ne "\e[0mCheck if given DRep-Script-ID is valid ..."
#lets do some further testing by converting the bech32 DRep-id into a Hex-DRep-ID
tmp=$(${bech32_bin} 2> /dev/null <<< "${checkDRepID}") #will have returncode 0 if the bech was valid
if [ $? -ne 0 ]; then echo -e "\n\n\e[35mERROR - \"${checkDRepID}\" is not a valid Bech32 DRep-ID.\e[0m"; exit 1; fi
echo -e "\e[32m OK\e[0m\n"
drepID=${checkDRepID}
echo -e "\e[0mRegular DRep-ID: \e[32m${drepID}\e[0m"
echo -e "\e[0m CIP129 DRep-ID: \e[33m$(convert_actionBech2CIP129 "${drepID}")\e[0m"

elif [ -f "${checkDRepName}.drep.vkey" ]; then #parameter is a DRep verification key file

Expand All @@ -63,51 +88,45 @@ elif [ -f "${checkDRepName}.drep.vkey" ]; then #parameter is a DRep verification
drepID=$(${cardanocli} ${cliEra} governance drep id --drep-verification-key-file "${checkDRepName}.drep.vkey" --out-file /dev/stdout 2> /dev/null)
if [ $? -ne 0 ]; then echo -e "\n\n\e[35mERROR - Could not generate the DRep-ID from \"${checkDRepName}.drep.vkey\"\e[0m"; exit 1; fi
echo -e "\e[32m OK\e[0m\n"
echo -e "\e[0mRegular DRep-ID: \e[32m${drepID}\e[0m"
echo -e "\e[0m CIP129 DRep-ID: \e[33m$(convert_actionBech2CIP129 "${drepID}")\e[0m"

elif [ -f "${checkDRepName}.drep.id" ]; then #parameter is a DRep verification id file, containing a bech32 id

echo -ne "\e[0mReading from DRep-ID-File\e[32m ${checkDRepName}.drep.id\e[0m ..."
checkDRepID=$(cat "${checkDRepName}.drep.id" 2> /dev/null)
if [ $? -ne 0 ]; then echo -e "\n\n\e[35mERROR - Could not read from file \"${checkDRepName}.drep.id\"\e[0m"; exit 1; fi
echo -e "\e[32m OK\e[0m\n"
#lets do some further testing by converting the bech32 DRep-id into a Hex-DRep-ID
#lets do some further testing by converting the bech32 DRep-ID into a Hex-DRep-ID
tmp=$(${bech32_bin} 2> /dev/null <<< "${checkDRepID}") #will have returncode 0 if the bech was valid
if [ $? -ne 0 ]; then echo -e "\e[35mERROR - \"${checkDRepID}\" is not a valid Bech32 DRep-ID.\e[0m"; exit 1; fi
drepID=${checkDRepID}

elif [[ "${checkDRepID:0:12}" == "drep_script1" && ${#checkDRepID} -eq 63 ]]; then #parameter is most likely a bech32-drep-script-id

echo -ne "\e[0mCheck if given DRep-Script-ID\e[32m ${checkDRepID}\e[0m is valid ..."
#lets do some further testing by converting the bech32 DRep-id into a Hex-DRep-ID
tmp=$(${bech32_bin} 2> /dev/null <<< "${checkDRepID}") #will have returncode 0 if the bech was valid
if [ $? -ne 0 ]; then echo -e "\n\n\e[35mERROR - \"${checkDRepID}\" is not a valid Bech32 DRep-ID.\e[0m"; exit 1; fi
echo -e "\e[32m OK\e[0m\n"
drepID=${checkDRepID}
echo -e "\e[0mRegular DRep-ID: \e[32m${drepID}\e[0m"
echo -e "\e[0m CIP129 DRep-ID: \e[33m$(convert_actionBech2CIP129 "${drepID}")\e[0m"

else
echo -e "\n\e[35mERROR - \"${checkDRepName}.drep.vkey/id\" does not exist, nor is \"${checkDRepID}\" a valid DRep-ID in Hex- or Bech-Format!\e[0m"; exit 1
fi


#calculate the drep hash again, just for showing it to the user as an additional information
drepHASH=$(${bech32_bin} 2> /dev/null <<< "${drepID}")
drepHASH=$(${bech32_bin} 2> /dev/null <<< "${drepID}"); drepHASH=${drepHASH: -56}

echo -e "\e[0mChecking Information about the DRep-ID:\e[32m ${drepID}\e[0m"
echo -e "\e[0m DRep-HASH:\e[94m ${drepHASH}\e[0m\n"
echo -e "\e[0m DRep-HASH:\e[94m ${drepHASH}\e[0m\n"

#Get state data for the drepID. When in online mode of course from the node and the chain, in light mode via koios
case ${workMode} in

"online") showProcessAnimation "Query DRep-ID Info: " &
drepStateJSON=$(${cardanocli} ${cliEra} query drep-state --drep-key-hash ${drepHASH} --drep-script-hash ${drepHASH} --include-stake 2> /dev/stdout )
if [ $? -ne 0 ]; then stopProcessAnimation; echo -e "\e[35mERROR - ${drepStateJSON}\e[0m\n"; exit $?; else stopProcessAnimation; fi;
drepStateJSON=$(jq -r .[0] <<< "${drepStateJSON}") #get rid of the outer array
drepStateJSON=$(jq -r ".[0] // []" <<< "${drepStateJSON}") #get rid of the outer array
;;

"light") #showProcessAnimation "Query DRep-ID-Info-LightMode: " &
#drepStateJSON=$(queryLight_drepInfo "${drepID}")
#if [ $? -ne 0 ]; then stopProcessAnimation; echo -e "\e[35mERROR - ${drepStateJSON}\e[0m\n"; exit $?; else stopProcessAnimation; fi;
echo -e "\e[91mINFORMATION - There is currently not Light-Mode available for this action. Please use Online(Full)- or Offline-Mode!\e[0m\n"; exit;
"light") showProcessAnimation "Query DRep-ID-Info-LightMode: " &
drepStateJSON=$(queryLight_drepInfo "${drepID}")
if [ $? -ne 0 ]; then stopProcessAnimation; echo -e "\e[35mERROR - ${drepStateJSON}\e[0m\n"; exit $?; else stopProcessAnimation; fi;
drepStateJSON=$(jq -r ".[0] // []" <<< "${drepStateJSON}") #get rid of the outer array
;;

"offline") readOfflineFile; #Reads the offlinefile into the offlineJSON variable
Expand All @@ -117,8 +136,7 @@ case ${workMode} in

esac

#jq -r . <<< ${drepStateJSON}

currentEpoch=$(get_currentEpoch)

{ read drepEntryCnt;
read drepDepositAmount;
Expand All @@ -133,15 +151,17 @@ if [[ ${drepEntryCnt} == 0 ]]; then #not registered yet
echo -e "\e[0mDRep-ID/HASH is\e[33m NOT registered on the chain\e[0m!\e[0m\n";
exit 1;

elif [[ ${drepExpireEpoch} -lt $(get_currentEpoch) ]]; then #activity expired
elif [[ ${drepExpireEpoch} -lt ${currentEpoch} ]]; then #activity expired
echo -e "\e[0mDRep-ID/HASH is \e[32mregistered\e[0m but activity \e[91mexpired\e[0m on the chain!\n"
echo -e "\e[0m Deposit-Amount:\e[32m ${drepDepositAmount}\e[0m lovelaces"
echo -e "\e[0m Inactive-Epoch:\e[91m ${drepExpireEpoch}\e[0m"
echo -e "\e[0m Current-Epoch:\e[32m ${currentEpoch}\e[0m"

else #normal registration and not expired
echo -e "\e[0mDRep-ID/HASH is \e[32mregistered\e[0m on the chain!\n"
echo -e "\e[0m Deposit-Amount:\e[32m ${drepDepositAmount}\e[0m lovelaces"
echo -e "\e[0m Expire-Epoch:\e[32m ${drepExpireEpoch}\e[0m"
echo -e "\e[0m Current-Epoch:\e[32m ${currentEpoch}\e[0m"
fi

echo -e "\e[0m DRep-KeyType:\e[94m ${drepKeyType}\e[0m"
Expand Down Expand Up @@ -196,7 +216,7 @@ if ${onlineMode}; then
if [ "${contentHASH}" != "${drepAnchorHASH}" ]; then
echo -e "\e[0m Anchor-STATUS:\e[35m HASH does not match! Online-HASH is \e[33m${contentHASH}\e[0m";
else
echo -e "\e[0m Anchor-STATUS:\e[32m Format and HASH is valid!\e[0m";
echo -e "\e[0m Anchor-STATUS:\e[32m Content-HASH is valid! (No integrity check)\e[0m";
fi
rm "${tmpAnchorContent}" #cleanup

Expand Down
Loading

0 comments on commit 9511773

Please sign in to comment.