Skip to content

Commit

Permalink
update: Exchange contract and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
berzanorg committed Dec 18, 2023
1 parent d37d232 commit 1565698
Show file tree
Hide file tree
Showing 4 changed files with 393 additions and 135 deletions.
289 changes: 188 additions & 101 deletions contracts/src/Exchange.test.ts
Original file line number Diff line number Diff line change
@@ -1,125 +1,212 @@
import { AccountUpdate, Field, Mina, PrivateKey, PublicKey, Encoding, UInt64 } from 'o1js'
import { Exchange } from './Exchange'
import {
AccountUpdate,
Field,
Mina,
PrivateKey,
PublicKey,
Encoding,
UInt64,
Signature,
MerkleTree,
Poseidon,
} from 'o1js'
import {
AUTHORITY_PRIVATE_KEY,
Errors,
Exchange,
ORDERS_HEIGHT,
OrderObject,
PAIRS_HEIGHT,
PairObject,
PairsWitness,
getErrorMessage,
} from './Exchange'
import { Token } from './Token'

const proofsEnabled = false

class TestAuthority {
// Stores all the pairs in an array.
private pairs: Array<PairObject> = []
// Stores all the pairs in a tree.
private pairsTree: MerkleTree = new MerkleTree(PAIRS_HEIGHT)

// Stores all the BUY orders of each pair in arrays.
private buyOrders: Map<number, Array<OrderObject>> = new Map()
// Stores all the BUY orders of each pair in trees.
private buyOrdersTrees: Map<number, MerkleTree> = new Map()

// Stores all the SELL orders of each pair in an array.
private sellOrders: Map<number, Array<OrderObject>> = new Map()
// Stores all the SELL orders of each pair in trees.
private sellOrdersTrees: Map<number, MerkleTree> = new Map()

private createSignature(message: Array<Field>): Signature {
return Signature.create(AUTHORITY_PRIVATE_KEY, message)
}

createPair(baseCurrencyAddress: PublicKey, quoteCurrencyAddress: PublicKey) {
const pairIndex = BigInt(this.pairs.length)

const emptyRoot = new MerkleTree(ORDERS_HEIGHT).getRoot()

const pair: PairObject = {
baseCurrencyAddress,
quoteCurrencyAddress,
buyOrdersRoot: emptyRoot,
sellOrdersRoot: emptyRoot,
}

this.pairs.push(pair)

this.pairsTree.setLeaf(
pairIndex,
Poseidon.hash([
...pair.baseCurrencyAddress.toFields(),
...pair.quoteCurrencyAddress.toFields(),
pair.buyOrdersRoot,
pair.sellOrdersRoot,
])
)

const pairsWitness = new PairsWitness(this.pairsTree.getWitness(pairIndex))

return {
pairsWitness,
signature: this.createSignature([
...baseCurrencyAddress.toFields(),
...quoteCurrencyAddress.toFields(),
new Field(pairIndex),
]),
}
}
}

const testAuthority = new TestAuthority()

