Testa klient TON (Telegram Open Network) och nytt Fift-språk för smarta kontrakt

För mer än ett år sedan blev det känt om planerna för Telegram Messenger att släppa sitt eget decentraliserade nätverk Telegram Open Network. Sedan blev ett omfattande tekniskt dokument tillgängligt, som påstås ha skrivits av Nikolai Durov och beskrev strukturen för det framtida nätverket. För de som missade det rekommenderar jag att ni läser min återberättelse av detta dokument (Del 1, Del 2; den tredje delen, tyvärr, samlar fortfarande damm i drag).

Sedan dess har det inte kommit några betydande nyheter om statusen för TON-utveckling förrän för ett par dagar sedan (i en av inofficiella kanaler) länken till sidan visades inte https://test.ton.org/download.html, var finns:
ton-test-liteclient-full.tar.xz — Källor till en ljusklient för TON-testnätverket.
ton-lite-client-test1.config.json — Konfigurationsfil för anslutning till testnätverket.
README — Information om att bygga och lansera kunden.
HUR — Steg-för-steg-instruktioner för att skapa ett smart kontrakt med en kund.
ton.pdf — uppdaterat dokument (daterat 2 mars 2019) med en teknisk översikt över TON-nätverket;
tvm.pdf — Teknisk beskrivning av TVM (TON Virtual Machine, TON virtuell maskin).
tblkch.pdf — Teknisk beskrivning av TON-blockkedjan.
fifthbase.pdf — Beskrivning av det nya språket Fift, designat för att skapa smarta kontrakt i TON.

Jag upprepar, det fanns ingen officiell bekräftelse av sidan och alla dessa dokument från Telegram, men volymen av dessa material gör dem ganska rimliga. Starta den publicerade klienten på egen risk.

Bygga en testklient

Låt oss först försöka bygga och köra en testklient - lyckligtvis, README beskriver denna enkla process i detalj. Jag kommer att göra detta med macOS 10.14.5 som ett exempel; jag kan inte garantera framgången med bygget på andra system.

  1. Ladda ner och packa upp källarkiv. Det är viktigt att ladda ner den senaste versionen eftersom bakåtkompatibilitet inte garanteras i detta skede.

  2. Se till att de senaste versionerna av make, cmake (version 3.0.2 eller högre), OpenSSL (inklusive C-huvudfiler), g++ eller clang är installerade på systemet. Jag behövde inte installera något, allt kom ihop direkt.

  3. Låt oss anta att källorna packas upp i en mapp ~/lite-client. Separat från det skapar du en tom mapp för det sammansatta projektet (till exempel, ~/liteclient-build), och från det (cd ~/liteclient-build) anropa kommandona:

    cmake ~/lite-client
    cmake --build . --target test-lite-client

    Testa klient TON (Telegram Open Network) och nytt Fift-språk för smarta kontrakt

    För att bygga språktolken Fift för smarta kontrakt (mer om det nedan) ringer vi också

    cmake --build . --target fift

  4. Ladda ner den nuvarande konfigurationsfil för att ansluta till testnätverket och lägga det i mappen med den monterade klienten.

  5. Mål, kan du starta klienten:

    ./test-lite-client -C ton-lite-client-test1.config.json

Om allt är gjort korrekt bör du se något i stil med detta:

Testa klient TON (Telegram Open Network) och nytt Fift-språk för smarta kontrakt

Som vi kan se finns det några tillgängliga kommandon:
help — visa den här listan med kommandon;
quit - gå ut;
time — visa aktuell tid på servern;
status — visa anslutningen och den lokala databasens status;
last — uppdatera blockkedjans tillstånd (ladda ner det sista blocket). Det är viktigt att köra det här kommandot innan några förfrågningar för att vara säker på att du ser det aktuella tillståndet för nätverket.
sendfile <filename> — ladda upp en lokal fil till TON-nätverket. Det är så interaktion med nätverket sker – inklusive till exempel skapandet av nya smarta kontrakt och förfrågningar om att överföra pengar mellan konton;
getaccount <address> — visa strömmen (vid den tidpunkt då kommandot utfördes) last) statusen för kontot med den angivna adressen;
privkey <filename> — ladda den privata nyckeln från en lokal fil.

