Skip to content

Commit

Permalink
Initialize admin address, decimals, and paused state in a method.
Browse files Browse the repository at this point in the history
Otherwise, the values are not checked in a proof and might be invalid.
  • Loading branch information
kantp committed Jul 17, 2024
1 parent 8a40f6f commit 4c38e83
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 84 deletions.
21 changes: 12 additions & 9 deletions FungibleToken.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,14 @@ describe("token integration", async () => {
adminPublicKey: tokenAdmin,
})
await tokenAContract.deploy({
admin: tokenAdmin,
symbol: "tokA",
src: "",
decimals: UInt8.from(9),
src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/FungibleToken.ts",
})
await tokenAContract.initialize()
await tokenAContract.initialize(
tokenAdmin,
UInt8.from(9),
Bool(true),
)
})

tx.sign([
Expand All @@ -97,13 +99,14 @@ describe("token integration", async () => {
adminPublicKey: tokenBAdmin,
})
await tokenBContract.deploy({
admin: tokenBAdmin,
symbol: "tokB",
src: "",
decimals: UInt8.from(9),
startUnpaused: true,
src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/FungibleToken.ts",
})
await tokenBContract.initialize()
await tokenBContract.initialize(
tokenBAdmin,
UInt8.from(9),
Bool(false),
)
})

