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

Replace this.token with methods on TokenContract #1446

Merged
merged 22 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
### Added

- Support for custom network identifiers other than `mainnet` or `testnet` https://github.com/o1-labs/o1js/pull/1444
- `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
- `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)

Expand Down
4 changes: 2 additions & 2 deletions src/examples/simple-zkapp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`);
Expand Down
57 changes: 0 additions & 57 deletions src/examples/zkapps/dex/arbitrary-token-interaction.ts

This file was deleted.

42 changes: 23 additions & 19 deletions src/examples/zkapps/dex/dex-with-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import {
Account,
AccountUpdate,
AccountUpdateForest,
Field,
InferProvable,
Mina,
Expand All @@ -16,24 +17,31 @@ import {
SmartContract,
State,
Struct,
TokenContract,
TokenId,
UInt64,
method,
state,
} 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
*
Expand Down Expand Up @@ -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) });
Copy link
Contributor

Choose a reason for hiding this comment

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

this.internal is a great way to communicate usage within smart contract methods, awesome!

}

/**
Expand All @@ -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
Expand All @@ -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();
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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;
Expand All @@ -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 {
Expand Down Expand Up @@ -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();

Expand Down
56 changes: 37 additions & 19 deletions src/examples/zkapps/dex/dex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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`.'
);
Expand All @@ -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);
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -186,22 +199,27 @@ 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 {
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(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;
Expand Down Expand Up @@ -241,7 +259,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];
Expand All @@ -267,7 +285,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)
Expand All @@ -292,7 +310,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);
Expand Down Expand Up @@ -377,7 +395,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(),
});
Expand All @@ -393,7 +411,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),
});
Expand Down
Loading
Loading