Om du, när du startar klienten, överför en mapp till den med alternativet -D, så kommer han att lägga till det sista blocket av masterchain i det:

./test-lite-client -C ton-lite-client-test1.config.json -D ~/ton-db-dir

Nu kan vi gå vidare till mer intressanta saker - lär oss Fift-språket, försök att kompilera ett smart kontrakt (till exempel skapa en testplånbok), ladda upp den till nätverket och prova att överföra pengar mellan konton.

Language Fift

Från dokument fifthbase.pdf du kan ta reda på att Telegram-teamet har skapat ett nytt stackspråk för att skapa smarta kontrakt Fift (uppenbarligen från siffran femte, liknande Forth, ett språk med vilket Fifth har mycket gemensamt).

Dokumentet är ganska omfattande, 87 sidor, och jag kommer inte att återberätta innehållet i detalj inom ramen för denna artikel (åtminstone eftersom jag inte har läst klart det själv :). Jag kommer att fokusera på huvudpunkterna och ge ett par kodexempel på detta språk.

På en grundläggande nivå är Fifts syntax ganska enkel: dess kod består av ord, vanligtvis åtskilda av mellanslag eller radbrytningar (speciellt fall: vissa ord kräver inte en avgränsare efter sig). Några слово är en skiftlägeskänslig sekvens av tecken som motsvarar en viss bestämning av (ungefär vad tolken ska göra när den stöter på detta ord). Om det inte finns någon definition av ett ord, försöker tolken analysera det som ett tal och lägga det på högen. Förresten, talen här är - plötsligt - 257-bitars heltal, och det finns inga bråk alls - mer exakt, de förvandlas omedelbart till ett par heltal, som bildar täljaren och nämnaren för ett rationellt bråk.

Ord tenderar att interagera med värden överst i stapeln. En separat typ av ord - prefix — använder inte stacken, utan de efterföljande tecknen från källfilen. Till exempel är det så här strängliteraler implementeras - citattecknet (") är ett prefixord som letar efter nästa (avslutande) citat och skjuter strängen mellan dem på stapeln. Oneliners beter sig på samma sätt (//) och multiline (/*) kommentarer.

Det är här nästan hela språkets interna struktur slutar. Allt annat (inklusive kontrollkonstruktioner) definieras som ord (antingen interna, såsom aritmetiska operationer och definitionen av nya ord; eller definieras i "standardbiblioteket" Fift.fif, som finns i mappen crypto/fift i källorna).

Ett enkelt exempelprogram i Fift:

{ dup =: x dup * =: y } : setxy
3 setxy x . y . x y + .
7 setxy x . y . x y + .

Den första raden definierar ett nytt ord setxy (notera prefixet {, vilket skapar ett block före det avslutande } och prefix :, som faktiskt definierar ordet). setxy tar ett nummer från toppen av stacken, definierar (eller omdefinierar) det som globalt konstant x, och kvadraten på detta tal som en konstant y (Med tanke på att värdena på konstanter kan omdefinieras, skulle jag hellre kalla dem variabler, men jag följer namnkonventionen i språket).

De följande två raderna lägger ett nummer på högen och ringer setxy, då visas värdena för konstanterna x, y (ordet används för utdata .), placeras båda konstanterna på stapeln, summeras, och resultatet skrivs också ut. Som ett resultat kommer vi att se:

3 9 12 ok
7 49 56 ok

(Raden "ok" skrivs ut av tolken när den avslutar bearbetningen av den aktuella raden i interaktivt inmatningsläge)

Tja, ett fullständigt kodexempel:

"Asm.fif" include

-1 constant wc  // create a wallet in workchain -1 (masterchain)

// Create new simple wallet
<{  SETCP0 DUP IFNOTRET INC 32 THROWIF  // return if recv_internal, fail unless recv_external
    512 INT LDSLICEX DUP 32 PLDU   // sign cs cnt
    c4 PUSHCTR CTOS 32 LDU 256 LDU ENDS  // sign cs cnt cnt' pubk
    s1 s2 XCPU            // sign cs cnt pubk cnt' cnt
    EQUAL 33 THROWIFNOT   // ( seqno mismatch? )
    s2 PUSH HASHSU        // sign cs cnt pubk hash
    s0 s4 s4 XC2PU        // pubk cs cnt hash sign pubk
    CHKSIGNU              // pubk cs cnt ?
    34 THROWIFNOT         // signature mismatch
    ACCEPT
    SWAP 32 LDU NIP 
    DUP SREFS IF:<{
      8 LDU LDREF         // pubk cnt mode msg cs
      s0 s2 XCHG SENDRAWMSG  // pubk cnt cs ; ( message sent )
    }>
    ENDS
    INC NEWC 32 STU 256 STU ENDC c4 POPCTR
}>c
// code
<b 0 32 u, 
   newkeypair swap dup constant wallet_pk 
   "new-wallet.pk" B>file
   B, 
b> // data
// no libraries
<b b{00110} s, rot ref, swap ref, b>  // create StateInit
dup ."StateInit: " <s csr. cr
dup hash dup constant wallet_addr
."new wallet address = " wc . .": " dup x. cr
wc over 7 smca>$ type cr
256 u>B "new-wallet.addr" B>file
<b 0 32 u, b>
dup ."signing message: " <s csr. cr
dup hash wallet_pk ed25519_sign_uint rot
<b b{1000100} s, wc 8 i, wallet_addr 256 u, b{000010} s, swap <s s, b{0} s, swap B, swap <s s, b>
dup ."External message for initialization is " <s csr. cr
2 boc+>B dup Bx. cr
"new-wallet-query.boc" tuck B>file
."(Saved to file " type .")" cr

Denna läskiga fil är till för att skapa ett smart kontrakt - den kommer att placeras i en fil new-wallet-query.boc efter avrättningen. Observera att ett annat sammansättningsspråk används här för TON Virtual Machine (jag kommer inte att uppehålla mig i detalj), vars instruktioner kommer att placeras på blockchain.

Således är assemblern för TVM skriven i Fift - källorna till denna assembler finns i filen crypto/fift/Asm.fif och är anslutna i början av ovanstående kodstycke.

Vad kan jag säga, tydligen älskar Nikolai Durov att skapa nya programmeringsspråk :)