tx.sign([deployer.key, tokenB.key, tokenBAdmin.key])
Expand Down
42 changes: 19 additions & 23 deletions FungibleToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
State,
state,
Struct,
TokenContract,
TokenContractV2,
Types,
UInt64,
Expand All @@ -22,19 +21,11 @@ import {
import { FungibleTokenAdmin, FungibleTokenAdminBase } from "./FungibleTokenAdmin.js"

interface FungibleTokenDeployProps extends Exclude<DeployArgs, undefined> {
/** Address of the contract controlling permissions for administrative actions */
admin: PublicKey
/** The token symbol. */
symbol: string
/** A source code reference, which is placed within the `zkappUri` of the contract account.
* Typically a link to a file on github. */
src: string
/** Number of decimals in a unit */
decimals: UInt8
/** Unless this is set to `true`, the tokens will start in paused mode,
* and will need to be explicitly resumed by calling the `resume()` method.
* You should only set this to `true` in atomic deploys. */
startUnpaused?: boolean
}

export class FungibleToken extends TokenContractV2 {
Expand All @@ -60,24 +51,29 @@ export class FungibleToken extends TokenContractV2 {

async deploy(props: FungibleTokenDeployProps) {
await super.deploy(props)

this.admin.set(props.admin)
this.decimals.set(props.decimals)
this.paused.set(Bool(false))

this.account.tokenSymbol.set(props.symbol)
this.paused.set(Bool(true))
this.account.zkappUri.set(props.src)

if (props.startUnpaused) {
this.paused.set(Bool(false))
} else {
this.paused.set(Bool(true))
}
}

// ** Initializes the account for tracking total circulation. */
/** Initializes the account for tracking total circulation.
* @argument {PublicKey} admin - public key where the admin contract is deployed
* @argument {UInt8} decimals - number of decimals for the token
* @argument {Bool} startPaused - if set to `Bool(true), the contract will start in a mode where token minting and transfers are paused. This should be used for non-atomic deployments
*/
@method
async initialize() {
async initialize(
admin: PublicKey,
decimals: UInt8,
startPaused: Bool,
) {
this.account.provedState.requireEquals(Bool(false))
super.init()
this.admin.set(admin)
this.decimals.set(decimals)
this.paused.set(Bool(false))

this.paused.set(startPaused)

const accountUpdate = AccountUpdate.createSigned(this.address, this.deriveTokenId())
let permissions = Permissions.default()
// This is necessary in order to allow token holders to burn.
Expand Down
27 changes: 16 additions & 11 deletions documentation/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,26 @@ The on-chain state is defined as follows:
@state(Bool) paused = State<Bool>()
```

The `deploy` function takes as arguments
The `deploy()` function takes as arguments

- The public key of the account that the admin contract has been deployed to
- A symbol to use as the token symbol
- A string to use as the token symbol
- A string pointing to the source code of the contract -- when following the standard, this should
point to the source of the standard implementation on github

Immediately after deploying the contract -- ideally, in the same transaction -- the contract needs
to be initialized via the `initialize()` method. Its arguments are

- The public key of the account that the admin contract has been deployed to
- A `UInt8` for the number of decimals
- An optional `boolean` to signify whether token transfers should be enabled immediately. Unless
this is supplied and set to `true`, the token contract will be in a paused state initially, and
the `resume()` will need to be called before tokens can be minted or transferred. This is safer if
you have a non-atomic deploy (i.e., if you do not have the admin contract deployed in the same
transaction as the token contract itself).

and initializes the state of the contract. Initially, the circulating supply is set to zero, as no
tokens have been created yet.
- A `Bool` to determine whether the token contract should start in paused mode. whether token
transfers should be enabled immediately. If set to `Bool(true)`, the token contract will be in a
paused state initially, and the `resume()` method will need to be called before tokens can be
minted or transferred. This is safer if you have a non-atomic deploy (i.e., if you do not have the
admin contract deployed in the same transaction as the token contract is itself is deployed and
initialized).

This method initializes the state of the contract. Initially, the circulating supply is set to zero,
as no tokens have been created yet.

## Methods

Expand Down
38 changes: 23 additions & 15 deletions documentation/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ token contract itself, and initializing the contract

## Deploying an admin contract

The first step is deploying the admin contract via its `deploy()` function.

The admin contract handles permissions for privileged actions, such as minting. It is called by the
token contract whenever a user tries to do a privileged action.

Expand All @@ -19,29 +21,35 @@ If you want to change your admin contract, you can write a contract that `extend

[!NOTE] Note that if you want to use a custom admin contract, you should write the admin contract
from scratch. Inheriting from `FungibleTokenAdmin` and overwriting specific methods might not work.
You can find an example of a custom admin contract in `FungibleToken.test.ts`.
You can find an example of a custom admin contract in `FungibleToken.test.ts`.ß

## Deploying the token contract
The `initialize()` method of `FungibleToken` takes as one argument the address of the admin
contract. If you have written your own admin contract, you will also need to set
`FungibleToken.AdminContract` to that class.

The `deploy` function of `FungibleToken` takes as one argument the address of the admin contract. If
you have written your own admin contract, you will also need to set `FungibleToken.AdminContract` to
that class.
[!NOTE] If you do not use the `FungibleToken` class as is, third parties that want to integrate your
token will need to use your custom contract as well.

[!NOTE] If you do not use the `FungibleToken` as is, third parties that want to integrate your token
will need to use your custom contract as well.
## Initializing and deploying the token contract

## Initializing the token contract
Next, the token contract needs to be deployed, via its `deploy()` function.

After being deployed, the token contract needs to be initialized, by calling the `initialize()`
method. That method creates an account on the chain that will be used to track the current
circulation of the token.
method. That method initializes the contract state, and creates an account on the chain that will be
used to track the current circulation of the token.

[!NOTE] All three steps above can be carried out in a single transaction, or in separate
transactions. [!NOTE] Each of the three steps requires funding a new account on the chain via
`AccountUpdate.fundNewAccount`. [!NOTE] Per default, the token contract will start in paused mode.
If you perform all the steps in one single transaction, you can instead opt for starting it in
non-paused mode. Otherwise, you will need to call `resume()` before any tokens can be minted or
transferred.
transactions. It is highly recommended to have a single transaction with all three steps.

[!NOTE] Unless you have a very good reason, please use one transaction that deploys the admin
contract, deploys the token contract, and calls `initialize()` on the token contract.

[!NOTE] Each of the three steps requires funding a new account on the chain via
`AccountUpdate.fundNewAccount`.

[!NOTE] If you use separate transactions for deploying the admin contract and deploying and
initializing the token contract, you should start the token contract in paused mode, and only call
`resume()` after you have verified that the admin contract has been successfully deployed.

Refer to
[examples/e2e.eg.ts](https://github.com/MinaFoundation/mina-fungible-token/blob/main/examples/e2e.eg.ts)
Expand Down
8 changes: 5 additions & 3 deletions documentation/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ const deployTx = await Mina.transaction({
AccountUpdate.fundNewAccount(deployer, 3)
await adminContract.deploy({ adminPublicKey: admin.publicKey })
await token.deploy({
admin: admin.publicKey,
symbol: "abc",
src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/examples/e2e.eg.ts",
decimals: UInt8.from(9),
})
await tokenBContract.initialize()
await tokenBContract.initialize(
admin.publicKey,
UInt8.from(9),
Bool(false),
)
})
await deployTx.prove()
deployTx.sign([deployer.key, contract.privateKey, admin.privateKey])
Expand Down
18 changes: 10 additions & 8 deletions examples/concurrent-transfer.eg.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AccountUpdate, Mina, PrivateKey, PublicKey, UInt64, UInt8 } from "o1js"
import { AccountUpdate, Bool, Mina, PrivateKey, PublicKey, UInt64, UInt8 } from "o1js"
import { setLightnetAccountManagerEndpoint } from "o1js/dist/node/lib/mina/fetch.js"
import { FungibleToken, FungibleTokenAdmin } from "../index.js"

const url = "https://proxy.devnet.minaexplorer.com/graphql"
Expand Down Expand Up @@ -88,17 +89,18 @@ const deployTx = await Mina.transaction({
AccountUpdate.fundNewAccount(feepayer.publicKey, 3)
await adminContract.deploy({ adminPublicKey: admin.publicKey })
await token.deploy({
admin: admin.publicKey,
symbol: "abc",
src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/examples/e2e.eg.ts",
decimals: UInt8.from(9),
// We can set `startUnpaused` to true here, because we are doing an atomic deployment
src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/FungibleToken.ts",
})
await token.initialize(
admin.publicKey,
UInt8.from(9),
// We can set `startPaused` to `Bool(false)` here, because we are doing an atomic deployment
// If you are not deploying the admin and token contracts in the same transaction,
// it is safer to start the tokens paused, and resume them only after verifying that
// the admin contract has been deployed
startUnpaused: true,
})
await token.initialize()
Bool(false),
)
})
await deployTx.prove()
deployTx.sign([feepayer.privateKey, contract.privateKey, admin.privateKey])
Expand Down
17 changes: 9 additions & 8 deletions 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, UInt8 } from "o1js"
import { AccountUpdate, Bool, Mina, PrivateKey, UInt64, UInt8 } from "o1js"
import { FungibleToken, FungibleTokenAdmin } from "../index.js"

const localChain = await Mina.LocalBlockchain({
Expand All @@ -25,17 +25,18 @@ const deployTx = await Mina.transaction({
AccountUpdate.fundNewAccount(deployer, 3)
await adminContract.deploy({ adminPublicKey: admin.publicKey })
await token.deploy({
admin: admin.publicKey,
symbol: "abc",
src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/examples/e2e.eg.ts",
decimals: UInt8.from(9),
// We can set `startUnpaused` to true here, because we are doing an atomic deployment
src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/FungibleToken.ts",
})
await token.initialize(
admin.publicKey,
UInt8.from(9),
// We can set `startPaused` to `Bool(false)` here, because we are doing an atomic deployment
// If you are not deploying the admin and token contracts in the same transaction,
// it is safer to start the tokens paused, and resume them only after verifying that
// the admin contract has been deployed
startUnpaused: true,
})
await token.initialize()
Bool(false),
)
})
await deployTx.prove()
deployTx.sign([deployer.key, contract.privateKey, admin.privateKey])
Expand Down
16 changes: 9 additions & 7 deletions examples/escrow.eg.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { equal } from "node:assert"
import {
AccountUpdate,
Bool,
DeployArgs,
method,
Mina,
Expand Down Expand Up @@ -80,17 +81,18 @@ const deployTokenTx = await Mina.transaction({
AccountUpdate.fundNewAccount(deployer, 3)
await adminContract.deploy({ adminPublicKey: admin.publicKey })
await token.deploy({
admin: admin.publicKey,
symbol: "abc",
src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/examples/escrow.eg.ts",
decimals: UInt8.from(9),
// We can set `startUnpaused` to true here, because we are doing an atomic deployment
src: "https://github.com/MinaFoundation/mina-fungible-token/blob/main/FungibleToken.ts",
})
await token.initialize(
admin.publicKey,
UInt8.from(9),
// We can set `startPaused` to `Bool(false)` here, because we are doing an atomic deployment
// If you are not deploying the admin and token contracts in the same transaction,
// it is safer to start the tokens paused, and resume them only after verifying that
// the admin contract has been deployed
startUnpaused: true,
})
await token.initialize()
Bool(false),
)
})
await deployTokenTx.prove()
deployTokenTx.sign([deployer.key, tokenContract.privateKey, admin.privateKey])
Expand Down

0 comments on commit 4c38e83

Please sign in to comment.