From 80a84cc8865330f3495abcb0dbfaad8f63565a1b Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 19 Feb 2024 16:57:36 +0100 Subject: [PATCH 01/21] move token() off of account update, deprecate on smart contract --- src/lib/account-update.ts | 94 ------------------------- src/lib/mina/token/token-contract.ts | 8 +++ src/lib/mina/token/token-methods.ts | 100 +++++++++++++++++++++++++++ src/lib/zkapp.ts | 7 +- 4 files changed, 113 insertions(+), 96 deletions(-) create mode 100644 src/lib/mina/token/token-methods.ts diff --git a/src/lib/account-update.ts b/src/lib/account-update.ts index f8673c7ad4..1447d24253 100644 --- a/src/lib/account-update.ts +++ b/src/lib/account-update.ts @@ -642,100 +642,6 @@ class AccountUpdate implements Types.AccountUpdate { return cloned; } - token() { - let thisAccountUpdate = this; - let tokenOwner = this.publicKey; - let parentTokenId = this.tokenId; - let id = TokenId.derive(tokenOwner, parentTokenId); - - function getApprovedAccountUpdate( - accountLike: PublicKey | AccountUpdate | SmartContract, - label: string - ) { - if (isSmartContract(accountLike)) { - accountLike = accountLike.self; - } - if (accountLike instanceof AccountUpdate) { - accountLike.tokenId.assertEquals(id); - thisAccountUpdate.approve(accountLike); - } - if (accountLike instanceof PublicKey) { - accountLike = AccountUpdate.defaultAccountUpdate(accountLike, id); - thisAccountUpdate.approve(accountLike); - } - if (!accountLike.label) - accountLike.label = `${ - thisAccountUpdate.label ?? 'Unlabeled' - }.${label}`; - return accountLike; - } - - return { - id, - parentTokenId, - tokenOwner, - - /** - * Mints token balance to `address`. Returns the mint account update. - */ - mint({ - address, - amount, - }: { - address: PublicKey | AccountUpdate | SmartContract; - amount: number | bigint | UInt64; - }) { - let receiver = getApprovedAccountUpdate(address, 'token.mint()'); - receiver.balance.addInPlace(amount); - return receiver; - }, - - /** - * Burn token balance on `address`. Returns the burn account update. - */ - burn({ - address, - amount, - }: { - address: PublicKey | AccountUpdate | SmartContract; - amount: number | bigint | UInt64; - }) { - let sender = getApprovedAccountUpdate(address, 'token.burn()'); - - // Sub the amount to burn from the sender's account - sender.balance.subInPlace(amount); - - // Require signature from the sender account being deducted - sender.body.useFullCommitment = Bool(true); - Authorization.setLazySignature(sender); - return sender; - }, - - /** - * Move token balance from `from` to `to`. Returns the `to` account update. - */ - send({ - from, - to, - amount, - }: { - from: PublicKey | AccountUpdate | SmartContract; - to: PublicKey | AccountUpdate | SmartContract; - amount: number | bigint | UInt64; - }) { - let sender = getApprovedAccountUpdate(from, 'token.send() (sender)'); - sender.balance.subInPlace(amount); - sender.body.useFullCommitment = Bool(true); - Authorization.setLazySignature(sender); - - let receiver = getApprovedAccountUpdate(to, 'token.send() (receiver)'); - receiver.balance.addInPlace(amount); - - return receiver; - }, - }; - } - get tokenId() { return this.body.tokenId; } diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index 58a0984a0e..1c829e963f 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -10,6 +10,7 @@ import { } from '../../account-update.js'; import { DeployArgs, SmartContract } from '../../zkapp.js'; import { TokenAccountUpdateIterator } from './forest-iterator.js'; +import { tokenMethods } from './token-methods.js'; export { TokenContract }; @@ -33,6 +34,13 @@ abstract class TokenContract extends SmartContract { }); } + /** + * Helper methods to use on a token contract. + */ + get token() { + return tokenMethods(this.self); + } + // APPROVABLE API has to be specified by subclasses, // but the hard part is `forEachUpdate()` diff --git a/src/lib/mina/token/token-methods.ts b/src/lib/mina/token/token-methods.ts new file mode 100644 index 0000000000..bfff2f0141 --- /dev/null +++ b/src/lib/mina/token/token-methods.ts @@ -0,0 +1,100 @@ +import { AccountUpdate, Authorization, TokenId } from '../../account-update.js'; +import { isSmartContract } from '../smart-contract-base.js'; +import { PublicKey } from '../../signature.js'; +import type { SmartContract } from '../../zkapp.js'; +import { UInt64 } from '../../int.js'; +import { Bool, Field } from '../../core.js'; + +export { tokenMethods }; + +function tokenMethods(self: AccountUpdate) { + let tokenOwner = self.publicKey; + let parentTokenId = self.tokenId; + let id = TokenId.derive(tokenOwner, parentTokenId); + + return { + id, + parentTokenId, + tokenOwner, + + /** + * Mints token balance to `address`. Returns the mint account update. + */ + mint({ + address, + amount, + }: { + address: PublicKey | AccountUpdate | SmartContract; + amount: number | bigint | UInt64; + }) { + let receiver = getApprovedUpdate(self, id, address, 'token.mint()'); + receiver.balance.addInPlace(amount); + return receiver; + }, + + /** + * Burn token balance on `address`. Returns the burn account update. + */ + burn({ + address, + amount, + }: { + address: PublicKey | AccountUpdate | SmartContract; + amount: number | bigint | UInt64; + }) { + let sender = getApprovedUpdate(self, id, address, 'token.burn()'); + + // Sub the amount to burn from the sender's account + sender.balance.subInPlace(amount); + + // Require signature from the sender account being deducted + sender.body.useFullCommitment = Bool(true); + Authorization.setLazySignature(sender); + return sender; + }, + + /** + * Move token balance from `from` to `to`. Returns the `to` account update. + */ + send({ + from, + to, + amount, + }: { + from: PublicKey | AccountUpdate | SmartContract; + to: PublicKey | AccountUpdate | SmartContract; + amount: number | bigint | UInt64; + }) { + let sender = getApprovedUpdate(self, id, from, 'token.send() (sender)'); + sender.balance.subInPlace(amount); + sender.body.useFullCommitment = Bool(true); + Authorization.setLazySignature(sender); + + let receiver = getApprovedUpdate(self, id, to, 'token.send() (receiver)'); + receiver.balance.addInPlace(amount); + + return receiver; + }, + }; +} + +function getApprovedUpdate( + self: AccountUpdate, + tokenId: Field, + child: PublicKey | AccountUpdate | SmartContract, + label: string +) { + if (isSmartContract(child)) { + child = child.self; + } + if (child instanceof AccountUpdate) { + child.tokenId.assertEquals(tokenId); + self.approve(child); + } + if (child instanceof PublicKey) { + child = AccountUpdate.defaultAccountUpdate(child, tokenId); + self.approve(child); + } + if (!child.label) child.label = `${self.label ?? 'Unlabeled'}.${label}`; + return child; +} diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index d927c490ae..309dc0a421 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -64,6 +64,7 @@ import { accountUpdateLayout, smartContractContext, } from './mina/smart-contract-context.js'; +import { tokenMethods } from './mina/token/token-methods.js'; // external API export { SmartContract, method, DeployArgs, declareMethods, Account, Reducer }; @@ -846,10 +847,12 @@ super.init(); return this.self.currentSlot; } /** - * Token of the {@link SmartContract}. + * @deprecated `SmartContract.token` will be removed, and token methods will only be available on `TokenContract`. + * + * For security reasons, it is recommended to use `TokenContract` as the base contract for tokens. */ get token() { - return this.self.token(); + return tokenMethods(this.self); } /** From d9751011374afcf6168e1991823d21b21dad7a40 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 19 Feb 2024 17:18:30 +0100 Subject: [PATCH 02/21] additional refactor, rename to TokenContract.internal --- src/lib/mina/token/token-contract.ts | 19 +++++++++--- .../mina/token/token-contract.unit-test.ts | 2 +- src/lib/mina/token/token-methods.ts | 30 +++++++++++++------ src/lib/zkapp.ts | 6 ++-- 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index 1c829e963f..e101efce1d 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -7,6 +7,7 @@ import { AccountUpdateForest, AccountUpdateTree, Permissions, + TokenId, } from '../../account-update.js'; import { DeployArgs, SmartContract } from '../../zkapp.js'; import { TokenAccountUpdateIterator } from './forest-iterator.js'; @@ -35,9 +36,16 @@ abstract class TokenContract extends SmartContract { } /** - * Helper methods to use on a token contract. + * The token ID of the token managed by this contract. */ - get token() { + deriveTokenId() { + return TokenId.derive(this.address, this.tokenId); + } + + /** + * Helper methods to use from within a token contract. + */ + get internal() { return tokenMethods(this.self); } @@ -55,7 +63,10 @@ abstract class TokenContract extends SmartContract { updates: AccountUpdateForest, callback: (update: AccountUpdate, usesToken: Bool) => void ) { - let iterator = TokenAccountUpdateIterator.create(updates, this.token.id); + let iterator = TokenAccountUpdateIterator.create( + updates, + this.deriveTokenId() + ); // iterate through the forest and apply user-defined logc for (let i = 0; i < MAX_ACCOUNT_UPDATES; i++) { @@ -119,7 +130,7 @@ abstract class TokenContract extends SmartContract { amount: UInt64 | number | bigint ) { // coerce the inputs to AccountUpdate and pass to `approveUpdates()` - let tokenId = this.token.id; + let tokenId = this.deriveTokenId(); if (from instanceof PublicKey) { from = AccountUpdate.defaultAccountUpdate(from, tokenId); from.requireSignature(); diff --git a/src/lib/mina/token/token-contract.unit-test.ts b/src/lib/mina/token/token-contract.unit-test.ts index bcbe2bc882..a62ebf160c 100644 --- a/src/lib/mina/token/token-contract.unit-test.ts +++ b/src/lib/mina/token/token-contract.unit-test.ts @@ -25,7 +25,7 @@ class ExampleTokenContract extends TokenContract { super.init(); // mint the entire supply to the token account with the same address as this contract - this.token.mint({ address: this.address, amount: this.SUPPLY }); + this.internal.mint({ address: this.address, amount: this.SUPPLY }); // pay fees for opened account this.balance.subInPlace(Mina.getNetworkConstants().accountCreationFee); diff --git a/src/lib/mina/token/token-methods.ts b/src/lib/mina/token/token-methods.ts index bfff2f0141..701bf36d36 100644 --- a/src/lib/mina/token/token-methods.ts +++ b/src/lib/mina/token/token-methods.ts @@ -5,18 +5,10 @@ import type { SmartContract } from '../../zkapp.js'; import { UInt64 } from '../../int.js'; import { Bool, Field } from '../../core.js'; -export { tokenMethods }; +export { tokenMethods, deprecatedToken }; function tokenMethods(self: AccountUpdate) { - let tokenOwner = self.publicKey; - let parentTokenId = self.tokenId; - let id = TokenId.derive(tokenOwner, parentTokenId); - return { - id, - parentTokenId, - tokenOwner, - /** * Mints token balance to `address`. Returns the mint account update. */ @@ -27,6 +19,7 @@ function tokenMethods(self: AccountUpdate) { address: PublicKey | AccountUpdate | SmartContract; amount: number | bigint | UInt64; }) { + let id = TokenId.derive(self.publicKey, self.tokenId); let receiver = getApprovedUpdate(self, id, address, 'token.mint()'); receiver.balance.addInPlace(amount); return receiver; @@ -42,6 +35,7 @@ function tokenMethods(self: AccountUpdate) { address: PublicKey | AccountUpdate | SmartContract; amount: number | bigint | UInt64; }) { + let id = TokenId.derive(self.publicKey, self.tokenId); let sender = getApprovedUpdate(self, id, address, 'token.burn()'); // Sub the amount to burn from the sender's account @@ -65,6 +59,7 @@ function tokenMethods(self: AccountUpdate) { to: PublicKey | AccountUpdate | SmartContract; amount: number | bigint | UInt64; }) { + let id = TokenId.derive(self.publicKey, self.tokenId); let sender = getApprovedUpdate(self, id, from, 'token.send() (sender)'); sender.balance.subInPlace(amount); sender.body.useFullCommitment = Bool(true); @@ -78,6 +73,8 @@ function tokenMethods(self: AccountUpdate) { }; } +// helper + function getApprovedUpdate( self: AccountUpdate, tokenId: Field, @@ -98,3 +95,18 @@ function getApprovedUpdate( if (!child.label) child.label = `${self.label ?? 'Unlabeled'}.${label}`; return child; } + +// deprecated token interface for `SmartContract` + +function deprecatedToken(self: AccountUpdate) { + let tokenOwner = self.publicKey; + let parentTokenId = self.tokenId; + let id = TokenId.derive(tokenOwner, parentTokenId); + + return { + id, + parentTokenId, + tokenOwner, + ...tokenMethods(self), + }; +} diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 309dc0a421..aff97c04c3 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -64,7 +64,7 @@ import { accountUpdateLayout, smartContractContext, } from './mina/smart-contract-context.js'; -import { tokenMethods } from './mina/token/token-methods.js'; +import { deprecatedToken } from './mina/token/token-methods.js'; // external API export { SmartContract, method, DeployArgs, declareMethods, Account, Reducer }; @@ -847,12 +847,12 @@ super.init(); return this.self.currentSlot; } /** - * @deprecated `SmartContract.token` will be removed, and token methods will only be available on `TokenContract`. + * @deprecated `SmartContract.token` will be removed, and token methods will only be available on `TokenContract.internal`. * * For security reasons, it is recommended to use `TokenContract` as the base contract for tokens. */ get token() { - return tokenMethods(this.self); + return deprecatedToken(this.self); } /** From b7210573052b46b9552d0a65cb2e711bdbd6e3b5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 19 Feb 2024 17:22:51 +0100 Subject: [PATCH 03/21] fix tests where immediately possible --- src/examples/zkapps/dex/dex.ts | 4 ++-- src/examples/zkapps/dex/erc20.ts | 4 ++-- src/examples/zkapps/token-with-proofs.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/examples/zkapps/dex/dex.ts b/src/examples/zkapps/dex/dex.ts index 5799e6f80a..b909030af1 100644 --- a/src/examples/zkapps/dex/dex.ts +++ b/src/examples/zkapps/dex/dex.ts @@ -377,7 +377,7 @@ class TokenContract extends BaseTokenContract { * * we mint the max uint64 of tokens here, so that we can overflow it in tests if we just mint a bit more */ - let receiver = this.token.mint({ + let receiver = this.internal.mint({ address: this.address, amount: UInt64.MAXINT(), }); @@ -393,7 +393,7 @@ class TokenContract extends BaseTokenContract { * mint additional tokens to some user, so we can overflow token balances */ @method init2() { - let receiver = this.token.mint({ + let receiver = this.internal.mint({ address: addresses.user, amount: UInt64.from(10n ** 6n), }); diff --git a/src/examples/zkapps/dex/erc20.ts b/src/examples/zkapps/dex/erc20.ts index a9cc97781b..bbc7585fc0 100644 --- a/src/examples/zkapps/dex/erc20.ts +++ b/src/examples/zkapps/dex/erc20.ts @@ -92,7 +92,7 @@ class TrivialCoin extends TokenContract implements Erc20Like { // mint the entire supply to the token account with the same address as this contract let address = this.self.body.publicKey; - let receiver = this.token.mint({ address, amount: this.SUPPLY }); + let receiver = this.internal.mint({ address, amount: this.SUPPLY }); // assert that the receiving account is new, so this can be only done once receiver.account.isNew.requireEquals(Bool(true)); @@ -120,7 +120,7 @@ class TrivialCoin extends TokenContract implements Erc20Like { balanceOf(owner: PublicKey | AccountUpdate): UInt64 { let update = owner instanceof PublicKey - ? AccountUpdate.create(owner, this.token.id) + ? AccountUpdate.create(owner, this.deriveTokenId()) : owner; this.approveAccountUpdate(update); return update.account.balance.getAndRequireEquals(); diff --git a/src/examples/zkapps/token-with-proofs.ts b/src/examples/zkapps/token-with-proofs.ts index 33e7b3fd96..4911b0e519 100644 --- a/src/examples/zkapps/token-with-proofs.ts +++ b/src/examples/zkapps/token-with-proofs.ts @@ -18,12 +18,12 @@ class Token extends TokenContract { @method mint(receiverAddress: PublicKey) { let amount = 1_000_000; - this.token.mint({ address: receiverAddress, amount }); + this.internal.mint({ address: receiverAddress, amount }); } @method burn(receiverAddress: PublicKey) { let amount = 1_000; - this.token.burn({ address: receiverAddress, amount }); + this.internal.burn({ address: receiverAddress, amount }); } } From 56bd4c409539091500d13bcf400461870bb799f1 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 19 Feb 2024 17:22:57 +0100 Subject: [PATCH 04/21] tweak comment --- src/lib/mina/token/token-contract.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index e101efce1d..2e39e2e118 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -36,7 +36,7 @@ abstract class TokenContract extends SmartContract { } /** - * The token ID of the token managed by this contract. + * Returns the `tokenId` of the token managed by this contract. */ deriveTokenId() { return TokenId.derive(this.address, this.tokenId); From 8f7546ce39eb5118df0aa7a25e2b1a09da8f85a3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 19 Feb 2024 17:31:44 +0100 Subject: [PATCH 05/21] refactor dex with actions to be a token contract --- src/examples/zkapps/dex/dex-with-actions.ts | 42 +++++++++++---------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/examples/zkapps/dex/dex-with-actions.ts b/src/examples/zkapps/dex/dex-with-actions.ts index add16ae08b..6b8a7b3014 100644 --- a/src/examples/zkapps/dex/dex-with-actions.ts +++ b/src/examples/zkapps/dex/dex-with-actions.ts @@ -6,6 +6,7 @@ import { Account, AccountUpdate, + AccountUpdateForest, Field, InferProvable, Mina, @@ -16,6 +17,7 @@ import { SmartContract, State, Struct, + TokenContract, TokenId, UInt64, method, @@ -23,17 +25,23 @@ import { } from 'o1js'; import { randomAccounts } from './dex.js'; -import { TrivialCoin as TokenContract } from './erc20.js'; +import { TrivialCoin } from './erc20.js'; export { Dex, DexTokenHolder, addresses, getTokenBalances, keys, tokenIds }; class RedeemAction extends Struct({ address: PublicKey, dl: UInt64 }) {} -class Dex extends SmartContract { +class Dex extends TokenContract { // addresses of token contracts are constants tokenX = addresses.tokenX; tokenY = addresses.tokenY; + // Approvable API + + @method approveBase(forest: AccountUpdateForest) { + this.checkZeroBalanceChange(forest); + } + /** * state that keeps track of total lqXY supply -- this is needed to calculate what to return when redeeming liquidity * @@ -71,7 +79,7 @@ class Dex extends SmartContract { } @method createAccount() { - this.token.mint({ address: this.sender, amount: UInt64.from(0) }); + this.internal.mint({ address: this.sender, amount: UInt64.from(0) }); } /** @@ -86,14 +94,14 @@ class Dex extends SmartContract { */ @method supplyLiquidityBase(dx: UInt64, dy: UInt64): UInt64 { let user = this.sender; - let tokenX = new TokenContract(this.tokenX); - let tokenY = new TokenContract(this.tokenY); + let tokenX = new TrivialCoin(this.tokenX); + let tokenY = new TrivialCoin(this.tokenY); // get balances of X and Y token - let dexX = AccountUpdate.create(this.address, tokenX.token.id); + let dexX = AccountUpdate.create(this.address, tokenX.deriveTokenId()); let x = dexX.account.balance.getAndRequireEquals(); - let dexY = AccountUpdate.create(this.address, tokenY.token.id); + let dexY = AccountUpdate.create(this.address, tokenY.deriveTokenId()); let y = dexY.account.balance.getAndRequireEquals(); // // assert dy === [dx * y/x], or x === 0 @@ -108,7 +116,7 @@ class Dex extends SmartContract { // calculate liquidity token output simply as dl = dx + dx // => maintains ratio x/l, y/l let dl = dy.add(dx); - this.token.mint({ address: user, amount: dl }); + this.internal.mint({ address: user, amount: dl }); // update l supply let l = this.totalSupply.get(); @@ -157,7 +165,7 @@ class Dex extends SmartContract { */ @method redeemInitialize(dl: UInt64) { this.reducer.dispatch(new RedeemAction({ address: this.sender, dl })); - this.token.burn({ address: this.sender, amount: dl }); + this.internal.burn({ address: this.sender, amount: dl }); // TODO: preconditioning on the state here ruins concurrent interactions, // there should be another `finalize` DEX method which reduces actions & updates state this.totalSupply.set(this.totalSupply.getAndRequireEquals().sub(dl)); @@ -186,8 +194,8 @@ class Dex extends SmartContract { * the called methods which requires proof authorization. */ swapX(dx: UInt64): UInt64 { - let tokenY = new TokenContract(this.tokenY); - let dexY = new DexTokenHolder(this.address, tokenY.token.id); + let tokenY = new TrivialCoin(this.tokenY); + let dexY = new DexTokenHolder(this.address, tokenY.deriveTokenId()); let dy = dexY.swap(this.sender, dx, this.tokenX); tokenY.transfer(dexY.self, this.sender, dy); return dy; @@ -204,16 +212,12 @@ class Dex extends SmartContract { * the called methods which requires proof authorization. */ swapY(dy: UInt64): UInt64 { - let tokenX = new TokenContract(this.tokenX); - let dexX = new DexTokenHolder(this.address, tokenX.token.id); + let tokenX = new TrivialCoin(this.tokenX); + let dexX = new DexTokenHolder(this.address, tokenX.deriveTokenId()); let dx = dexX.swap(this.sender, dy, this.tokenY); tokenX.transfer(dexX.self, this.sender, dx); return dx; } - - @method transfer(from: PublicKey, to: PublicKey, amount: UInt64) { - this.token.send({ from, to, amount }); - } } class DexTokenHolder extends SmartContract { @@ -292,10 +296,10 @@ class DexTokenHolder extends SmartContract { ): UInt64 { // we're writing this as if our token === y and other token === x let dx = otherTokenAmount; - let tokenX = new TokenContract(otherTokenAddress); + let tokenX = new TrivialCoin(otherTokenAddress); // get balances of X and Y token - let dexX = AccountUpdate.create(this.address, tokenX.token.id); + let dexX = AccountUpdate.create(this.address, tokenX.deriveTokenId()); let x = dexX.account.balance.getAndRequireEquals(); let y = this.account.balance.getAndRequireEquals(); From 7b24cc09d06ac709525c361e85143c83d46840fa Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 19 Feb 2024 17:44:05 +0100 Subject: [PATCH 06/21] refactor dex to token contract --- .../zkapps/dex/arbitrary-token-interaction.ts | 2 +- src/examples/zkapps/dex/dex.ts | 46 ++++++++++++------- src/examples/zkapps/dex/upgradability.ts | 6 ++- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/examples/zkapps/dex/arbitrary-token-interaction.ts b/src/examples/zkapps/dex/arbitrary-token-interaction.ts index 182beb95fa..27843a1a23 100644 --- a/src/examples/zkapps/dex/arbitrary-token-interaction.ts +++ b/src/examples/zkapps/dex/arbitrary-token-interaction.ts @@ -42,7 +42,7 @@ tx = await Mina.transaction(userAddress, () => { ); // 😈😈😈 mint any number of tokens to our account 😈😈😈 let tokenContract = new TokenContract(addresses.tokenX); - tokenContract.token.mint({ + tokenContract.internal.mint({ address: userAddress, amount: UInt64.from(1e18), }); diff --git a/src/examples/zkapps/dex/dex.ts b/src/examples/zkapps/dex/dex.ts index b909030af1..eec19763d7 100644 --- a/src/examples/zkapps/dex/dex.ts +++ b/src/examples/zkapps/dex/dex.ts @@ -25,11 +25,18 @@ class UInt64x2 extends Struct([UInt64, UInt64]) {} function createDex({ lockedLiquiditySlots, }: { lockedLiquiditySlots?: number } = {}) { - class Dex extends SmartContract { + class Dex extends BaseTokenContract { // addresses of token contracts are constants tokenX = addresses.tokenX; tokenY = addresses.tokenY; + // Approvable API + + @method + approveBase(forest: AccountUpdateForest) { + this.checkZeroBalanceChange(forest); + } + /** * state which keeps track of total lqXY supply -- this is needed to calculate what to return when redeeming liquidity * @@ -53,10 +60,16 @@ function createDex({ let tokenY = new TokenContract(this.tokenY); // get balances of X and Y token - let dexXUpdate = AccountUpdate.create(this.address, tokenX.token.id); + let dexXUpdate = AccountUpdate.create( + this.address, + tokenX.deriveTokenId() + ); let dexXBalance = dexXUpdate.account.balance.getAndRequireEquals(); - let dexYUpdate = AccountUpdate.create(this.address, tokenY.token.id); + let dexYUpdate = AccountUpdate.create( + this.address, + tokenY.deriveTokenId() + ); let dexYBalance = dexYUpdate.account.balance.getAndRequireEquals(); // assert dy === [dx * y/x], or x === 0 @@ -71,7 +84,7 @@ function createDex({ // calculate liquidity token output simply as dl = dx + dy // => maintains ratio x/l, y/l let dl = dy.add(dx); - let userUpdate = this.token.mint({ address: user, amount: dl }); + let userUpdate = this.internal.mint({ address: user, amount: dl }); if (lockedLiquiditySlots !== undefined) { /** * exercise the "timing" (vesting) feature to lock the received liquidity tokens. @@ -114,7 +127,7 @@ function createDex({ // calculate dy outside circuit let x = Account(this.address, TokenId.derive(this.tokenX)).balance.get(); let y = Account(this.address, TokenId.derive(this.tokenY)).balance.get(); - if (x.value.isConstant() && x.value.isZero().toBoolean()) { + if (x.value.isConstant() && x.value.equals(0).toBoolean()) { throw Error( 'Cannot call `supplyLiquidity` when reserves are zero. Use `supplyLiquidityBase`.' ); @@ -136,7 +149,7 @@ function createDex({ redeemLiquidity(dl: UInt64) { // call the token X holder inside a token X-approved callback let tokenX = new TokenContract(this.tokenX); - let dexX = new DexTokenHolder(this.address, tokenX.token.id); + let dexX = new DexTokenHolder(this.address, tokenX.deriveTokenId()); let dxdy = dexX.redeemLiquidity(this.sender, dl, this.tokenY); let dx = dxdy[0]; tokenX.transfer(dexX.self, this.sender, dx); @@ -152,7 +165,7 @@ function createDex({ */ @method swapX(dx: UInt64): UInt64 { let tokenY = new TokenContract(this.tokenY); - let dexY = new DexTokenHolder(this.address, tokenY.token.id); + let dexY = new DexTokenHolder(this.address, tokenY.deriveTokenId()); let dy = dexY.swap(this.sender, dx, this.tokenX); tokenY.transfer(dexY.self, this.sender, dy); return dy; @@ -167,7 +180,7 @@ function createDex({ */ @method swapY(dy: UInt64): UInt64 { let tokenX = new TokenContract(this.tokenX); - let dexX = new DexTokenHolder(this.address, tokenX.token.id); + let dexX = new DexTokenHolder(this.address, tokenX.deriveTokenId()); let dx = dexX.swap(this.sender, dy, this.tokenY); tokenX.transfer(dexX.self, this.sender, dx); return dx; @@ -186,22 +199,21 @@ function createDex({ */ @method burnLiquidity(user: PublicKey, dl: UInt64): UInt64 { // this makes sure there is enough l to burn (user balance stays >= 0), so l stays >= 0, so l was >0 before - this.token.burn({ address: user, amount: dl }); + this.internal.burn({ address: user, amount: dl }); let l = this.totalSupply.get(); this.totalSupply.requireEquals(l); this.totalSupply.set(l.sub(dl)); return l; } - - @method transfer(from: PublicKey, to: PublicKey, amount: UInt64) { - this.token.send({ from, to, amount }); - } } class ModifiedDex extends Dex { @method swapX(dx: UInt64): UInt64 { let tokenY = new TokenContract(this.tokenY); - let dexY = new ModifiedDexTokenHolder(this.address, tokenY.token.id); + let dexY = new ModifiedDexTokenHolder( + this.address, + tokenY.deriveTokenId() + ); let dy = dexY.swap(this.sender, dx, this.tokenX); tokenY.transfer(dexY.self, this.sender, dy); return dy; @@ -241,7 +253,7 @@ function createDex({ ): UInt64x2 { // first call the Y token holder, approved by the Y token contract; this makes sure we get dl, the user's lqXY let tokenY = new TokenContract(otherTokenAddress); - let dexY = new DexTokenHolder(this.address, tokenY.token.id); + let dexY = new DexTokenHolder(this.address, tokenY.deriveTokenId()); let result = dexY.redeemLiquidityPartial(user, dl); let l = result[0]; let dy = result[1]; @@ -267,7 +279,7 @@ function createDex({ let dx = otherTokenAmount; let tokenX = new TokenContract(otherTokenAddress); // get balances - let dexX = AccountUpdate.create(this.address, tokenX.token.id); + let dexX = AccountUpdate.create(this.address, tokenX.deriveTokenId()); let x = dexX.account.balance.getAndRequireEquals(); let y = this.account.balance.getAndRequireEquals(); // send x from user to us (i.e., to the same address as this but with the other token) @@ -292,7 +304,7 @@ function createDex({ let dx = otherTokenAmount; let tokenX = new TokenContract(otherTokenAddress); // get balances - let dexX = AccountUpdate.create(this.address, tokenX.token.id); + let dexX = AccountUpdate.create(this.address, tokenX.deriveTokenId()); let x = dexX.account.balance.getAndRequireEquals(); let y = this.account.balance.get(); this.account.balance.requireEquals(y); diff --git a/src/examples/zkapps/dex/upgradability.ts b/src/examples/zkapps/dex/upgradability.ts index 6511e64aa3..0c9fba9b9b 100644 --- a/src/examples/zkapps/dex/upgradability.ts +++ b/src/examples/zkapps/dex/upgradability.ts @@ -378,11 +378,13 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { // Making sure that both token holder accounts have been updated with the new modified verification key expect( - Mina.getAccount(addresses.dex, tokenX.token.id).zkapp?.verificationKey?.data + Mina.getAccount(addresses.dex, tokenX.deriveTokenId()).zkapp + ?.verificationKey?.data ).toEqual(ModifiedDexTokenHolder._verificationKey?.data); expect( - Mina.getAccount(addresses.dex, tokenY.token.id).zkapp?.verificationKey?.data + Mina.getAccount(addresses.dex, tokenY.deriveTokenId()).zkapp + ?.verificationKey?.data ).toEqual(ModifiedDexTokenHolder._verificationKey?.data); // this is important; we have to re-enable proof production (and verification) to make sure the proofs are valid against the newly deployed VK From da7786e04ebc7fe854aeeea0e58f38d346c9b9c6 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 19 Feb 2024 17:44:12 +0100 Subject: [PATCH 07/21] tweak comment --- src/lib/zkapp.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index aff97c04c3..0f39725950 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -848,8 +848,9 @@ super.init(); } /** * @deprecated `SmartContract.token` will be removed, and token methods will only be available on `TokenContract.internal`. + * Instead of `SmartContract.token.id`, use `TokenContract.deriveTokenId()`. * - * For security reasons, it is recommended to use `TokenContract` as the base contract for tokens. + * For security reasons, it is recommended to use `TokenContract` as the base contract for all tokens. */ get token() { return deprecatedToken(this.self); From f64e6ccf13dc4b3e4b96265ac90c6efbb5480afa Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 19 Feb 2024 17:44:30 +0100 Subject: [PATCH 08/21] adapt example --- src/examples/zkapps/token-with-proofs.ts | 2 +- src/lib/token.test.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/examples/zkapps/token-with-proofs.ts b/src/examples/zkapps/token-with-proofs.ts index 4911b0e519..6ce61bc301 100644 --- a/src/examples/zkapps/token-with-proofs.ts +++ b/src/examples/zkapps/token-with-proofs.ts @@ -58,7 +58,7 @@ let zkAppBKey = PrivateKey.random(); let zkAppBAddress = zkAppBKey.toPublicKey(); let tokenZkApp = new Token(tokenZkAppAddress); -let tokenId = tokenZkApp.token.id; +let tokenId = tokenZkApp.deriveTokenId(); let zkAppB = new ZkAppB(zkAppBAddress, tokenId); let zkAppC = new ZkAppC(zkAppCAddress, tokenId); diff --git a/src/lib/token.test.ts b/src/lib/token.test.ts index e6c2ee1f5b..df138aefc1 100644 --- a/src/lib/token.test.ts +++ b/src/lib/token.test.ts @@ -18,6 +18,8 @@ import { const tokenSymbol = 'TOKEN'; +// TODO: Refactor to `TokenContract` + class TokenContract extends SmartContract { SUPPLY = UInt64.from(10n ** 18n); @state(UInt64) totalAmountInCirculation = State(); From cafbb52fc9faf20e289fed9706d6cfeb876f9ea9 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 19 Feb 2024 17:52:36 +0100 Subject: [PATCH 09/21] isNew precondition --- src/lib/mina/token/token-contract.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index 2e39e2e118..673524d1ef 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -29,10 +29,17 @@ abstract class TokenContract extends SmartContract { deploy(args?: DeployArgs) { super.deploy(args); + + // set access permission, to prevent unauthorized token operations this.account.permissions.set({ ...Permissions.default(), access: Permissions.proofOrSignature(), }); + + // assert that this account is new, to ensure unauthorized token operations + // are not possible before this contract is deployed + // see https://github.com/o1-labs/o1js/issues/1439 for details + this.account.isNew.requireEquals(Bool(true)); } /** From e5b28453796095ed9b2459c578e79ce8ee411966 Mon Sep 17 00:00:00 2001 From: Gregor Date: Mon, 19 Feb 2024 17:53:28 +0100 Subject: [PATCH 10/21] changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0cba5c1aa..6f34205241 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,16 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/3b5f7c7...HEAD) +### Deprecated + +- `SmartContract.token` is deprecated in favor of new methods on `TokenContract` https://github.com/o1-labs/o1js/pull/1446 + - `TokenContract.deriveTokenId()` to get the ID of the managed token + - `TokenContract.internal.{send, mint, burn}` to perform token operations from within the contract + +### Fixed + +- Mitigate security hazard of deploying token contracts https://github.com/o1-labs/o1js/issues/1439 + ## [0.16.1](https://github.com/o1-labs/o1js/compare/834a44002...3b5f7c7) ### Breaking changes From be9619101f02ef2e3488cb2616deca6af537fe59 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 20 Feb 2024 11:31:30 +0100 Subject: [PATCH 11/21] method to generate random keypair --- src/lib/signature.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/lib/signature.ts b/src/lib/signature.ts index 261aab8cc2..42cf340dda 100644 --- a/src/lib/signature.ts +++ b/src/lib/signature.ts @@ -32,9 +32,13 @@ class PrivateKey extends CircuitValue { } /** - * You can use this method to generate a private key. You can then obtain - * the associated public key via {@link toPublicKey}. And generate signatures - * via {@link Signature.create}. + * Generate a random private key. + * + * You can obtain the associated public key via {@link toPublicKey}. + * And generate signatures via {@link Signature.create}. + * + * Note: This uses node or browser built-in APIs to obtain cryptographically strong randomness, + * and can be safely used to generate a real private key. * * @returns a new {@link PrivateKey}. */ @@ -42,6 +46,17 @@ class PrivateKey extends CircuitValue { return new PrivateKey(Scalar.random()); } + /** + * Create a random keypair `{ privateKey: PrivateKey, publicKey: PublicKey }`. + * + * Note: This uses node or browser built-in APIs to obtain cryptographically strong randomness, + * and can be safely used to generate a real keypair. + */ + static randomKeypair() { + let privateKey = PrivateKey.random(); + return { privateKey, publicKey: privateKey.toPublicKey() }; + } + /** * Deserializes a list of bits into a {@link PrivateKey}. * From 1b89d8160797850839343d54ba9c5c4aac0b06aa Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 20 Feb 2024 11:32:11 +0100 Subject: [PATCH 12/21] fix token contract test --- src/lib/mina/token/token-contract.unit-test.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/lib/mina/token/token-contract.unit-test.ts b/src/lib/mina/token/token-contract.unit-test.ts index a62ebf160c..3301f9380a 100644 --- a/src/lib/mina/token/token-contract.unit-test.ts +++ b/src/lib/mina/token/token-contract.unit-test.ts @@ -7,6 +7,7 @@ import { AccountUpdateForest, TokenContract, Int64, + PrivateKey, } from '../../../index.js'; class ExampleTokenContract extends TokenContract { @@ -26,9 +27,6 @@ class ExampleTokenContract extends TokenContract { // mint the entire supply to the token account with the same address as this contract this.internal.mint({ address: this.address, amount: this.SUPPLY }); - - // pay fees for opened account - this.balance.subInPlace(Mina.getNetworkConstants().accountCreationFee); } } @@ -39,15 +37,19 @@ Mina.setActiveInstance(Local); let [ { publicKey: sender, privateKey: senderKey }, - { publicKey: tokenAddress, privateKey: tokenKey }, { publicKey: otherAddress, privateKey: otherKey }, ] = Local.testAccounts; +let { publicKey: tokenAddress, privateKey: tokenKey } = + PrivateKey.randomKeypair(); let token = new ExampleTokenContract(tokenAddress); -let tokenId = token.token.id; +let tokenId = token.deriveTokenId(); // deploy token contract -let deployTx = await Mina.transaction(sender, () => token.deploy()); +let deployTx = await Mina.transaction(sender, () => { + AccountUpdate.fundNewAccount(sender, 2); + token.deploy(); +}); await deployTx.prove(); await deployTx.sign([tokenKey, senderKey]).send(); From 358aa27c2cf4233c2a3a8779af5ac5ecc7c9160d Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 20 Feb 2024 11:41:26 +0100 Subject: [PATCH 13/21] fix dex deploy scripts to respect isNew precondition --- src/examples/zkapps/dex/happy-path-with-actions.ts | 12 ++++++------ src/examples/zkapps/dex/happy-path-with-proofs.ts | 12 ++++++------ src/examples/zkapps/dex/run-live.ts | 12 ++++++------ src/examples/zkapps/dex/run.ts | 11 ++++++----- src/examples/zkapps/dex/upgradability.ts | 10 +++++----- 5 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/examples/zkapps/dex/happy-path-with-actions.ts b/src/examples/zkapps/dex/happy-path-with-actions.ts index 47ca1078bf..78a07961eb 100644 --- a/src/examples/zkapps/dex/happy-path-with-actions.ts +++ b/src/examples/zkapps/dex/happy-path-with-actions.ts @@ -43,14 +43,14 @@ let dexTokenHolderY = new DexTokenHolder(addresses.dex, tokenIds.Y); tic('deploy & init token contracts'); tx = await Mina.transaction(feePayerAddress, () => { - const accountFee = Mina.getNetworkConstants().accountCreationFee; - // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves - let feePayerUpdate = AccountUpdate.createSigned(feePayerAddress); - feePayerUpdate.balance.subInPlace(accountFee.mul(2)); - feePayerUpdate.send({ to: addresses.tokenX, amount: accountFee }); - feePayerUpdate.send({ to: addresses.tokenY, amount: accountFee }); tokenX.deploy(); tokenY.deploy(); + + // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves + const accountFee = Mina.getNetworkConstants().accountCreationFee; + let feePayerUpdate = AccountUpdate.fundNewAccount(feePayerAddress, 2); + feePayerUpdate.send({ to: tokenX.self, amount: accountFee }); + feePayerUpdate.send({ to: tokenY.self, amount: accountFee }); }); await tx.prove(); await tx.sign([feePayerKey, keys.tokenX, keys.tokenY]).send(); diff --git a/src/examples/zkapps/dex/happy-path-with-proofs.ts b/src/examples/zkapps/dex/happy-path-with-proofs.ts index 6f44280549..2b6fae11b5 100644 --- a/src/examples/zkapps/dex/happy-path-with-proofs.ts +++ b/src/examples/zkapps/dex/happy-path-with-proofs.ts @@ -45,14 +45,14 @@ let dexTokenHolderY = new DexTokenHolder(addresses.dex, tokenIds.Y); tic('deploy & init token contracts'); tx = await Mina.transaction(feePayerAddress, () => { - const accountFee = Mina.getNetworkConstants().accountCreationFee; - // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves - let feePayerUpdate = AccountUpdate.createSigned(feePayerAddress); - feePayerUpdate.balance.subInPlace(accountFee.mul(2)); - feePayerUpdate.send({ to: addresses.tokenX, amount: accountFee }); - feePayerUpdate.send({ to: addresses.tokenY, amount: accountFee }); tokenX.deploy(); tokenY.deploy(); + + // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves + const accountFee = Mina.getNetworkConstants().accountCreationFee; + let feePayerUpdate = AccountUpdate.fundNewAccount(feePayerAddress, 2); + feePayerUpdate.send({ to: tokenX.self, amount: accountFee }); + feePayerUpdate.send({ to: tokenY.self, amount: accountFee }); }); await tx.prove(); await tx.sign([feePayerKey, keys.tokenX, keys.tokenY]).send(); diff --git a/src/examples/zkapps/dex/run-live.ts b/src/examples/zkapps/dex/run-live.ts index 22c013966f..6db2631acc 100644 --- a/src/examples/zkapps/dex/run-live.ts +++ b/src/examples/zkapps/dex/run-live.ts @@ -73,14 +73,14 @@ let userSpec = { sender: addresses.user, fee: 0.1e9 }; if (successfulTransactions <= 0) { tic('deploy & init token contracts'); tx = await Mina.transaction(senderSpec, () => { - const accountFee = Mina.getNetworkConstants().accountCreationFee; - // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves - let feePayerUpdate = AccountUpdate.createSigned(sender); - feePayerUpdate.balance.subInPlace(accountFee.mul(2)); - feePayerUpdate.send({ to: addresses.tokenX, amount: accountFee }); - feePayerUpdate.send({ to: addresses.tokenY, amount: accountFee }); tokenX.deploy(); tokenY.deploy(); + + // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves + const accountFee = Mina.getNetworkConstants().accountCreationFee; + let feePayerUpdate = AccountUpdate.fundNewAccount(sender, 2); + feePayerUpdate.send({ to: tokenX.self, amount: accountFee }); + feePayerUpdate.send({ to: tokenY.self, amount: accountFee }); }); await tx.prove(); pendingTx = await tx.sign([senderKey, keys.tokenX, keys.tokenY]).send(); diff --git a/src/examples/zkapps/dex/run.ts b/src/examples/zkapps/dex/run.ts index ca831647e3..0244ae541e 100644 --- a/src/examples/zkapps/dex/run.ts +++ b/src/examples/zkapps/dex/run.ts @@ -76,13 +76,14 @@ async function main({ withVesting }: { withVesting: boolean }) { console.log('deploy & init token contracts...'); tx = await Mina.transaction(feePayerAddress, () => { - const accountFee = Mina.getNetworkConstants().accountCreationFee; - // pay fees for creating 2 token contract accounts, and fund them so each can create 2 accounts themselves - let feePayerUpdate = AccountUpdate.fundNewAccount(feePayerAddress, 2); - feePayerUpdate.send({ to: addresses.tokenX, amount: accountFee.mul(2) }); - feePayerUpdate.send({ to: addresses.tokenY, amount: accountFee.mul(2) }); tokenX.deploy(); tokenY.deploy(); + + // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves + const accountFee = Mina.getNetworkConstants().accountCreationFee; + let feePayerUpdate = AccountUpdate.fundNewAccount(feePayerAddress, 2); + feePayerUpdate.send({ to: tokenX.self, amount: accountFee.mul(2) }); + feePayerUpdate.send({ to: tokenY.self, amount: accountFee.mul(2) }); }); await tx.prove(); tx.sign([feePayerKey, keys.tokenX, keys.tokenY]); diff --git a/src/examples/zkapps/dex/upgradability.ts b/src/examples/zkapps/dex/upgradability.ts index 0c9fba9b9b..4f8cf4a7ec 100644 --- a/src/examples/zkapps/dex/upgradability.ts +++ b/src/examples/zkapps/dex/upgradability.ts @@ -267,14 +267,14 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { console.log('deploy & init token contracts...'); tx = await Mina.transaction(feePayerAddress, () => { - const accountFee = Mina.getNetworkConstants().accountCreationFee; + tokenX.deploy(); + tokenY.deploy(); + // pay fees for creating 2 token contract accounts, and fund them so each can create 2 accounts themselves - let feePayerUpdate = AccountUpdate.createSigned(feePayerAddress); - feePayerUpdate.balance.subInPlace(accountFee.mul(2)); + const accountFee = Mina.getNetworkConstants().accountCreationFee; + let feePayerUpdate = AccountUpdate.fundNewAccount(feePayerAddress, 2); feePayerUpdate.send({ to: addresses.tokenX, amount: accountFee.mul(2) }); feePayerUpdate.send({ to: addresses.tokenY, amount: accountFee.mul(2) }); - tokenX.deploy(); - tokenY.deploy(); }); await tx.prove(); tx.sign([feePayerKey, keys.tokenX, keys.tokenY]); From 8e9a6161755ee9dfd5fd13e874646ff5210cefd8 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 20 Feb 2024 11:42:49 +0100 Subject: [PATCH 14/21] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f34205241..255351b785 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/3b5f7c7...HEAD) +### Added + +- `PrivateKey.randomKeypair()` to generate private and public key in one command https://github.com/o1-labs/o1js/pull/1446 + ### Deprecated - `SmartContract.token` is deprecated in favor of new methods on `TokenContract` https://github.com/o1-labs/o1js/pull/1446 From 71087acda48d7fe962f4a6e09a5ce6af93dfd13c Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 20 Feb 2024 11:43:11 +0100 Subject: [PATCH 15/21] dump vks --- tests/vk-regression/vk-regression.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/vk-regression/vk-regression.json b/tests/vk-regression/vk-regression.json index 6e8a9a738d..2c0384dd01 100644 --- a/tests/vk-regression/vk-regression.json +++ b/tests/vk-regression/vk-regression.json @@ -84,8 +84,12 @@ } }, "Dex": { - "digest": "1adce234400fe0b0ea8b1e625256538660d9ed6e15767e2ac78bb7a59d83a3af", + "digest": "36685f69aa608d71cbd88eddf77cc2271d0ad781182e7f8ce7ceab746cde896b", "methods": { + "approveBase": { + "rows": 13244, + "digest": "4dd81f1cc1a06b617677e08883e50fbd" + }, "supplyLiquidityBase": { "rows": 2882, "digest": "9930f9a0b82eff6247cded6430c6356f" @@ -101,15 +105,11 @@ "burnLiquidity": { "rows": 718, "digest": "6c406099fe2d2493bd216f9bbe3ba934" - }, - "transfer": { - "rows": 1044, - "digest": "1df1d01485d388ee364156f940214d23" } }, "verificationKey": { - "data": "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", - "hash": "23594323045578615602880853374590447788338441806100547122393736875331781522763" + "data": "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", + "hash": "27190148268093452485039136201330807645985391900537471731621996478489554499244" } }, "Group Primitive": { From a6a06e44c99ee2bd26c3f23000e7f0de72f64858 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 20 Feb 2024 11:45:54 +0100 Subject: [PATCH 16/21] remove obsolete example --- .../zkapps/dex/arbitrary-token-interaction.ts | 57 ------------------- 1 file changed, 57 deletions(-) delete mode 100644 src/examples/zkapps/dex/arbitrary-token-interaction.ts diff --git a/src/examples/zkapps/dex/arbitrary-token-interaction.ts b/src/examples/zkapps/dex/arbitrary-token-interaction.ts deleted file mode 100644 index 27843a1a23..0000000000 --- a/src/examples/zkapps/dex/arbitrary-token-interaction.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { AccountUpdate, Mina, TokenId, UInt64 } from 'o1js'; -import { TokenContract, addresses, keys, tokenIds } from './dex.js'; - -let doProofs = true; -let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); -Mina.setActiveInstance(Local); - -let [{ privateKey: userKey, publicKey: userAddress }] = Local.testAccounts; -let tx; - -console.log('-------------------------------------------------'); -console.log('TOKEN X ADDRESS\t', addresses.tokenX.toBase58()); -console.log('USER ADDRESS\t', userAddress.toBase58()); -console.log('-------------------------------------------------'); -console.log('TOKEN X ID\t', TokenId.toBase58(tokenIds.X)); -console.log('-------------------------------------------------'); - -// compile & deploy all 5 zkApps -console.log('compile (token)...'); -await TokenContract.compile(); - -let tokenX = new TokenContract(addresses.tokenX); - -console.log('deploy & init token contracts...'); -tx = await Mina.transaction(userAddress, () => { - // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves - let feePayerUpdate = AccountUpdate.createSigned(userAddress); - feePayerUpdate.balance.subInPlace( - Mina.getNetworkConstants().accountCreationFee.mul(1) - ); - tokenX.deploy(); -}); -await tx.prove(); -tx.sign([keys.tokenX]); -await tx.send(); - -console.log('arbitrary token minting...'); -tx = await Mina.transaction(userAddress, () => { - // pay fees for creating user's token X account - AccountUpdate.createSigned(userAddress).balance.subInPlace( - Mina.getNetworkConstants().accountCreationFee.mul(1) - ); - // 😈😈😈 mint any number of tokens to our account 😈😈😈 - let tokenContract = new TokenContract(addresses.tokenX); - tokenContract.internal.mint({ - address: userAddress, - amount: UInt64.from(1e18), - }); -}); -await tx.prove(); -console.log(tx.toPretty()); -await tx.send(); - -console.log( - 'User tokens: ', - Mina.getBalance(userAddress, tokenIds.X).value.toBigInt() -); From 40ebf95c8d5b3f2a4631564178b056b2763cc2b5 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 20 Feb 2024 12:09:51 +0100 Subject: [PATCH 17/21] fixup upgradability test --- src/examples/zkapps/dex/dex.ts | 6 ++++++ src/examples/zkapps/dex/upgradability.ts | 4 ++-- src/lib/precondition.ts | 7 +++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/examples/zkapps/dex/dex.ts b/src/examples/zkapps/dex/dex.ts index eec19763d7..84dec73c87 100644 --- a/src/examples/zkapps/dex/dex.ts +++ b/src/examples/zkapps/dex/dex.ts @@ -208,6 +208,12 @@ function createDex({ } class ModifiedDex extends Dex { + deploy() { + super.deploy(); + // override the isNew requirement for re-deploying + this.account.isNew.requireNothing(); + } + @method swapX(dx: UInt64): UInt64 { let tokenY = new TokenContract(this.tokenY); let dexY = new ModifiedDexTokenHolder( diff --git a/src/examples/zkapps/dex/upgradability.ts b/src/examples/zkapps/dex/upgradability.ts index 4f8cf4a7ec..8358b45364 100644 --- a/src/examples/zkapps/dex/upgradability.ts +++ b/src/examples/zkapps/dex/upgradability.ts @@ -273,8 +273,8 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { // pay fees for creating 2 token contract accounts, and fund them so each can create 2 accounts themselves const accountFee = Mina.getNetworkConstants().accountCreationFee; let feePayerUpdate = AccountUpdate.fundNewAccount(feePayerAddress, 2); - feePayerUpdate.send({ to: addresses.tokenX, amount: accountFee.mul(2) }); - feePayerUpdate.send({ to: addresses.tokenY, amount: accountFee.mul(2) }); + feePayerUpdate.send({ to: tokenX.self, amount: accountFee.mul(2) }); + feePayerUpdate.send({ to: tokenY.self, amount: accountFee.mul(2) }); }); await tx.prove(); tx.sign([feePayerKey, keys.tokenX, keys.tokenY]); diff --git a/src/lib/precondition.ts b/src/lib/precondition.ts index ff54c2c835..8800d5b344 100644 --- a/src/lib/precondition.ts +++ b/src/lib/precondition.ts @@ -426,6 +426,13 @@ function preconditionSubclass< this.requireEquals(value); }, requireNothing() { + let property = getPath( + accountUpdate.body.preconditions, + longKey + ) as AnyCondition; + if ('isSome' in property) { + property.isSome = Bool(false); + } context.constrained.add(longKey); }, assertNothing() { From 6be807ba93965647f5fd8f19117466107dd842a2 Mon Sep 17 00:00:00 2001 From: Gregor Date: Tue, 20 Feb 2024 12:14:46 +0100 Subject: [PATCH 18/21] another fix --- src/examples/zkapps/dex/upgradability.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/examples/zkapps/dex/upgradability.ts b/src/examples/zkapps/dex/upgradability.ts index 8358b45364..366ca2c467 100644 --- a/src/examples/zkapps/dex/upgradability.ts +++ b/src/examples/zkapps/dex/upgradability.ts @@ -57,13 +57,14 @@ async function atomicActionsTest({ withVesting }: { withVesting: boolean }) { console.log('deploy & init token contracts...'); tx = await Mina.transaction(feePayerAddress, () => { - const accountFee = Mina.getNetworkConstants().accountCreationFee; - // pay fees for creating 2 token contract accounts, and fund them so each can create 2 accounts themselves - let feePayerUpdate = AccountUpdate.fundNewAccount(feePayerAddress, 2); - feePayerUpdate.send({ to: addresses.tokenX, amount: accountFee.mul(2) }); - feePayerUpdate.send({ to: addresses.tokenY, amount: accountFee.mul(2) }); tokenX.deploy(); tokenY.deploy(); + + // pay fees for creating 2 token contract accounts, and fund them so each can create 2 accounts themselves + const accountFee = Mina.getNetworkConstants().accountCreationFee; + let feePayerUpdate = AccountUpdate.fundNewAccount(feePayerAddress, 2); + feePayerUpdate.send({ to: tokenX.self, amount: accountFee.mul(2) }); + feePayerUpdate.send({ to: tokenY.self, amount: accountFee.mul(2) }); }); await tx.prove(); tx.sign([feePayerKey, keys.tokenX, keys.tokenY]); From 88d9a70f66ebe2c92c3770cfc3317b92a202b6b8 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 21 Feb 2024 10:58:58 +0100 Subject: [PATCH 19/21] document token contract deploy --- src/lib/mina/token/token-contract.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/lib/mina/token/token-contract.ts b/src/lib/mina/token/token-contract.ts index 673524d1ef..793afcbfb9 100644 --- a/src/lib/mina/token/token-contract.ts +++ b/src/lib/mina/token/token-contract.ts @@ -27,6 +27,27 @@ const MAX_ACCOUNT_UPDATES = 20; abstract class TokenContract extends SmartContract { // change default permissions - important that token contracts use an access permission + /** + * Deploys a {@link TokenContract}. + * + * In addition to base smart contract deployment, this adds two steps: + * - set the `access` permission to `proofOrSignature()`, to prevent against unauthorized token operations + * - not doing this would imply that anyone can bypass token contract authorization and simply mint themselves tokens + * - require the zkapp account to be new, using the `isNew` precondition. + * this guarantees that the access permission is set from the very start of the existence of this account. + * creating the zkapp account before deployment would otherwise be a security vulnerability that is too easy to introduce. + * + * Note that because of the `isNew` precondition, the zkapp account must not be created prior to calling `deploy()`. + * + * If the contract needs to be re-deployed, you can switch off this behaviour by overriding the `isNew` precondition: + * ```ts + * deploy() { + * super.deploy(); + * // DON'T DO THIS ON THE INITIAL DEPLOYMENT! + * this.account.isNew.requireNothing(); + * } + * ``` + */ deploy(args?: DeployArgs) { super.deploy(args); From d8079fde52da47bfcc2b6ba1865ec39c231f7a79 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 21 Feb 2024 11:00:02 +0100 Subject: [PATCH 20/21] minor --- src/examples/simple-zkapp.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/examples/simple-zkapp.ts b/src/examples/simple-zkapp.ts index bd17d6eae4..8d6e855eb9 100644 --- a/src/examples/simple-zkapp.ts +++ b/src/examples/simple-zkapp.ts @@ -99,10 +99,10 @@ console.log('deploy'); let tx = await Mina.transaction(sender, () => { let senderUpdate = AccountUpdate.fundNewAccount(sender); senderUpdate.send({ to: zkappAddress, amount: initialBalance }); - zkapp.deploy({ zkappKey }); + zkapp.deploy(); }); await tx.prove(); -await tx.sign([senderKey]).send(); +await tx.sign([senderKey, zkappKey]).send(); console.log('initial state: ' + zkapp.x.get()); console.log(`initial balance: ${zkapp.account.balance.get().div(1e9)} MINA`); From 01614a5f85fc8045fe22c9563e4ba02fc42909a3 Mon Sep 17 00:00:00 2001 From: Gregor Date: Wed, 21 Feb 2024 11:01:50 +0100 Subject: [PATCH 21/21] link in comment --- src/lib/zkapp.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib/zkapp.ts b/src/lib/zkapp.ts index 0f39725950..de5386fa21 100644 --- a/src/lib/zkapp.ts +++ b/src/lib/zkapp.ts @@ -65,6 +65,7 @@ import { smartContractContext, } from './mina/smart-contract-context.js'; import { deprecatedToken } from './mina/token/token-methods.js'; +import type { TokenContract } from './mina/token/token-contract.js'; // external API export { SmartContract, method, DeployArgs, declareMethods, Account, Reducer }; @@ -847,10 +848,11 @@ super.init(); return this.self.currentSlot; } /** - * @deprecated `SmartContract.token` will be removed, and token methods will only be available on `TokenContract.internal`. + * @deprecated + * `SmartContract.token` will be removed, and token methods will only be available on `TokenContract.internal`. * Instead of `SmartContract.token.id`, use `TokenContract.deriveTokenId()`. * - * For security reasons, it is recommended to use `TokenContract` as the base contract for all tokens. + * For security reasons, it is recommended to use {@link TokenContract} as the base contract for all tokens. */ get token() { return deprecatedToken(this.self);