Skapa ett smart kontrakt och interagera med TON

Så låt oss anta att vi har satt ihop TON-klienten och Fift-tolken enligt beskrivningen ovan och blivit bekanta med språket. Hur skapar man ett smart kontrakt nu? Detta beskrivs i filen HUR, bifogat källorna.

Konton i TON

Som jag beskrev i TON recension, detta nätverk innehåller mer än en blockchain - det finns en vanlig, den så kallade. "huvudkedja", samt ett godtyckligt antal ytterligare "arbetskedjor", identifierade med ett 32-bitars nummer. Masterkedjan har en identifierare på -1, utöver den kan en "bas"-arbetskedja med identifieraren 0. Varje arbetskedja kan ha sin egen konfiguration. Internt är varje arbetskedja uppdelad i shardchains, men detta är en implementeringsdetalj som man inte behöver ha i åtanke.

Inom en arbetskedja lagras många konton som har sina egna account_id-identifierare. För masterkedjan och nollarbetskedjan är de 256 bitar långa. Således skrivs kontoidentifieraren till exempel så här:

-1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d

Detta är det "råa" formatet: först arbetskedjans ID, sedan ett kolon och konto-ID:t i hexadecimal notation.

Dessutom finns det ett förkortat format - arbetskedjans nummer och kontoadressen kodas i binär form, en kontrollsumma läggs till dem, och allt detta kodas i Base64:

Ef+BVndbeTJeXWLnQtm5bDC2UVpc0vH2TF2ksZPAPwcODSkb

Genom att känna till detta postformat kan vi begära det aktuella tillståndet för ett konto genom en testklient med kommandot

getaccount -1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d

Vi får något sånt här:

