feat(common & core & legacy): Stellar, add support for StellarManageBuyOfferOp.

pull/1896/head
Jun Luo 3 years ago committed by matejcik
parent 3a5768c4bf
commit 1dcb8e4913

@ -76,6 +76,7 @@ message StellarSignTx {
* @next StellarCreateAccountOp
* @next StellarPathPaymentStrictReceiveOp
* @next StellarManageSellOfferOp
* @next StellarManageBuyOfferOp
* @next StellarCreatePassiveOfferOp
* @next StellarSetOptionsOp
* @next StellarChangeTrustOp
@ -140,6 +141,21 @@ message StellarManageSellOfferOp {
required uint64 offer_id = 7; // Offer ID for updating an existing offer
}
/**
* Request: ask device to confirm this operation type
* @next StellarTxOpRequest
* @next StellarSignedTx
*/
message StellarManageBuyOfferOp {
optional string source_account = 1; // (optional) source account address
required StellarAsset selling_asset = 2;
required StellarAsset buying_asset = 3;
required sint64 amount = 4;
required uint32 price_n = 5; // Price numerator
required uint32 price_d = 6; // Price denominator
required uint64 offer_id = 7; // Offer ID for updating an existing offer
}
/**
* Request: ask device to confirm this operation type
* @next StellarTxOpRequest

@ -239,6 +239,7 @@ enum MessageType {
// omitted: StellarInflationOp is not a supported operation, would be 219
MessageType_StellarManageDataOp = 220 [(wire_in) = true];
MessageType_StellarBumpSequenceOp = 221 [(wire_in) = true];
MessageType_StellarManageBuyOfferOp = 222 [(wire_in) = true];
MessageType_StellarSignedTx = 230 [(wire_out) = true];
// Cardano

@ -335,6 +335,45 @@
"signature": "y9xb8IgPpkjgFa87I9alTD0mVc6EUcJrD7erZVPVGLdDs7rjh7fVtLAJS7iin85Yle0AwnqqEADYAjVzHzz7Bg=="
}
},
{
"name": "StellarManageBuyOfferOp",
"parameters": {
"xdr": "AAAAAgAAAAAvIrnGLwi3dPPr5t1ufbk8PsLL3gJ5Vho9nFIluMMikgAAAGQAAAAAAAAD6AAAAAEAAAAAG4J3zQAAAABd5CqEAAAAAAAAAAEAAAAAAAAADAAAAAJBQkNERUZHSElKS0wAAAAAKYSWAsIOWDZfEjwS2HocpFUNEM0hsK4OGNROPlb9ahUAAAABWAAAAAAAAABwi6oxX35cFm2EtGS/s4/WJXj+OtJyJ+dsy7ehecRRIQAAAAAdzxaYAAAAAwAAAAQAAAAAAAAFOQAAAAAAAAAA",
"address_n": "m/44'/148'/0'",
"network_passphrase": "Test SDF Network ; September 2015",
"tx": {
"source_account": "GAXSFOOGF4ELO5HT5PTN23T5XE6D5QWL3YBHSVQ2HWOFEJNYYMRJENBV",
"fee": 100,
"sequence_number": 1000,
"timebounds_start": 461535181,
"timebounds_end": 1575234180,
"memo_type": "NONE"
},
"operations": [
{
"_message_type": "StellarManageBuyOfferOp",
"selling_asset": {
"type": "ALPHANUM12",
"code": "ABCDEFGHIJKL",
"issuer": "GAUYJFQCYIHFQNS7CI6BFWD2DSSFKDIQZUQ3BLQODDKE4PSW7VVBKENC"
},
"buying_asset": {
"type": "ALPHANUM4",
"code": "X",
"issuer": "GBYIXKRRL57FYFTNQS2GJP5TR7LCK6H6HLJHEJ7HNTF3PILZYRISDLNQ"
},
"amount": 500111000,
"price_n": 3,
"price_d": 4,
"offer_id": 1337
}
]
},
"result": {
"public_key": "2f22b9c62f08b774f3ebe6dd6e7db93c3ec2cbde0279561a3d9c5225b8c32292",
"signature": "sDQuKEm7j6Lsuw+QUTrotSloZiF+8LrXsuoLCBadewWpArO8+qmMgonrG3bJfaZ4dYdD8WcpfP5LNLOfU+lDBA=="
}
},
{
"name": "StellarManageSellOfferOp",
"parameters": {

@ -0,0 +1 @@
Stellar: add support for StellarManageBuyOfferOp.

@ -25,6 +25,7 @@ Stellar transaction is composed of one or more operations. We support all [opera
- Create Account
- Create Passive Offer
- Manage Data
- Manage Buy Offer
- Manage Sell Offer
- Path Payment Strict Receive
- Payment

@ -14,6 +14,7 @@ if False:
StellarCreateAccountOp,
StellarCreatePassiveOfferOp,
StellarManageDataOp,
StellarManageBuyOfferOp,
StellarManageSellOfferOp,
StellarPathPaymentStrictReceiveOp,
StellarPaymentOp,
@ -28,6 +29,7 @@ if False:
StellarCreateAccountOp,
StellarCreatePassiveOfferOp,
StellarManageDataOp,
StellarManageBuyOfferOp,
StellarManageSellOfferOp,
StellarPathPaymentStrictReceiveOp,
StellarPaymentOp,
@ -37,7 +39,7 @@ if False:
TX_TYPE = b"\x00\x00\x00\x02"
# source: https://github.com/stellar/go/blob/3d2c1defe73dbfed00146ebe0e8d7e07ce4bb1b6/xdr/Stellar-transaction.x#L16
# source: https://github.com/stellar/go/blob/a1db2a6b1f/xdr/Stellar-transaction.x#L35
# Inflation not supported see https://github.com/trezor/trezor-core/issues/202#issuecomment-393342089
op_codes: dict[int, int] = {
MessageType.StellarAccountMergeOp: 8,
@ -47,6 +49,7 @@ op_codes: dict[int, int] = {
MessageType.StellarCreateAccountOp: 0,
MessageType.StellarCreatePassiveOfferOp: 4,
MessageType.StellarManageDataOp: 10,
MessageType.StellarManageBuyOfferOp: 12,
MessageType.StellarManageSellOfferOp: 3,
MessageType.StellarPathPaymentStrictReceiveOp: 2,
MessageType.StellarPaymentOp: 1,
@ -61,6 +64,7 @@ op_wire_types = [
MessageType.StellarCreateAccountOp,
MessageType.StellarCreatePassiveOfferOp,
MessageType.StellarManageDataOp,
MessageType.StellarManageBuyOfferOp,
MessageType.StellarManageSellOfferOp,
MessageType.StellarPathPaymentStrictReceiveOp,
MessageType.StellarPaymentOp,

@ -34,6 +34,9 @@ async def process_operation(
elif serialize.StellarManageDataOp.is_type_of(op):
await layout.confirm_manage_data_op(ctx, op)
serialize.write_manage_data_op(w, op)
elif serialize.StellarManageBuyOfferOp.is_type_of(op):
await layout.confirm_manage_buy_offer_op(ctx, op)
serialize.write_manage_buy_offer_op(w, op)
elif serialize.StellarManageSellOfferOp.is_type_of(op):
await layout.confirm_manage_sell_offer_op(ctx, op)
serialize.write_manage_sell_offer_op(w, op)

@ -7,6 +7,7 @@ from trezor.messages import (
StellarChangeTrustOp,
StellarCreateAccountOp,
StellarCreatePassiveOfferOp,
StellarManageBuyOfferOp,
StellarManageDataOp,
StellarManageSellOfferOp,
StellarPathPaymentStrictReceiveOp,
@ -106,8 +107,20 @@ async def confirm_create_passive_offer_op(
await _confirm_offer(ctx, text, op)
async def confirm_manage_buy_offer_op(
ctx: Context, op: StellarManageBuyOfferOp
) -> None:
await _confirm_manage_offer_op_common(ctx, op)
async def confirm_manage_sell_offer_op(
ctx: Context, op: StellarManageSellOfferOp
) -> None:
await _confirm_manage_offer_op_common(ctx, op)
async def _confirm_manage_offer_op_common(
ctx: Context, op: StellarManageBuyOfferOp | StellarManageSellOfferOp
) -> None:
if op.offer_id == 0:
text = "New Offer"
@ -123,21 +136,37 @@ async def confirm_manage_sell_offer_op(
async def _confirm_offer(
ctx: Context,
title: str,
op: StellarCreatePassiveOfferOp | StellarManageSellOfferOp,
op: StellarCreatePassiveOfferOp
| StellarManageSellOfferOp
| StellarManageBuyOfferOp,
) -> None:
await confirm_properties(
ctx,
"op_offer",
title=title,
props=(
("Selling:", format_amount(op.amount, op.selling_asset)),
("Buying:", format_asset(op.buying_asset)),
(
f"Price per {format_asset(op.buying_asset)}:",
str(op.price_n / op.price_d),
),
),
)
if StellarManageBuyOfferOp.is_type_of(op):
buying = ("Buying:", format_amount(op.amount, op.buying_asset))
selling = ("Selling:", format_asset(op.selling_asset))
price = (
f"Price per {format_asset(op.selling_asset)}:",
str(op.price_n / op.price_d),
)
await confirm_properties(
ctx,
"op_offer",
title=title,
props=(buying, selling, price),
)
else:
selling = ("Selling:", format_amount(op.amount, op.selling_asset))
buying = ("Buying:", format_asset(op.buying_asset))
price = (
f"Price per {format_asset(op.buying_asset)}:",
str(op.price_n / op.price_d),
)
await confirm_properties(
ctx,
"op_offer",
title=title,
props=(selling, buying, price),
)
await confirm_asset_issuer(ctx, op.selling_asset)
await confirm_asset_issuer(ctx, op.buying_asset)

@ -7,6 +7,7 @@ from trezor.messages import (
StellarChangeTrustOp,
StellarCreateAccountOp,
StellarCreatePassiveOfferOp,
StellarManageBuyOfferOp,
StellarManageDataOp,
StellarManageSellOfferOp,
StellarPathPaymentStrictReceiveOp,
@ -65,10 +66,20 @@ def write_manage_data_op(w: Writer, msg: StellarManageDataOp) -> None:
writers.write_string(w, msg.value)
def write_manage_buy_offer_op(w: Writer, msg: StellarManageBuyOfferOp) -> None:
_write_manage_offer_op_common(w, msg)
def write_manage_sell_offer_op(w: Writer, msg: StellarManageSellOfferOp) -> None:
_write_manage_offer_op_common(w, msg)
def _write_manage_offer_op_common(
w: Writer, msg: StellarManageSellOfferOp | StellarManageBuyOfferOp
) -> None:
_write_asset(w, msg.selling_asset)
_write_asset(w, msg.buying_asset)
writers.write_uint64(w, msg.amount) # amount to sell
writers.write_uint64(w, msg.amount) # amount to sell / buy
writers.write_uint32(w, msg.price_n) # numerator
writers.write_uint32(w, msg.price_d) # denominator
writers.write_uint64(w, msg.offer_id)

@ -130,6 +130,7 @@ if not utils.BITCOIN_ONLY:
StellarAccountMergeOp = 218
StellarManageDataOp = 220
StellarBumpSequenceOp = 221
StellarManageBuyOfferOp = 222
StellarSignedTx = 230
CardanoSignTx = 303
CardanoGetPublicKey = 305

@ -135,6 +135,7 @@ if TYPE_CHECKING:
StellarAccountMergeOp = 218
StellarManageDataOp = 220
StellarBumpSequenceOp = 221
StellarManageBuyOfferOp = 222
StellarSignedTx = 230
CardanoSignTx = 303
CardanoGetPublicKey = 305

@ -4824,6 +4824,32 @@ if TYPE_CHECKING:
def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["StellarManageSellOfferOp"]:
return isinstance(msg, cls)
class StellarManageBuyOfferOp(protobuf.MessageType):
source_account: "str | None"
selling_asset: "StellarAsset"
buying_asset: "StellarAsset"
amount: "int"
price_n: "int"
price_d: "int"
offer_id: "int"
def __init__(
self,
*,
selling_asset: "StellarAsset",
buying_asset: "StellarAsset",
amount: "int",
price_n: "int",
price_d: "int",
offer_id: "int",
source_account: "str | None" = None,
) -> None:
pass
@classmethod
def is_type_of(cls, msg: protobuf.MessageType) -> TypeGuard["StellarManageBuyOfferOp"]:
return isinstance(msg, cls)
class StellarCreatePassiveOfferOp(protobuf.MessageType):
source_account: "str | None"
selling_asset: "StellarAsset"

@ -0,0 +1 @@
Stellar: add support for StellarManageBuyOfferOp.

@ -120,6 +120,7 @@ void fsm_msgStellarPaymentOp(const StellarPaymentOp *msg);
void fsm_msgStellarCreateAccountOp(const StellarCreateAccountOp *msg);
void fsm_msgStellarPathPaymentStrictReceiveOp(
const StellarPathPaymentStrictReceiveOp *msg);
void fsm_msgStellarManageBuyOfferOp(const StellarManageBuyOfferOp *msg);
void fsm_msgStellarManageSellOfferOp(const StellarManageSellOfferOp *msg);
void fsm_msgStellarCreatePassiveOfferOp(const StellarCreatePassiveOfferOp *msg);
void fsm_msgStellarSetOptionsOp(const StellarSetOptionsOp *msg);

@ -128,6 +128,24 @@ void fsm_msgStellarPathPaymentStrictReceiveOp(
}
}
void fsm_msgStellarManageBuyOfferOp(const StellarManageBuyOfferOp *msg) {
if (!stellar_confirmManageBuyOfferOp(msg)) return;
if (stellar_allOperationsConfirmed()) {
RESP_INIT(StellarSignedTx);
stellar_fillSignedTx(resp);
msg_write(MessageType_MessageType_StellarSignedTx, resp);
layoutHome();
}
// Request the next operation to sign
else {
RESP_INIT(StellarTxOpRequest);
msg_write(MessageType_MessageType_StellarTxOpRequest, resp);
}
}
void fsm_msgStellarManageSellOfferOp(const StellarManageSellOfferOp *msg) {
if (!stellar_confirmManageSellOfferOp(msg)) return;

@ -21,6 +21,7 @@ StellarPathPaymentStrictReceiveOp.source_account max_size:57
StellarPathPaymentStrictReceiveOp.destination_account max_size:57
StellarPathPaymentStrictReceiveOp.paths max_count:5
StellarManageBuyOfferOp.source_account max_size:57
StellarManageSellOfferOp.source_account max_size:57

@ -388,6 +388,93 @@ bool stellar_confirmPathPaymentStrictReceiveOp(
return true;
}
bool stellar_confirmManageBuyOfferOp(const StellarManageBuyOfferOp *msg) {
if (!stellar_signing) return false;
if (!stellar_confirmSourceAccount(msg->has_source_account,
msg->source_account)) {
stellar_signingAbort(_("Source account error"));
return false;
}
// Hash: operation type
stellar_hashupdate_uint32(12);
// New Offer / Delete #123 / Update #123
char str_offer[32] = {0};
if (msg->offer_id == 0) {
strlcpy(str_offer, _("New Offer"), sizeof(str_offer));
} else {
char str_offer_id[20] = {0};
stellar_format_uint64(msg->offer_id, str_offer_id, sizeof(str_offer_id));
if (msg->amount == 0) {
strlcpy(str_offer, _("Delete #"), sizeof(str_offer));
} else {
strlcpy(str_offer, _("Update #"), sizeof(str_offer));
}
strlcat(str_offer, str_offer_id, sizeof(str_offer));
}
char str_buying[32] = {0};
char str_buying_amount[32] = {0};
char str_buying_asset[32] = {0};
stellar_format_asset(&(msg->buying_asset), str_buying_asset,
sizeof(str_buying_asset));
stellar_format_stroops(msg->amount, str_buying_amount,
sizeof(str_buying_amount));
/*
Buy 200
XLM (Native Asset)
*/
strlcpy(str_buying, _("Buy "), sizeof(str_buying));
strlcat(str_buying, str_buying_amount, sizeof(str_buying));
char str_selling[32] = {0};
char str_selling_asset[32] = {0};
char str_price[32] = {0};
stellar_format_asset(&(msg->selling_asset), str_selling_asset,
sizeof(str_selling_asset));
stellar_format_price(msg->price_n, msg->price_d, str_price,
sizeof(str_price));
/*
For 0.675952 Per
USD (G12345678)
*/
strlcpy(str_selling, _("For "), sizeof(str_selling));
strlcat(str_selling, str_price, sizeof(str_selling));
strlcat(str_selling, _(" Per"), sizeof(str_selling));
stellar_layoutTransactionDialog(str_offer, str_buying, str_buying_asset,
str_selling, str_selling_asset);
if (!protectButton(ButtonRequestType_ButtonRequest_ProtectCall, false)) {
stellar_signingAbort(_("User canceled"));
return false;
}
// Hash selling asset
stellar_hashupdate_asset(&(msg->selling_asset));
// buying asset
stellar_hashupdate_asset(&(msg->buying_asset));
// amount to buy (signed vs. unsigned doesn't matter wrt hashing)
stellar_hashupdate_uint64(msg->amount);
// numerator
stellar_hashupdate_uint32(msg->price_n);
// denominator
stellar_hashupdate_uint32(msg->price_d);
// offer ID
stellar_hashupdate_uint64(msg->offer_id);
// At this point, the operation is confirmed
stellar_activeTx.confirmed_operations++;
return true;
}
bool stellar_confirmManageSellOfferOp(const StellarManageSellOfferOp *msg) {
if (!stellar_signing) return false;

@ -61,6 +61,7 @@ bool stellar_confirmCreateAccountOp(const StellarCreateAccountOp *msg);
bool stellar_confirmPaymentOp(const StellarPaymentOp *msg);
bool stellar_confirmPathPaymentStrictReceiveOp(
const StellarPathPaymentStrictReceiveOp *msg);
bool stellar_confirmManageBuyOfferOp(const StellarManageBuyOfferOp *msg);
bool stellar_confirmManageSellOfferOp(const StellarManageSellOfferOp *msg);
bool stellar_confirmCreatePassiveOfferOp(
const StellarCreatePassiveOfferOp *msg);

@ -156,6 +156,7 @@ class MessageType(IntEnum):
StellarAccountMergeOp = 218
StellarManageDataOp = 220
StellarBumpSequenceOp = 221
StellarManageBuyOfferOp = 222
StellarSignedTx = 230
CardanoSignTx = 303
CardanoGetPublicKey = 305
@ -6388,6 +6389,38 @@ class StellarManageSellOfferOp(protobuf.MessageType):
self.source_account = source_account
class StellarManageBuyOfferOp(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 222
FIELDS = {
1: protobuf.Field("source_account", "string", repeated=False, required=False),
2: protobuf.Field("selling_asset", "StellarAsset", repeated=False, required=True),
3: protobuf.Field("buying_asset", "StellarAsset", repeated=False, required=True),
4: protobuf.Field("amount", "sint64", repeated=False, required=True),
5: protobuf.Field("price_n", "uint32", repeated=False, required=True),
6: protobuf.Field("price_d", "uint32", repeated=False, required=True),
7: protobuf.Field("offer_id", "uint64", repeated=False, required=True),
}
def __init__(
self,
*,
selling_asset: "StellarAsset",
buying_asset: "StellarAsset",
amount: "int",
price_n: "int",
price_d: "int",
offer_id: "int",
source_account: Optional["str"] = None,
) -> None:
self.selling_asset = selling_asset
self.buying_asset = buying_asset
self.amount = amount
self.price_n = price_n
self.price_d = price_d
self.offer_id = offer_id
self.source_account = source_account
class StellarCreatePassiveOfferOp(protobuf.MessageType):
MESSAGE_WIRE_TYPE = 214
FIELDS = {

@ -43,6 +43,7 @@ try:
TrustLineEntryFlag,
Price,
Network,
ManageBuyOffer,
)
from stellar_sdk.xdr.signer_key_type import SignerKeyType
@ -225,6 +226,17 @@ def _read_operation(op: "Operation"):
return messages.StellarBumpSequenceOp(
source_account=source_account, bump_to=op.bump_to
)
if isinstance(op, ManageBuyOffer):
price = _read_price(op.price)
return messages.StellarManageBuyOfferOp(
source_account=source_account,
selling_asset=_read_asset(op.selling),
buying_asset=_read_asset(op.buying),
amount=_read_amount(op.amount),
price_n=price.n,
price_d=price.d,
offer_id=op.offer_id,
)
raise ValueError(f"Unknown operation type: {op.__class__.__name__}")

@ -712,3 +712,73 @@ def test_bump_sequence():
assert isinstance(operations[0], messages.StellarBumpSequenceOp)
assert operations[0].source_account == operation_source
assert operations[0].bump_to == bump_to
def test_manage_buy_offer_new_offer():
tx = make_default_tx()
price = "0.5"
amount = "50.0111"
selling_code = "XLM"
selling_issuer = None
buying_code = "USD"
buying_issuer = "GCSJ7MFIIGIRMAS4R3VT5FIFIAOXNMGDI5HPYTWS5X7HH74FSJ6STSGF"
operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V"
envelope = tx.append_manage_buy_offer_op(
selling_code=selling_code,
selling_issuer=selling_issuer,
buying_code=buying_code,
buying_issuer=buying_issuer,
amount=amount,
price=price,
source=operation_source,
).build()
tx, operations = stellar.from_envelope(envelope)
assert len(operations) == 1
assert isinstance(operations[0], messages.StellarManageBuyOfferOp)
assert operations[0].source_account == operation_source
assert operations[0].selling_asset.type == messages.StellarAssetType.NATIVE
assert operations[0].buying_asset.type == messages.StellarAssetType.ALPHANUM4
assert operations[0].buying_asset.code == buying_code
assert operations[0].buying_asset.issuer == buying_issuer
assert operations[0].amount == 500111000
assert operations[0].price_n == 1
assert operations[0].price_d == 2
assert operations[0].offer_id == 0 # indicates a new offer
def test_manage_buy_offer_update_offer():
tx = make_default_tx()
price = "0.5"
amount = "50.0111"
selling_code = "XLM"
selling_issuer = None
buying_code = "USD"
buying_issuer = "GCSJ7MFIIGIRMAS4R3VT5FIFIAOXNMGDI5HPYTWS5X7HH74FSJ6STSGF"
offer_id = 12345
operation_source = "GAEB4MRKRCONK4J7MVQXAHTNDPAECUCCCNE7YC5CKM34U3OJ673A4D6V"
envelope = tx.append_manage_buy_offer_op(
selling_code=selling_code,
selling_issuer=selling_issuer,
buying_code=buying_code,
buying_issuer=buying_issuer,
amount=amount,
price=price,
offer_id=offer_id,
source=operation_source,
).build()
tx, operations = stellar.from_envelope(envelope)
assert len(operations) == 1
assert isinstance(operations[0], messages.StellarManageBuyOfferOp)
assert operations[0].source_account == operation_source
assert operations[0].selling_asset.type == messages.StellarAssetType.NATIVE
assert operations[0].buying_asset.type == messages.StellarAssetType.ALPHANUM4
assert operations[0].buying_asset.code == buying_code
assert operations[0].buying_asset.issuer == buying_issuer
assert operations[0].amount == 500111000
assert operations[0].price_n == 1
assert operations[0].price_d == 2
assert operations[0].offer_id == offer_id

@ -846,6 +846,7 @@
"test_stellar.py::test_sign_tx[StellarCreateAccountOp]": "2582717c25974d2b3ee156624b00375148ff7fd12eeea73625a7c367fa610373",
"test_stellar.py::test_sign_tx[StellarCreatePassiveOfferOp]": "6b0f0d2b746f98e2c85006ea7e2d5c49cd9277662e47f223138ff418066791e3",
"test_stellar.py::test_sign_tx[StellarManageDataOp]": "8fbec6547a8f9d1f002181db0cbe57fe86abef8d365b1c06fd14292cd0b068a7",
"test_stellar.py::test_sign_tx[StellarManageBuyOfferOp]": "fc57e1ca8b65588aa16cc3524d6dc0f01e094ad5d16a6f7e739a69c101b554bc",
"test_stellar.py::test_sign_tx[StellarManageSellOfferOp]": "6ed84765b2ed46711be0ed1219d91c27e927119d352f37b2baf8c6501186bbce",
"test_stellar.py::test_sign_tx[StellarPathPaymentStrictReceiveOp]": "58f3bfaece0706bc172d6e6564b728ec0b7f8e2629d8c64dc60672786586076d",
"test_stellar.py::test_sign_tx[StellarPaymentOp-asset12]": "1d8e9d5d65420a259f7e2deef1efaf0ce5be966a0f1e5b8e95b832f176f00de2",

Loading…
Cancel
Save