-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
393 additions
and
135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
}) | ||
}) |
Oops, something went wrong.