[ 3][t 2][1558746708.815218925][test-lite-client.cpp:631][!testnode]    requesting account state for -1:8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D
[ 3][t 2][1558746708.858564138][test-lite-client.cpp:652][!testnode]    got account state for -1:8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D with respect to blocks (-1,8000000000000000,72355):F566005749C1B97F18EDE013EBA7A054B9014961BC1AD91F475B9082919A2296:1BD5DE54333164025EE39D389ECE2E93DA2871DA616D488253953E52B50DC03F and (-1,8000000000000000,72355):F566005749C1B97F18EDE013EBA7A054B9014961BC1AD91F475B9082919A2296:1BD5DE54333164025EE39D389ECE2E93DA2871DA616D488253953E52B50DC03F
account state is (account
  addr:(addr_std
    anycast:nothing workchain_id:-1 address:x8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D)
  storage_stat:(storage_info
    used:(storage_used
      cells:(var_uint len:1 value:3)
      bits:(var_uint len:2 value:539)
      public_cells:(var_uint len:0 value:0)) last_paid:0
    due_payment:nothing)
  storage:(account_storage last_trans_lt:74208000003
    balance:(currencies
      grams:(nanograms
        amount:(var_uint len:7 value:999928362430000))
      other:(extra_currencies
        dict:hme_empty))
    state:(account_active
      (
        split_depth:nothing
        special:nothing
        code:(just
          value:(raw@^Cell 
            x{}
             x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
            ))
        data:(just
          value:(raw@^Cell 
            x{}
             x{0000000D}
            ))
        library:hme_empty))))
x{CFF8156775B79325E5D62E742D9B96C30B6515A5CD2F1F64C5DA4B193C03F070E0D2068086C000000000000000451C90E00DC0E35B7DB5FB8C134_}
 x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
 x{0000000D}

Vi ser strukturen som är lagrad i DHT för den angivna arbetskedjan. Till exempel i fält storage.balance är bytesbalansen, i storage.state.code - smart avtalskod, och in storage.state.data - dess aktuella data. Observera att TON-datalagringen - Cell, celler - är trädliknande, varje cell kan ha både sina egna data och barnceller. Detta visas som indrag på de sista raderna.

Bygga ett smart kontrakt

Låt oss nu skapa en sådan struktur själva (det kallas BOC - påse med celler) med hjälp av språket Fift. Lyckligtvis behöver du inte skriva ett smart kontrakt själv - i mappen crypto/block det finns en fil från källarkivet new-wallet.fif, som hjälper oss att skapa en ny plånbok. Låt oss kopiera den till mappen med den monterade klienten (~/liteclient-build, om du följde instruktionerna ovan). Jag citerade dess innehåll ovan som ett exempel på kod på Fift.

Kör den här filen enligt följande:

./crypto/fift -I"<source-directory>/crypto/fift" new-wallet.fif

Här <source-directory> måste ersättas med sökvägen till de uppackade källorna (symbolen "~" kan tyvärr inte användas här, hela sökvägen behövs). Istället för att använda en nyckel -I du kan definiera en miljövariabel FIFTPATH och lägg denna väg in i den.

Sedan vi lanserade Fift med filnamnet new-wallet.fif, kommer den att köra den och avsluta. Om du utelämnar filnamnet kan du spela med tolken interaktivt.

Efter körning bör något i stil med detta visas i konsolen:

StateInit: x{34_}
 x{FF0020DDA4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED54}
 x{0000000055375F730EDC2292E8CB15C42E8036EE9C25AA958EE002D2DE48A205E3A3426B}

new wallet address = -1 : 4fcd520b8fcca096b567d734be3528edc6bed005f6930a9ec9ac1aa714f211f2 
0f9PzVILj8yglrVn1zS-NSjtxr7QBfaTCp7JrBqnFPIR8nhZ
signing message: x{00000000}

External message for initialization is x{89FEE120E20C7E953E31546F64C23CD654002C1AA919ADD24DB12DDF85C6F3B58AE41198A28AD8DAF3B9588E7A629252BA3DB88F030D00BC1016110B2073359EAC3C13823C53245B65D056F2C070B940CDA09789585935C7ABA4D2AD4BED139281CFA1200000001_}
 x{FF0020DDA4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED54}
 x{0000000055375F730EDC2292E8CB15C42E8036EE9C25AA958EE002D2DE48A205E3A3426B}

