Skip to content

Commit

Permalink
Allow pausing transfers. (#57)
Browse files Browse the repository at this point in the history
  • Loading branch information
kantp authored May 8, 2024
1 parent 1fdd3a6 commit 971bf58
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 11 deletions.
91 changes: 89 additions & 2 deletions FungibleToken.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
State,
state,
UInt64,
UInt8,
} from "o1js"
import { TestPublicKey } from "o1js/dist/node/lib/mina/local-blockchain.js"
import {
Expand Down Expand Up @@ -94,6 +95,7 @@ describe("token integration", () => {
admin: tokenAdmin,
symbol: "tokA",
src: "",
decimals: UInt8.from(9),
})
})

Expand All @@ -120,6 +122,7 @@ describe("token integration", () => {
admin: tokenBAdmin,
symbol: "tokB",
src: "",
decimals: UInt8.from(9),
})
})

Expand Down Expand Up @@ -407,8 +410,8 @@ describe("token integration", () => {
tokenBContract.deriveTokenId(),
)
updateReceive.balanceChange = Int64.fromUnsigned(sendAmount)
await rejects(() => (
Mina.transaction({
await rejects(async () => (
await Mina.transaction({
sender: deployer,
fee: 1e8,
}, async () => {
Expand All @@ -420,6 +423,78 @@ describe("token integration", () => {
})
})

describe("pausing/resuming", () => {
const sendAmount = UInt64.from(1)

it("can be paused by the admin", async () => {
const tx = await Mina.transaction({
sender: sender,
fee: 1e8,
}, async () => {
await tokenAContract.pause()
})
tx.sign([sender.key, newTokenAdmin.key])
await tx.prove()
await tx.send()
})
it("will block transactions while paused", async () => {
await rejects(() => (
Mina.transaction({
sender: sender,
fee: 1e8,
}, async () => {
await tokenAContract.transfer(sender, receiver, sendAmount)
})
))
})
it("can be resumed by the admin", async () => {
const tx = await Mina.transaction({
sender: sender,
fee: 1e8,
}, async () => {
await tokenAContract.resume()
})
tx.sign([sender.key, newTokenAdmin.key])
await tx.prove()
await tx.send()
})
it("will accept transactions after resume", async () => {
const initialBalanceSender = (await tokenAContract.getBalanceOf(sender))
.toBigInt()
const initialBalanceReceiver = (await tokenAContract.getBalanceOf(receiver))
.toBigInt()
const initialCirculating = (await tokenAContract.getCirculating()).toBigInt()

const tx = await Mina.transaction({
sender: sender,
fee: 1e8,
}, async () => {
await tokenAContract.transfer(
sender,
receiver,
sendAmount,
)
})

tx.sign([sender.key])
await tx.prove()
await tx.send()

equal(
(await tokenAContract.getBalanceOf(sender)).toBigInt(),
initialBalanceSender - sendAmount.toBigInt(),
)
equal(
(await tokenAContract.getBalanceOf(receiver)).toBigInt(),
initialBalanceReceiver + sendAmount.toBigInt(),
)
equal(
(await tokenAContract.getCirculating()).toBigInt(),
initialCirculating,
)
})
})

describe("third party", () => {
const depositAmount = UInt64.from(100)

Expand Down Expand Up @@ -650,6 +725,18 @@ class CustomTokenAdmin extends SmartContract implements FungibleTokenAdminBase {
this.ensureAdminSignature()
return Bool(true)
}

@method.returns(Bool)
public async canPause(): Promise<Bool> {
this.ensureAdminSignature()
return Bool(true)
}

@method.returns(Bool)
public async canResume(): Promise<Bool> {
this.ensureAdminSignature()
return Bool(true)
}
}

export default class ThirdParty extends SmartContract {
Expand Down
35 changes: 30 additions & 5 deletions FungibleToken.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
AccountUpdate,
AccountUpdateForest,
Bool,
DeployArgs,
Field,
Int64,
Expand All @@ -13,6 +14,7 @@ import {
Struct,
TokenContract,
UInt64,
UInt8,
} from "o1js"
import { FungibleTokenAdmin, FungibleTokenAdminBase } from "./FungibleTokenAdmin.js"
import type { FungibleTokenLike } from "./FungibleTokenLike.js"
Expand All @@ -24,17 +26,21 @@ export interface FungibleTokenDeployProps extends Exclude<DeployArgs, undefined>
symbol: string
/** A source code reference, which is placed within the `zkappUri` of the contract account. */
src: string
/** Number of decimals in a unit */
decimals: UInt8
}

export class FungibleToken extends TokenContract implements FungibleTokenLike {
decimals = UInt64.from(9)

@state(UInt8)
decimals = State<UInt8>() // UInt64.from(9)
@state(PublicKey)
admin = State<PublicKey>()
@state(UInt64)
private circulating = State<UInt64>()
@state(Field)
actionState = State<Field>()
@state(Bool)
paused = State<Bool>()

// This defines the type of the contract that is used to control access to administrative actions.
// If you want to have a custom contract, overwrite this by setting FungibleToken.adminContract to
Expand All @@ -57,6 +63,8 @@ export class FungibleToken extends TokenContract implements FungibleTokenLike {

this.admin.set(props.admin)
this.circulating.set(UInt64.from(0))
this.decimals.set(props.decimals)
this.paused.set(Bool(false))

this.account.tokenSymbol.set(props.symbol)
this.account.zkappUri.set(props.src)
Expand All @@ -78,6 +86,7 @@ export class FungibleToken extends TokenContract implements FungibleTokenLike {

@method.returns(AccountUpdate)
async mint(recipient: PublicKey, amount: UInt64) {
this.paused.getAndRequireEquals().assertFalse()
const accountUpdate = this.internal.mint({ address: recipient, amount })
const canMint = await this.getAdminContract()
.canMint(accountUpdate)
Expand All @@ -90,22 +99,38 @@ export class FungibleToken extends TokenContract implements FungibleTokenLike {

@method.returns(AccountUpdate)
async burn(from: PublicKey, amount: UInt64) {
this.paused.getAndRequireEquals().assertFalse()
const accountUpdate = this.internal.burn({ address: from, amount })
this.emitEvent("Burn", new BurnEvent({ from, amount }))
this.reducer.dispatch(Int64.fromUnsigned(amount).neg())
return accountUpdate
}

@method
async pause() {
const canPause = await this.getAdminContract().canPause()
canPause.assertTrue()
this.paused.set(Bool(true))
}

@method
async resume() {
const canResume = await this.getAdminContract().canResume()
canResume.assertTrue()
this.paused.set(Bool(false))
}

@method
async transfer(from: PublicKey, to: PublicKey, amount: UInt64) {
this.paused.getAndRequireEquals().assertFalse()
this.internal.send({ from, to, amount })
this.emitEvent("Transfer", new TransferEvent({ from, to, amount }))
}

@method
async approveBase(updates: AccountUpdateForest): Promise<void> {
this.paused.getAndRequireEquals().assertFalse()
this.checkZeroBalanceChange(updates)
// TODO: event emission here
}

@method.returns(UInt64)
Expand Down Expand Up @@ -160,9 +185,9 @@ export class FungibleToken extends TokenContract implements FungibleTokenLike {
this.actionState.set(pendingActions.hash)
}

@method.returns(UInt64)
@method.returns(UInt8)
async getDecimals() {
return this.decimals
return this.decimals.getAndRequireEquals()
}
}

Expand Down
14 changes: 14 additions & 0 deletions FungibleTokenAdmin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
export type FungibleTokenAdminBase = SmartContract & {
canMint(accountUpdate: AccountUpdate): Promise<Bool>
canChangeAdmin(admin: PublicKey): Promise<Bool>
canPause(): Promise<Bool>
canResume(): Promise<Bool>
}

export interface FungibleTokenAdminDeployProps extends Exclude<DeployArgs, undefined> {
Expand Down Expand Up @@ -52,4 +54,16 @@ export class FungibleTokenAdmin extends SmartContract implements FungibleTokenAd
this.ensureAdminSignature()
return Bool(true)
}

@method.returns(Bool)
public async canPause(): Promise<Bool> {
this.ensureAdminSignature()
return Bool(true)
}

@method.returns(Bool)
public async canResume(): Promise<Bool> {
this.ensureAdminSignature()
return Bool(true)
}
}
11 changes: 9 additions & 2 deletions FungibleTokenLike.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import type { AccountUpdate, AccountUpdateForest, AccountUpdateTree, PublicKey, UInt64 } from "o1js"
import type {
AccountUpdate,
AccountUpdateForest,
AccountUpdateTree,
PublicKey,
UInt64,
UInt8,
} from "o1js"

/** A collection of methods of methods which make an object _like_ a Mina fungible token. */
export interface FungibleTokenLike {
Expand All @@ -7,7 +14,7 @@ export interface FungibleTokenLike {
/** Get the amount circulating of the current token. */
getCirculating(): Promise<UInt64>
/** Get the number of decimals used in representing the current token. */
getDecimals(): Promise<UInt64>
getDecimals(): Promise<UInt8>
/**
* Move a specified amount of tokens between two accounts.
* @param from the public key of the account from which the tokens should be sent.
Expand Down
3 changes: 2 additions & 1 deletion examples/concurrent-transfer.eg.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AccountUpdate, Mina, PrivateKey, PublicKey, TokenId, UInt64 } from "o1js"
import { AccountUpdate, Mina, PrivateKey, PublicKey, TokenId, UInt64, UInt8 } from "o1js"
import { FungibleToken, FungibleTokenAdmin } from "../index.js"

const url = "https://proxy.devnet.minaexplorer.com/graphql"
Expand Down Expand Up @@ -97,6 +97,7 @@ const deployTx = await Mina.transaction({
admin: admin.publicKey,
symbol: "abc",
src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/examples/e2e.eg.ts",
decimals: UInt8.from(9),
})
})
await deployTx.prove()
Expand Down
3 changes: 2 additions & 1 deletion examples/e2e.eg.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { equal } from "node:assert"
import { AccountUpdate, Mina, PrivateKey, UInt64 } from "o1js"
import { AccountUpdate, Mina, PrivateKey, UInt64, UInt8 } from "o1js"
import { FungibleToken, FungibleTokenAdmin } from "../index.js"
import { TestPublicKeys } from "../test_util.js"

Expand Down Expand Up @@ -29,6 +29,7 @@ const deployTx = await Mina.transaction({
admin: admin.publicKey,
symbol: "abc",
src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/examples/e2e.eg.ts",
decimals: UInt8.from(9),
})
})
await deployTx.prove()
Expand Down
2 changes: 2 additions & 0 deletions examples/escrow.eg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
State,
state,
UInt64,
UInt8,
} from "o1js"
import { TestPublicKeys } from "test_util.js"
import { FungibleToken, FungibleTokenAdmin } from "../index.js"
Expand Down Expand Up @@ -83,6 +84,7 @@ const deployTokenTx = await Mina.transaction({
admin: admin.publicKey,
symbol: "abc",
src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/examples/escrow.eg.ts",
decimals: UInt8.from(9),
})
})
await deployTokenTx.prove()
Expand Down

0 comments on commit 971bf58

Please sign in to comment.