describe('Exchange Contract', () => {
let deployerPrivateKey: PrivateKey
let deployerPublicKey: PublicKey
const Local = Mina.LocalBlockchain({ proofsEnabled })
Mina.setActiveInstance(Local)

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

let user2PrivateKey: PrivateKey
let user2PublicKey: PublicKey
const user1PrivateKey: PrivateKey = Local.testAccounts[1].privateKey
const user1PublicKey: PublicKey = Local.testAccounts[1].publicKey

let exchangeZkAppInstance: Exchange
let exchangeZkAppPrivateKey: PrivateKey
let exchangeZkAppPublicKey: PublicKey
let exchangeZkAppverificationKey: { data: string, hash: Field }
const user2PrivateKey: PrivateKey = Local.testAccounts[2].privateKey
const user2PublicKey: PublicKey = Local.testAccounts[2].publicKey

let tokenOneZkAppInstance: Token
let tokenOneZkAppPrivateKey: PrivateKey
let tokenOneZkAppPublicKey: PublicKey
let tokenOneZkAppverificationKey: { data: string, hash: Field }
const exchangeZkAppPrivateKey: PrivateKey = PrivateKey.random()
const exchangeZkAppPublicKey: PublicKey = exchangeZkAppPrivateKey.toPublicKey()
const exchange: Exchange = new Exchange(exchangeZkAppPublicKey)

let tokenTwoZkAppInstance: Token
let tokenTwoZkAppPrivateKey: PrivateKey
let tokenTwoZkAppPublicKey: PublicKey
let tokenTwoZkAppverificationKey: { data: string, hash: Field }
const tokenOneZkAppPrivateKey: PrivateKey = PrivateKey.random()
const tokenOneZkAppPublicKey: PublicKey = tokenOneZkAppPrivateKey.toPublicKey()
const tokenOne: Token = new Token(tokenOneZkAppPublicKey)

const tokenTwoZkAppPrivateKey: PrivateKey = PrivateKey.random()
const tokenTwoZkAppPublicKey: PublicKey = tokenTwoZkAppPrivateKey.toPublicKey()
const tokenTwo: Token = new Token(tokenTwoZkAppPublicKey)

let exchangeZkAppverificationKey: { data: string; hash: Field }
let tokenZkAppverificationKey: { data: string; hash: Field }

beforeAll(async () => {
const Local = Mina.LocalBlockchain({ proofsEnabled })
Mina.setActiveInstance(Local)
beforeAll(async () => {
// we're compiling both contracts in parallel to finish earlier
const results = await Promise.all([Exchange.compile(), Token.compile()])

deployerPrivateKey = Local.testAccounts[0].privateKey
deployerPublicKey = Local.testAccounts[0].publicKey
exchangeZkAppverificationKey = results[0].verificationKey
tokenZkAppverificationKey = results[1].verificationKey
})

user1PrivateKey = Local.testAccounts[1].privateKey
user1PublicKey = Local.testAccounts[1].publicKey
it('can create exchange', async () => {
const tx = await Mina.transaction(deployerPublicKey, () => {
AccountUpdate.fundNewAccount(deployerPublicKey)
exchange.deploy({
verificationKey: exchangeZkAppverificationKey,
zkappKey: exchangeZkAppPrivateKey,
})
})

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

user2PrivateKey = Local.testAccounts[2].privateKey
user2PublicKey = Local.testAccounts[2].publicKey
it('can create ONE token', async () => {
const symbol = Encoding.stringToFields('ONE')[0]
const supply = UInt64.from(21_000_000)

const tx = await Mina.transaction(user1PublicKey, () => {
AccountUpdate.fundNewAccount(user1PublicKey)
AccountUpdate.fundNewAccount(user1PublicKey)

// Deploy exchange zkApp.
exchangeZkAppPrivateKey = PrivateKey.random()
exchangeZkAppPublicKey = exchangeZkAppPrivateKey.toPublicKey()
exchangeZkAppInstance = new Exchange(exchangeZkAppPublicKey)
exchangeZkAppverificationKey = (await Exchange.compile()).verificationKey
tokenOne.deploy({
verificationKey: tokenZkAppverificationKey,
zkappKey: tokenOneZkAppPrivateKey,
symbol: symbol,
fixedSupply: supply,
})
})

const txToDeployExchange = await Mina.transaction(deployerPublicKey, () => {
AccountUpdate.fundNewAccount(deployerPublicKey)
exchangeZkAppInstance.deploy({
verificationKey: exchangeZkAppverificationKey,
zkappKey: exchangeZkAppPrivateKey
})
await tx.prove()
await tx.sign([user1PrivateKey, tokenOneZkAppPrivateKey]).send()
})
await txToDeployExchange.prove()
await txToDeployExchange.sign([deployerPrivateKey]).send()


// Deploy token one zkApp.
tokenOneZkAppPrivateKey = PrivateKey.random()
tokenOneZkAppPublicKey = tokenOneZkAppPrivateKey.toPublicKey()
tokenOneZkAppInstance = new Token(tokenOneZkAppPublicKey)
tokenOneZkAppverificationKey = (await Token.compile()).verificationKey

const txnTokenOne = await Mina.transaction(user1PublicKey, () => {
AccountUpdate.fundNewAccount(user1PublicKey)
AccountUpdate.fundNewAccount(user1PublicKey)

const name = Encoding.stringToFields('ONE')[0]
const ticker = Encoding.stringToFields('Token One')[0]
const supply = UInt64.from(21_000_000)

tokenOneZkAppInstance.deploy({
verificationKey: tokenOneZkAppverificationKey,
zkappKey: tokenOneZkAppPrivateKey,
name,
ticker,
supply,
})

it('can create TWO token', async () => {
const symbol = Encoding.stringToFields('TWO')[0]
const supply = UInt64.from(1_000_000_000)

const tx = await Mina.transaction(user2PublicKey, () => {
AccountUpdate.fundNewAccount(user2PublicKey)
AccountUpdate.fundNewAccount(user2PublicKey)

tokenTwo.deploy({
verificationKey: tokenZkAppverificationKey,
zkappKey: tokenTwoZkAppPrivateKey,
symbol,
fixedSupply: supply,
})
})

await tx.prove()
await tx.sign([user2PrivateKey, tokenTwoZkAppPrivateKey]).send()
})
await txnTokenOne.prove()
await txnTokenOne.sign([user1PrivateKey]).send()


// Deploy token two zkApp.
tokenTwoZkAppPrivateKey = PrivateKey.random()
tokenTwoZkAppPublicKey = tokenTwoZkAppPrivateKey.toPublicKey()
tokenTwoZkAppInstance = new Token(tokenTwoZkAppPublicKey)
tokenTwoZkAppverificationKey = tokenOneZkAppverificationKey // They are different instances of the same smart contract. So their verification key is the same.

const txnTokenTwo = await Mina.transaction(user2PublicKey, () => {
AccountUpdate.fundNewAccount(user2PublicKey)
AccountUpdate.fundNewAccount(user2PublicKey)

const name = Encoding.stringToFields('TWO')[0]
const ticker = Encoding.stringToFields('Token Two')[0]
const supply = UInt64.from(100_000_000)

tokenTwoZkAppInstance.deploy({
verificationKey: tokenTwoZkAppverificationKey,
zkappKey: tokenTwoZkAppPrivateKey,
name,
ticker,
supply
})

it('can create pairs', async () => {
const baseCurrencyAddress = tokenOne.address
const quoteCurrencyAddress = tokenTwo.address

const { pairsWitness, signature } = testAuthority.createPair(baseCurrencyAddress, quoteCurrencyAddress)

const tx = await Mina.transaction(user1PublicKey, () => {
exchange.createPair(baseCurrencyAddress, quoteCurrencyAddress, pairsWitness, signature)
})

await tx.prove()
await tx.sign([user1PrivateKey]).send()
})
await txnTokenTwo.prove()
await txnTokenTwo.sign([user2PrivateKey]).send()
})

it('can place orders', async () => {
const tx = await Mina.transaction(user1PublicKey, () => {
AccountUpdate.fundNewAccount(user1PublicKey)
exchangeZkAppInstance.placeOrder(tokenOneZkAppPublicKey, tokenTwoZkAppPublicKey, UInt64.from(200_000))

it("can't create pairs when signature is invalid", async () => {
try {
const baseCurrencyAddress = tokenOne.address
const quoteCurrencyAddress = tokenTwo.address

const { pairsWitness, signature } = testAuthority.createPair(baseCurrencyAddress, quoteCurrencyAddress)

const fakeSignature = Signature.create(PrivateKey.random(), [new Field(0)])

const tx = await Mina.transaction(user1PublicKey, () => {
exchange.createPair(baseCurrencyAddress, quoteCurrencyAddress, pairsWitness, fakeSignature)
})

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

throw 'Must have failed!'
} catch (error) {
const errorMessage = getErrorMessage(error)
expect(errorMessage).toEqual(Errors.InvalidSignature)
}
})
await tx.prove()
await tx.sign([user1PrivateKey]).send()
})
})
Loading

0 comments on commit 1565698

Please sign in to comment.