B5EE9C724104030100000000D60002CF89FEE120E20C7E953E31546F64C23CD654002C1AA919ADD24DB12DDF85C6F3B58AE41198A28AD8DAF3B9588E7A629252BA3DB88F030D00BC1016110B2073359EAC3C13823C53245B65D056F2C070B940CDA09789585935C7ABA4D2AD4BED139281CFA1200000001001020084FF0020DDA4F260810200D71820D70B1FED44D0D31FD3FFD15112BAF2A122F901541044F910F2A2F80001D31F3120D74A96D307D402FB00DED1A4C8CB1FCBFFC9ED5400480000000055375F730EDC2292E8CB15C42E8036EE9C25AA958EE002D2DE48A205E3A3426B6290698B
(Saved to file new-wallet-query.boc)

Det betyder att plånboken med ID -1:4fcd520b8fcca096b567d734be3528edc6bed005f6930a9ec9ac1aa714f211f2 (eller, vad är samma, 0f9PzVILj8yglrVn1zS-NSjtxr7QBfaTCp7JrBqnFPIR8nhZ) har skapats framgångsrikt. Motsvarande kod kommer att finnas i filen new-wallet-query.boc, hans adress finns i new-wallet.addr, och den privata nyckeln är i new-wallet.pk (var försiktig - att köra skriptet igen kommer att skriva över dessa filer).

Naturligtvis känner TON-nätverket ännu inte till denna plånbok, den lagras endast i form av dessa filer. Nu måste den laddas upp till nätverket. Men problemet är att för att skapa ett smart kontrakt måste du betala en provision, och ditt kontosaldo är fortfarande noll.

I arbetsläge kommer detta problem att lösas genom att köpa gram på börsen (eller överföra från en annan plånbok). Jo, i det aktuella testläget har ett speciellt smart kontrakt skapats, från vilket man kan begära upp till 20 gram bara sådär.

Generera en förfrågan till någon annans smarta kontrakt

Vi gör en förfrågan till ett smart kontrakt som delar ut gram vänster och höger så här. I samma mapp crypto/block hitta fil testgiver.fif:

// "testgiver.addr" file>B 256 B>u@ 
0x8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d
dup constant wallet_addr ."Test giver address = " x. cr

0x4fcd520b8fcca096b567d734be3528edc6bed005f6930a9ec9ac1aa714f211f2
constant dest_addr

-1 constant wc
0x00000011 constant seqno

1000000000 constant Gram
{ Gram swap */ } : Gram*/

6.666 Gram*/ constant amount

// b x --> b'  ( serializes a Gram amount )
{ -1 { 1+ 2dup 8 * ufits } until
  rot over 4 u, -rot 8 * u, } : Gram, 

// create a message (NB: 01b00.., b = bounce)
<b b{010000100} s, wc 8 i, dest_addr 256 u, amount Gram, 0 9 64 32 + + 1+ 1+ u, "GIFT" $, b>
<b seqno 32 u, 1 8 u, swap ref, b>
dup ."enveloping message: " <s csr. cr
<b b{1000100} s, wc 8 i, wallet_addr 256 u, 0 Gram, b{00} s,
   swap <s s, b>
dup ."resulting external message: " <s csr. cr
2 boc+>B dup Bx. cr
"wallet-query.boc" B>file

Vi kommer också att spara det i mappen med den monterade klienten, men vi kommer att korrigera den femte raden - före raden "constant dest_addr". Låt oss ersätta den med adressen till plånboken som du skapade tidigare (fullständig, inte förkortad). Du behöver inte skriva "-1:" i början, lägg istället "0x" i början.

Du kan också ändra linjen 6.666 Gram*/ constant amount — det här är mängden i gram som du begär (högst 20). Även om du anger ett heltal, lämna decimalkomma.

Slutligen måste du korrigera linjen 0x00000011 constant seqno. Det första numret här är det aktuella sekvensnumret, som lagras i kontot som utfärdar gram. Var kan jag få det ifrån? Som nämnts ovan, starta klienten och kör:

last
getaccount -1:8156775b79325e5d62e742d9b96c30b6515a5cd2f1f64c5da4b193c03f070e0d

I slutet kommer smarta kontraktsdata att innehålla

...
x{FF0020DDA4F260D31F01ED44D0D31FD166BAF2A1F80001D307D4D1821804A817C80073FB0201FB00A4C8CB1FC9ED54}
 x{0000000D}

Siffran 0000000D (ditt kommer att vara större) är sekvensnumret som måste ersättas med testgiver.fif.

Det var allt, spara filen och kör (./crypto/fift testgiver.fif). Utdata kommer att vara en fil wallet-query.boc. Detta är vad som bildas сообщение till någon annans smarta kontrakt - en begäran "överför så många gram till ett sådant och ett konto."

Med hjälp av klienten laddar vi upp den till nätverket:

> sendfile wallet-query.boc
[ 1][t 1][1558747399.456575155][test-lite-client.cpp:577][!testnode]    sending query from file wallet-query.boc
[ 3][t 2][1558747399.500236034][test-lite-client.cpp:587][!query]   external message status is 1

Om du nu ringer last, och sedan återigen begära status för kontot som vi bad om gram från, då bör vi se att dess sekvensnummer har ökat med ett - det betyder att det skickade pengar till vårt konto.

Det sista steget återstår - ladda ner koden för vår plånbok (dess saldo har redan fyllts på, men utan den smarta kontraktskoden kommer vi inte att kunna hantera det). Vi genomför sendfile new-wallet-query.boc — och det är det, du har din egen plånbok på TON-nätverket (även om det bara är ett test för tillfället).

Skapa utgående transaktioner

För att överföra pengar från saldot på det skapade kontot finns det en fil crypto/block/wallet.fif, som också måste placeras i mappen med den monterade klienten.

I likhet med de föregående stegen måste du justera beloppet du överför, mottagarens adress (dest_addr) och seqno för din plånbok (den är lika med 1 efter initialisering av plånboken och ökar med 1 efter varje utgående transaktion - du kan se det genom att begära status för ditt konto). För tester kan du använda till exempel min plånbok - 0x4fcd520b8fcca096b567d734be3528edc6bed005f6930a9ec9ac1aa714f211f2.

Vid start (./crypto/fift wallet.fif) skriptet tar adressen till din plånbok (varifrån du överför) och dess privata nyckel från filerna new-wallet.addr и new-wallet.pk, och det mottagna meddelandet kommer att skrivas till new-wallet-query.boc.

Som tidigare, för att direkt utföra transaktionen, ring sendfile new-wallet-query.boc i klienten. Efter detta, glöm inte att uppdatera blockkedjans tillstånd (last) och kontrollera att saldot och seqno på vår plånbok har ändrats (getaccount <account_id>).

Testa klient TON (Telegram Open Network) och nytt Fift-språk för smarta kontrakt

Det är allt, nu kan vi skapa smarta kontrakt i TON och skicka förfrågningar till dem. Som du kan se räcker den nuvarande funktionaliteten redan för att till exempel göra en mer användarvänlig plånbok med ett grafiskt gränssnitt (dock förväntas det att den redan kommer att bli tillgänglig som en del av messengern).

Endast registrerade användare kan delta i undersökningen. Logga in, Snälla du.

Är du intresserad av att fortsätta artiklarna med analys av TON, TVM, Fift?

  • Ja, jag väntar på slutförandet av artikelserien med en allmän översikt över TON

  • Ja, det är intressant att läsa mer om språket Fift

  • Ja, jag vill lära mig mer om TON Virtual Machine och assemblern för den

  • Nej, inget av detta är intressant

39 användare röstade. 12 användare avstod från att rösta.

Vad tycker du om Telegrams planer på att lansera TON?

  • Jag har stora förhoppningar på det här projektet

  • Jag följer bara dess utveckling med intresse.

  • Jag är skeptisk och tvivlar på dess framgång.

  • Jag är benägen att betrakta detta initiativ som ett misslyckande och onödigt för de breda massorna

47 användare röstade. 12 användare avstod från att rösta.

Källa: will.com

Lägg en kommentar