Skip to content

Commit

Permalink
update: Token contract
Browse files Browse the repository at this point in the history
  • Loading branch information
berzanorg committed Dec 22, 2023
1 parent ff1809b commit c590ed0
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 78 deletions.
101 changes: 64 additions & 37 deletions contracts/src/Token.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AccountUpdate, Field, Mina, PrivateKey, PublicKey, UInt64, Encoding } from 'o1js'
import { AccountUpdate, Field, Mina, PrivateKey, PublicKey, UInt64, Encoding, Experimental } from 'o1js'
import { Token } from './Token'

const proofsEnabled = false
Expand All @@ -9,8 +9,13 @@ describe('Token Contract', () => {

const deployerPrivateKey: PrivateKey = Local.testAccounts[0].privateKey
const deployerPublicKey: PublicKey = Local.testAccounts[0].publicKey

const userPrivateKey: PrivateKey = Local.testAccounts[1].privateKey
const userPublicKey: PublicKey = Local.testAccounts[1].publicKey

const tokenZkAppPrivateKey: PrivateKey = PrivateKey.random()
const tokenZkAppPublicKey: PublicKey = tokenZkAppPrivateKey.toPublicKey()

const token: Token = new Token(tokenZkAppPublicKey)

let verificationKey: { data: string; hash: Field }
Expand All @@ -20,69 +25,91 @@ describe('Token Contract', () => {
})

it('can create a new token', async () => {
const symbol = Encoding.stringToFields('MY')[0]
const fixedSupply = UInt64.from(100_000_000)

const tx = await Mina.transaction(deployerPublicKey, () => {
AccountUpdate.fundNewAccount(deployerPublicKey)
AccountUpdate.fundNewAccount(deployerPublicKey)
token.deploy({ verificationKey, zkappKey: tokenZkAppPrivateKey, symbol, fixedSupply })
token.deploy({ verificationKey, zkappKey: tokenZkAppPrivateKey })
})

await tx.prove()
await tx.sign([deployerPrivateKey, tokenZkAppPrivateKey]).send()
})

expect(token.fixedSupply.get()).toEqual(fixedSupply)
expect(Mina.getBalance(deployerPublicKey, token.token.id)).toEqual(fixedSupply)
it('can initialize token ', async () => {
const symbol = Encoding.stringToFields('MYT')[0]
const decimals = UInt64.from(3)
const maxSupply = UInt64.from(100_000_000)

const tx = await Mina.transaction(deployerPublicKey, () => {
token.initialize(symbol, decimals, maxSupply)
})

await tx.prove()
await tx.sign([deployerPrivateKey, tokenZkAppPrivateKey]).send()

console.log(token.decimals.get().toString())

expect(token.decimals.get()).toEqual(decimals)
expect(token.symbol.get()).toEqual(symbol)
expect(token.maxSupply.get()).toEqual(maxSupply)
expect(token.circulatingSupply.get()).toEqual(UInt64.from(0))
})

it('can send and receive tokens', async () => {
const receiverAddress = PrivateKey.random().toPublicKey()
const amount = UInt64.from(20_000_000)
it('can mint tokens', async () => {
const receiverAddress = userPublicKey
const amount = UInt64.from(50_000_000)

const tx = await Mina.transaction(deployerPublicKey, () => {
AccountUpdate.fundNewAccount(deployerPublicKey)
token.transfer(deployerPublicKey, receiverAddress, amount)
token.mint(receiverAddress, amount)
})

await tx.prove()
await tx.sign([deployerPrivateKey]).send()

expect(Mina.getBalance(deployerPublicKey, token.token.id)).toEqual(UInt64.from(80_000_000))
expect(Mina.getBalance(receiverAddress, token.token.id)).toEqual(amount)
})

it("can't send and receive tokens if balance is not enough", async () => {
try {
const receiverAddress = PrivateKey.random().toPublicKey()
const amount = UInt64.from(90_000_000)
it('can send and receive tokens', async () => {
const sender = userPublicKey
const receiver = PrivateKey.random().toPublicKey()
const amount = UInt64.from(20_000_000)
const remainingAmount = UInt64.from(80_000_000)

const tx = await Mina.transaction(deployerPublicKey, () => {
AccountUpdate.fundNewAccount(deployerPublicKey)
token.transfer(deployerPublicKey, receiverAddress, amount)
})
const tx = await Mina.transaction(sender, () => {
AccountUpdate.fundNewAccount(sender)
token.transfer(sender, receiver, amount)
})

await tx.prove()
await tx.sign([deployerPrivateKey]).send()
await tx.prove()
await tx.sign([userPrivateKey]).send()

throw 'this must have failed'
} catch (error) {}
expect(Mina.getBalance(receiver, token.token.id)).toEqual(amount)
expect(Mina.getBalance(sender, token.token.id)).toEqual(remainingAmount)
})

it("can't send and receive tokens if not signed by the sender", async () => {
try {
const receiverAddress = PrivateKey.random().toPublicKey()
const amount = UInt64.from(10_000_000)
it("can't send and receive tokens if balance is not enough", async () => {
const receiverAddress = PrivateKey.random().toPublicKey()
const amount = UInt64.from(90_000_000)

const tx = await Mina.transaction(deployerPublicKey, () => {
AccountUpdate.fundNewAccount(deployerPublicKey)
token.transfer(deployerPublicKey, receiverAddress, amount)
})
const tx = await Mina.transaction(deployerPublicKey, () => {
AccountUpdate.fundNewAccount(deployerPublicKey)
token.transfer(deployerPublicKey, receiverAddress, amount)
})

await tx.prove()
await tx.sign([]).send()
await tx.prove()
await tx.sign([deployerPrivateKey]).send()
})

it("can't send and receive tokens if not signed by the sender", async () => {
const receiverAddress = PrivateKey.random().toPublicKey()
const amount = UInt64.from(10_000_000)

const tx = await Mina.transaction(deployerPublicKey, () => {
AccountUpdate.fundNewAccount(deployerPublicKey)
token.transfer(deployerPublicKey, receiverAddress, amount)
})

throw 'this must have failed'
} catch (error) {}
await tx.prove()
await tx.sign([]).send()
})
})
122 changes: 81 additions & 41 deletions contracts/src/Token.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,119 @@
import {
AccountUpdate,
DeployArgs,
Experimental,
Field,
Int64,
Permissions,
PublicKey,
SmartContract,
State,
UInt64,
method,
state
} from "o1js"

interface CustomDeployArgs {
symbol: Field
fixedSupply: UInt64
}
state,
} from 'o1js'

/**
*
*
* # `Token` Smart Contract
*
*
* The smart contract for tokens on Xane.
*
* **Note:** `deploy` method requires two account funding.
*
*
* **Note:** `deploy` method requires two account funding.
*
* # Usage
*
*
* ```ts
* // Import `Token` smart contract.
* import { Token } from 'xane'
*
*
* // Create an instance of `Token` contract.
* const token = new Token(zkAppPublicKey)
*
* // Deploy it with given symbol and supply.
* token.deploy({ verificationKey, zkappKey, symbol, fixedSupply })
*
* // Transfer tokens.
* token.transfer(receiver, amount)
*
*
* // Deploy it.
* token.deploy({ verificationKey, zkappKey })
*
*
* ```
*
*
*/
export class Token extends SmartContract {
@state(Field) symbol = State<Field>()
enum TokenError {
MaxSupplyCannotBeExceeded = 'TOKEN: Max supply cannot be exceeded.',
}

@state(UInt64) fixedSupply = State<UInt64>()
export class Token extends SmartContract {
@state(UInt64) symbol = State<Field>()
@state(UInt64) decimals = State<UInt64>()
@state(UInt64) maxSupply = State<UInt64>()
@state(UInt64) circulatingSupply = State<UInt64>()

deploy(args: DeployArgs & CustomDeployArgs) {
deploy(args: DeployArgs) {
super.deploy(args)

this.account.permissions.set({
...Permissions.default(),
editState: Permissions.proof(),
setTokenSymbol: Permissions.proof(),
send: Permissions.none(),
receive: Permissions.proof(),
access: Permissions.proof()
send: Permissions.proof(),
access: Permissions.proof(),
})
}

this.symbol.set(args.symbol)
@method initialize(symbol: Field, decimals: UInt64, maxSupply: UInt64) {
this.symbol.set(symbol)
this.decimals.set(decimals)
this.maxSupply.set(maxSupply)
this.circulatingSupply.set(UInt64.from(0))
}

@method mint(receiver: PublicKey, amount: UInt64) {
const maxSupply = this.maxSupply.getAndRequireEquals()
const circulatingSupply = this.circulatingSupply.getAndRequireEquals()

const newCirculatingSupply = circulatingSupply.add(amount)

newCirculatingSupply.assertLessThanOrEqual(maxSupply, TokenError.MaxSupplyCannotBeExceeded)

this.token.mint({
address: this.sender,
amount: args.fixedSupply
address: receiver,
amount,
})
this.fixedSupply.set(args.fixedSupply)

this.circulatingSupply.set(newCirculatingSupply)
}

@method transfer(from: PublicKey, to: PublicKey, amount: UInt64) {
this.token.send({
from,
to,
amount
@method burn(burner: PublicKey, amount: UInt64) {
const circulatingSupply = this.circulatingSupply.getAndRequireEquals()

const newCirculatingSupply = circulatingSupply.sub(amount)

this.token.burn({
address: burner,
amount,
})

this.circulatingSupply.set(newCirculatingSupply)
}

@method transfer(sender: PublicKey, receiver: PublicKey, amount: UInt64) {
this.token.send({ from: sender, to: receiver, amount })
}
}

@method transferWithCallback(
sender: PublicKey,
receiver: PublicKey,
amount: UInt64,
callback: Experimental.Callback<any>
) {
const tokenId = this.token.id

const senderAccountUpdate = this.approve(callback, AccountUpdate.Layout.AnyChildren)
senderAccountUpdate.body.tokenId.assertEquals(tokenId)
senderAccountUpdate.body.publicKey.assertEquals(sender)

const negativeAmount = Int64.fromObject(senderAccountUpdate.body.balanceChange)
negativeAmount.assertEquals(Int64.from(amount).neg())

const receiverAccountUpdate = Experimental.createChildAccountUpdate(this.self, receiver, tokenId)
receiverAccountUpdate.balance.addInPlace(amount)
}
}

0 comments on commit c590ed0

Please sign in to comment.