Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tokens #11449

Merged
merged 12 commits into from
Jul 25, 2022
2 changes: 2 additions & 0 deletions src/lib/mina_base/account_id.ml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ module Digest = struct

let of_field = Fn.id

let to_field_unsafe = Fn.id

module Assert = struct
let equal : t -> t -> unit = Field.Assert.equal
end
Expand Down
2 changes: 2 additions & 0 deletions src/lib/mina_base/account_id.mli
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ module Digest : sig

val of_field : Pickles.Impls.Step.Field.t -> t

val to_field_unsafe : t -> Pickles.Impls.Step.Field.t
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels super hacky, does this work? I need a way to convert a Mina_base.Token_id.Checked.t to a Field.t. This type checks but I'm pretty sure the logic is wrong. If there is a better way, can someone advise how to do so?

cc @mitschabaude @bkase

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is the right approach 👍🏻 Would've done the same


module Assert : sig
val equal : t -> t -> unit
end
Expand Down
68 changes: 58 additions & 10 deletions src/lib/snarky_js_bindings/lib/snarky_js_bindings_lib.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1143,6 +1143,10 @@ let poseidon =
val events = Js.string (zkapp_events :> string)

val sequenceEvents = Js.string (zkapp_sequence_events :> string)

val partyCons = Js.string (party_cons :> string)

val partyNode = Js.string (party_node :> string)
end
end

Expand Down Expand Up @@ -2309,6 +2313,8 @@ module Ledger = struct

type account =
< publicKey : group_class Js.t Js.readonly_prop
; tokenId : field_class Js.t Js.readonly_prop
; tokenSymbol : Js.js_string Js.t Js.readonly_prop
; balance : js_uint64 Js.readonly_prop
; nonce : js_uint32 Js.readonly_prop
; zkapp : zkapp_account Js.readonly_prop >
Expand Down Expand Up @@ -2428,6 +2434,12 @@ module Ledger = struct
let create_new_account_exn (t : L.t) account_id account =
L.create_new_account t account_id account |> Or_error.ok_exn

let public_key_checked (pk : public_key) :
Signature_lib.Public_key.Compressed.var =
let x = pk##.g##.x##.value in
let y = pk##.g##.y##.value in
Signature_lib.Public_key.compress_var (x, y) |> Impl.run_checked

let public_key (pk : public_key) : Signature_lib.Public_key.Compressed.t =
{ x = to_unchecked pk##.g##.x##.value
; is_odd = Bigint.(test_bit (of_field (to_unchecked pk##.g##.y##.value)) 0)
Expand All @@ -2439,8 +2451,22 @@ module Ledger = struct
(fun () -> failwith "invalid scalar")
Fn.id

let account_id pk =
Mina_base.Account_id.create (public_key pk) Mina_base.Token_id.default
let token_id_checked (token : field_class Js.t) =
token |> of_js_field |> Mina_base.Token_id.Checked.of_field

let token_id (token : field_class Js.t) : Mina_base.Token_id.t =
token |> of_js_field_unchecked |> Mina_base.Token_id.of_field

let default_token_id_js =
Mina_base.Token_id.default |> Mina_base.Token_id.to_field_unsafe
|> Field.constant |> to_js_field

let account_id_checked pk token =
Mina_base.Account_id.Checked.create (public_key_checked pk)
(token_id_checked token)

let account_id pk token =
Mina_base.Account_id.create (public_key pk) (token_id token)

let max_state_size =
Pickles_types.Nat.to_int Mina_base.Zkapp_state.Max_state_size.n
Expand Down Expand Up @@ -2514,6 +2540,9 @@ module Ledger = struct
let x, y = Signature_lib.Public_key.decompress_exn pk in
to_js_group (Field.constant x) (Field.constant y)

let token_id (token_id : Mina_base.Token_id.t) =
token_id |> Mina_base.Token_id.to_field_unsafe |> field

let private_key (sk : Signature_lib.Private_key.t) = to_js_scalar sk

let signature (sg : Signature_lib.Schnorr.Chunked.Signature.t) =
Expand All @@ -2528,6 +2557,10 @@ module Ledger = struct
object%js
val publicKey = public_key a.public_key

val tokenId = token_id a.token_id

val tokenSymbol = Js.string a.token_symbol

val balance = uint64 (Currency.Balance.to_uint64 a.balance)

val nonce = uint32 (Mina_numbers.Account_nonce.to_uint32 a.nonce)
Expand Down Expand Up @@ -2632,7 +2665,8 @@ module Ledger = struct
val party = to_js_field_unchecked (party.elt.party_digest :> Impl.field)

val calls =
to_js_field_unchecked (Parties.Digest.Forest.empty :> Impl.field)
to_js_field_unchecked
(Parties.Call_forest.hash party.elt.calls :> Impl.field)
end

let sign_field_element (x : field_class Js.t) (key : private_key) =
Expand Down Expand Up @@ -2744,7 +2778,7 @@ module Ledger = struct
@@ Mina_base.Signed_command_memo.create_from_string_exn @@ Js.to_string memo

let add_account_exn (l : L.t) pk (balance : string) =
let account_id = account_id pk in
let account_id = account_id pk default_token_id_js in
let bal_u64 = Unsigned.UInt64.of_string balance in
let balance = Currency.Balance.of_uint64 bal_u64 in
let a : Mina_base.Account.t =
Expand All @@ -2765,8 +2799,9 @@ module Ledger = struct
add_account_exn l a##.publicKey (Js.to_string a##.balance) ) ;
new%js ledger_constr l

let get_account l (pk : public_key) : account Js.optdef =
let loc = L.location_of_account l##.value (account_id pk) in
let get_account l (pk : public_key) (token : field_class Js.t) :
account Js.optdef =
let loc = L.location_of_account l##.value (account_id pk token) in
let account = Option.bind loc ~f:(L.get l##.value) in
To_js.option To_js.account account

Expand Down Expand Up @@ -2834,19 +2869,31 @@ module Ledger = struct
in
apply_parties_transaction l txn (Js.to_string account_creation_fee)

let create_token_account pk token =
account_id pk token |> Mina_base.Account_id.public_key
|> Signature_lib.Public_key.Compressed.to_string |> Js.string

let custom_token_id_checked pk token =
Mina_base.Account_id.Checked.derive_token_id
~owner:(account_id_checked pk token)
|> Mina_base.Account_id.Digest.Checked.to_field_unsafe |> to_js_field

let custom_token_id_unchecked pk token =
Mina_base.Account_id.derive_token_id ~owner:(account_id pk token)
|> Mina_base.Token_id.to_field_unsafe |> to_js_field_unchecked

let () =
let static_method name f =
Js.Unsafe.set ledger_class (Js.string name) (Js.wrap_callback f)
in
let method_ name (f : ledger_class Js.t -> _) =
method_ ledger_class name f
in
static_method "customTokenId" custom_token_id_unchecked ;
static_method "customTokenIdChecked" custom_token_id_checked ;
static_method "createTokenAccount" create_token_account ;
static_method "create" create ;

static_method "hashParty" hash_party ;
static_method "hashTransaction" hash_transaction ;
static_method "hashTransactionChecked" hash_transaction_checked ;

static_method "transactionCommitments" transaction_commitments ;
static_method "zkappPublicInput" zkapp_public_input ;
static_method "signFieldElement" sign_field_element ;
Expand All @@ -2862,6 +2909,7 @@ module Ledger = struct
static_method "fieldOfBase58" field_of_base58 ;
static_method "memoToBase58" memo_to_base58 ;

static_method "hashPartyFromJson" hash_party ;
static_method "hashPartyFromFields"
(Checked.fields_to_hash
(Mina_base.Party.Body.typ ())
Expand Down
2 changes: 1 addition & 1 deletion src/lib/snarky_js_bindings/snarkyjs
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,7 @@ let jsonDeploy = await deploy(SimpleZkapp, {
zkappKey,
verificationKey,
initialBalance,
feePayerKey: sender.privateKey,
shouldSignFeePayer: true,
transactionFee,
feePayer: { feePayerKey: sender.privateKey, fee: transactionFee },
});
toc();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,8 @@ toc();
tic("create deploy transaction");
let partiesJsonDeploy = await deploy(SimpleZkapp, {
zkappKey,
verificationKey,
initialBalance,
feePayerKey: sender.privateKey,
shouldSignFeePayer: true,
transactionFee,
feePayer: { feePayerKey: sender.privateKey, fee: transactionFee },
});
toc();

Expand Down
199 changes: 199 additions & 0 deletions src/lib/snarky_js_bindings/test_module/simple-zkapp-token.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import {
Field,
declareState,
declareMethods,
State,
PublicKey,
PrivateKey,
SmartContract,
isReady,
shutdown,
Mina,
Permissions,
Party,
UInt64,
Ledger,
Token,
getDefaultTokenId,
} from "snarkyjs";

function sendTransaction(tx) {
// console.log("DEBUG -- TXN\n", JSON.stringify(partiesToJson(tx.transaction)));
tx.send();
}

await isReady;

// declare the zkapp
class SimpleZkapp extends SmartContract {
constructor(address) {
super(address);
this.x = State();
}

deploy(args) {
super.deploy(args);
this.setPermissions({
...Permissions.default(),
editState: Permissions.proofOrSignature(),
});
this.balance.addInPlace(UInt64.fromNumber(initialBalance));
this.x.set(initialState);
this.tokenSymbol.set("TEST_TOKEN");
console.log(
"Current tokenId while deploying: ",
Ledger.fieldToBase58(this.tokenId)
);
}

update(y) {
let x = this.x.get();
this.x.set(x.add(y));
}

initialize() {
this.x.set(initialState);
}

mint(receiverAddress) {
let amount = UInt64.from(1_000_000);
this.token().mint({
address: receiverAddress,
amount,
});
console.log(`Minting ${amount} to ${receiverAddress.toBase58()}`);
}

burn(receiverAddress) {
let amount = UInt64.from(1_000);
this.token().burn({
address: receiverAddress,
amount,
});
console.log(`Burning ${amount} to ${receiverAddress.toBase58()}`);
}

send(senderAddress, receiverAddress) {
let amount = UInt64.from(1_000);
this.token().send({
from: senderAddress,
to: receiverAddress,
amount,
});
console.log(`Sending ${amount} to ${receiverAddress.toBase58()}`);
}
}
// note: this is our non-typescript way of doing what our decorators do
declareState(SimpleZkapp, { x: Field });
declareMethods(SimpleZkapp, {
initialize: [],
update: [Field],
send: [PublicKey, PublicKey],
mint: [PublicKey],
burn: [PublicKey],
});

let Local = Mina.LocalBlockchain();
Mina.setActiveInstance(Local);

// a test account that pays all the fees, and puts additional funds into the zkapp
let feePayer = Local.testAccounts[0].privateKey;

// the zkapp account
let zkappKey = PrivateKey.fromBase58(
"EKEfEZpMctKoyon4nxhqFBiKyUsCyyZReF9fbs21nDrrTgGMTcok"
);
let zkappAddress = zkappKey.toPublicKey();

let tokenAccount1Key = Local.testAccounts[1].privateKey;
let tokenAccount1 = tokenAccount1Key.toPublicKey();

let tokenAccount2Key = Local.testAccounts[2].privateKey;
let tokenAccount2 = tokenAccount2Key.toPublicKey();

let initialBalance = 10_000_000_000;
let initialState = Field(1);
let zkapp = new SimpleZkapp(zkappAddress);
let tx;

console.log("deploy");
tx = await Local.transaction(feePayer, () => {
Party.fundNewAccount(feePayer, { initialBalance });
zkapp.deploy({ zkappKey });
});
sendTransaction(tx);

console.log(`initial balance: ${zkapp.account.balance.get().div(1e9)} MINA`);

// Log custom token info
const customToken = new Token({ tokenOwner: zkappAddress });
console.log("---FEE PAYER", feePayer.toPublicKey().toBase58());
console.log("---TOKEN OWNER", zkappAddress.toBase58());
console.log("---CUSTOM TOKEN", Ledger.fieldToBase58(customToken.id));
console.log(`---TOKEN SYMBOL ${Mina.getAccount(zkappAddress).tokenSymbol}`);
console.log("---TOKEN ACCOUNT1", tokenAccount1.toBase58());
console.log("---TOKEN ACCOUNT2", tokenAccount2.toBase58());
console.log(
"---CUSTOM TOKEN CHECKED",
Ledger.fieldToBase58(
Ledger.customTokenIdChecked(zkappAddress, getDefaultTokenId())
)
);
console.log(
"---CUSTOM TOKEN UNCHECKED",
Ledger.fieldToBase58(Ledger.customTokenId(zkappAddress, getDefaultTokenId()))
);

console.log("----------token minting----------");
tx = await Local.transaction(feePayer, () => {
Party.fundNewAccount(feePayer);
zkapp.mint(tokenAccount1);
zkapp.sign(zkappKey);
});
sendTransaction(tx);

console.log(
`tokenAccount1 balance: ${Mina.getBalance(
tokenAccount1,
customToken.id
)} custom tokens`
);

console.log("----------token burning----------");
tx = await Local.transaction(feePayer, () => {
zkapp.burn(tokenAccount1);
zkapp.sign(zkappKey);
});
tx = tx.sign([tokenAccount1Key]);
sendTransaction(tx);

console.log(
`tokenAccount1 balance: ${Mina.getBalance(
tokenAccount1,
customToken.id
)} custom tokens`
);

console.log("----------token transfer----------");
tx = await Local.transaction(feePayer, () => {
Party.fundNewAccount(feePayer);
zkapp.send(tokenAccount1, tokenAccount2);
zkapp.sign(zkappKey);
});
tx = tx.sign([tokenAccount1Key, tokenAccount2Key]);
sendTransaction(tx);

console.log(
`tokenAccount1 balance: ${Mina.getBalance(
tokenAccount1,
customToken.id
)} custom tokens`
);
console.log(
`tokenAccount2 balance: ${Mina.getBalance(
tokenAccount2,
customToken.id
)} custom tokens`
);

shutdown();
Loading