@lumina-dex/contracts
Version:
Contracts for Lumina DEX.
1,055 lines (1,042 loc) • 107 kB
JavaScript
import { FungibleToken } from 'mina-fungible-token';
export * from 'mina-fungible-token';
import { Field, Struct, UInt32, Poseidon, PublicKey, Signature, MerkleMapWitness, Provable, Gadgets, state, method, VerificationKey, TokenContract, State, Mina, Bool, MerkleMap, Permissions, AccountUpdate, UInt64, TokenId, AccountUpdateForest, Int64, Types, assert, SmartContract, MerkleWitness } from 'o1js';
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
function __decorate(decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
}
function __metadata(metadataKey, metadataValue) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
const updateSigner = "UpdateSigner";
const updateFactory = "UpdateFactory";
const updateDelegator = "UpdateDelegator";
const updateProtocol = "UpdateProtocol";
const deployPoolRight = Field(1);
const updateSignerRight = Field(2);
const updateProtocolRight = Field(4);
const updateDelegatorRight = Field(8);
const updateFactoryRight = Field(16);
const allRight = Field(31); // 1+2+4+8+16
function hasRight(userRight, right) {
return Gadgets.and(userRight, right, 32).equals(right);
}
/**
* Information needed to update the factory verification key
*/
class UpdateFactoryInfo extends Struct({
/**
* new verification key hash to submit
*/
newVkHash: Field,
/**
* deadline to use this signature
*/
deadlineSlot: UInt32
}) {
constructor(value) {
super(value);
}
/**
* Data use to create the signature
* @returns array of field of all parameters
*/
toFields() {
return UpdateFactoryInfo.toFields(this);
}
hash() {
return Poseidon.hashWithPrefix(updateFactory, this.toFields());
}
}
/**
* Information needed to update the delegator/protocol
*/
class UpdateAccountInfo extends Struct({
// old account address
oldUser: PublicKey,
// new account address
newUser: PublicKey,
// signature right to use
right: Field,
// deadline to use this signature
deadlineSlot: UInt32
}) {
constructor(value) {
super(value);
}
/**
* Data use to create the signature
* @returns array of field of all parameters
*/
toFields() {
return UpdateAccountInfo.toFields(this);
}
hash() {
let prefix = "";
if (hasRight(this.right, updateDelegatorRight).toBoolean()) {
prefix = updateDelegator;
}
else if (hasRight(this.right, updateProtocolRight).toBoolean()) {
prefix = updateProtocol;
}
else {
throw new Error("Invalid right to update account");
}
return Poseidon.hashWithPrefix(prefix, this.toFields());
}
}
/**
* Information needed to update the approved signer
*/
class UpdateSignerData extends Struct({
// old signer root
oldRoot: Field,
// new signer root
newRoot: Field,
// deadline to use this signature
deadlineSlot: UInt32
}) {
constructor(value) {
super(value);
}
/**
* Data use to create the signature
* @returns array of field of all parameters
*/
toFields() {
return UpdateSignerData.toFields(this);
}
hash() {
return Poseidon.hashWithPrefix(updateSigner, this.toFields());
}
}
class MultisigInfo extends Struct({
// merkle root of account who can sign to upgrade
approvedUpgrader: Field,
// hash of data passed to the signature
messageHash: Field,
// deadline
deadlineSlot: UInt32
}) {
constructor(value) {
super(value);
}
/**
* Data use to create the signature
* @returns array of field of all parameters
*/
toFields() {
return MultisigInfo.toFields(this);
}
}
/**
* Information needed to verify signature in the proof
*/
class SignatureInfo extends Struct({
// signer account
user: PublicKey,
// witness of this account in the merkle root
witness: MerkleMapWitness,
// signature created by this user
signature: Signature,
// right
right: Field
}) {
constructor(value) {
super(value);
}
/**
* Check if the signature match the current user and data subnit
* @param merkle list of approved signer
* @param data data use for the signature
* @returns true if the signature is valid
*/
validate(merkle, data) {
const hashUser = Poseidon.hash(this.user.toFields());
const value = Poseidon.hash(this.right.toFields());
const [root, key] = this.witness.computeRootAndKey(value);
root.assertEquals(merkle, "Invalid signer");
key.assertEquals(hashUser, "Invalid key");
return this.signature.verify(this.user, data);
}
}
/**
* Information needed to verify the signatures is correct
*/
class Multisig extends Struct({
info: MultisigInfo,
signatures: Provable.Array(SignatureInfo, 2)
}) {
constructor(value) {
if (value.signatures.length !== 2) {
throw new Error("Need 2 signatures");
}
super(value);
}
/**
* Check if the signature match the current user and data submit
* @param data needed to verify the signature
*/
verifyUpdateFactory(updateInfo) {
const right = updateFactoryRight;
verifySignature(this.signatures, updateInfo.deadlineSlot, updateFactory, this.info, this.info.approvedUpgrader, updateInfo.toFields(), right);
}
/**
* Check if the signature match the current user and data submit
* @param data needed to verify the signature
*/
verifyUpdateDelegator(updateInfo) {
const right = updateDelegatorRight;
verifySignature(this.signatures, updateInfo.deadlineSlot, updateDelegator, this.info, this.info.approvedUpgrader, updateInfo.toFields(), right);
}
/**
* Check if the signature match the current user and data submit
* @param data needed to verify the signature
*/
verifyUpdateProtocol(updateInfo) {
const right = updateProtocolRight;
verifySignature(this.signatures, updateInfo.deadlineSlot, updateProtocol, this.info, this.info.approvedUpgrader, updateInfo.toFields(), right);
}
}
/**
* Information needed to verify the multisig is correct to update the signer list
*/
class MultisigSigner extends Struct({
info: MultisigInfo,
signatures: Provable.Array(SignatureInfo, 2),
newSignatures: Provable.Array(SignatureInfo, 2)
}) {
constructor(value) {
if (value.signatures.length !== 2) {
throw new Error("Need 2 signatures");
}
if (value.newSignatures.length !== 2) {
throw new Error("Need 2 new signatures");
}
super(value);
}
/**
* Check if the signature match the current user and data submit
* @param data needed to verify the signature
*/
verifyUpdateSigner(upgradeInfo) {
const right = updateSignerRight;
upgradeInfo.oldRoot.equals(this.info.approvedUpgrader).assertTrue("Merkle root doesn't match");
upgradeInfo.oldRoot.equals(upgradeInfo.newRoot).assertFalse("Can't reuse same merkle");
verifySignature(this.signatures, upgradeInfo.deadlineSlot, updateSigner, this.info, this.info.approvedUpgrader, upgradeInfo.toFields(), right);
verifySignature(this.newSignatures, upgradeInfo.deadlineSlot, updateSigner, this.info, upgradeInfo.newRoot, upgradeInfo.toFields(), right);
}
}
/**
* Check if the 2 signatures are valid
*/
function verifySignature(signatures, deadlineSlot, prefix, info, root, data, right) {
info.deadlineSlot.equals(deadlineSlot).assertTrue("Deadline doesn't match");
// check the signature come from 2 different users
signatures[0].user.equals(signatures[1].user).assertFalse("Can't include same signer");
const hash = Poseidon.hashWithPrefix(prefix, data);
for (let index = 0; index < signatures.length; index++) {
const element = signatures[index];
// check if he can upgrade a pool
hasRight(element.right, right).assertTrue("User doesn't have the right for this operation");
// verfify the signature validity for all users
const valid = element.validate(root, data);
// hash valid
info.messageHash.equals(hash).assertTrue("Message didn't match parameters");
valid.assertTrue("Invalid signature");
}
}
/**
* Current verification key of pool contract for testnet
*/
const poolDataTestnet = "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";
/**
* Current hash of verification key of pool contract for testnet
*/
const poolHashTestnet = Field(7480901441026468595703278519003423509807923435933557767337375565636818933806n);
/**
* Current verification key of pool token holder contract for testnet
*/
const poolTokenHolderDataTestnet = "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";
/**
* Current hash of verification key of pool token holder contract for testnet
*/
const poolTokenHolderHashTestnet = Field(10934710013189555854007459451926974774069788064534905477416401624924106043331n);
/**
* Current verification key of pool contract for mainnet
*/
const poolDataMainnet = "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";
/**
* Current hash of verification key of pool contract for mainnet
*/
const poolHashMainnet = Field(3299583463397083593714557042003388888155095734726973619427877840682642099496n);
/**
* Current verification key of pool token holder contract for mainnet
*/
const poolTokenHolderDataMainnet = "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";
/**
* Current hash of verification key of pool token holder contract for mainnet
*/
const poolTokenHolderHashMainnet = Field(19547542559998694953854465352863219978805042242060104621274795409137786879692n);
/**
* Event emitted when a new pool is created
*/
class PoolCreationEvent extends Struct({
sender: PublicKey,
signer: PublicKey,
poolAddress: PublicKey,
token0Address: PublicKey,
token1Address: PublicKey
}) {
constructor(value) {
super(value);
}
}
/**
* Event emitted when an address is updated
*/
class UpdateUserEvent extends Struct({
newUser: PublicKey
}) {
constructor(newUser) {
super({ newUser });
}
}
/**
* Event emitted when the verification key is updated
*/
class UpdateVerificationKeyEvent extends Struct({
hash: Field
}) {
constructor(hash) {
super({ hash });
}
}
/**
* Event emitted when the signer list is updated
*/
class UpdateSignerEvent extends Struct({
root: Field
}) {
constructor(root) {
super({ root });
}
}
/**
* Factory who create pools
*/
class PoolFactory extends TokenContract {
constructor() {
super(...arguments);
/**
* List of signer approved to deploy a new pool
*/
this.approvedSigner = State();
/**
* Account who collect protocol fees
*/
this.protocol = State();
/**
* Delegator account for mina pools
*/
this.delegator = State();
/**
* List of pool factory events
*/
this.events = {
poolAdded: PoolCreationEvent,
upgrade: UpdateVerificationKeyEvent,
updateSigner: UpdateSignerEvent,
updateProtocol: UpdateUserEvent,
updateDelegator: UpdateUserEvent,
updateOwner: UpdateUserEvent
};
}
/**
* Current verification key of pool contract, can differ between networks
*/
get vkPool() {
if (Mina.getNetworkId() === "mainnet") {
return new VerificationKey({
data: poolDataMainnet,
hash: poolHashMainnet
});
}
else if (Mina.getNetworkId() === "devnet" || Mina.getNetworkId() === "testnet") {
return new VerificationKey({
data: poolDataTestnet,
hash: poolHashTestnet
});
}
else {
throw new Error(`Network ${Mina.getNetworkId()} not supported`);
}
}
/**
* Current verification key of pool token holder contract, can differ between networks
*/
get vkPoolTokenHolder() {
if (Mina.getNetworkId() === "mainnet") {
return new VerificationKey({
data: poolTokenHolderDataMainnet,
hash: poolTokenHolderHashMainnet
});
}
else if (Mina.getNetworkId() === "devnet" || Mina.getNetworkId() === "testnet") {
return new VerificationKey({
data: poolTokenHolderDataTestnet,
hash: poolTokenHolderHashTestnet
});
}
else {
throw new Error(`Network ${Mina.getNetworkId()} not supported`);
}
}
/**
* Method call when you deploy the pool factory contracts
* @param args default data stored in the contracts
*/
async deploy(args) {
await super.deploy(args);
this.account.isNew.requireEquals(new Bool(true));
const defaultRoot = new MerkleMap().getRoot();
args.approvedSigner.equals(Field.empty()).assertFalse("Approved signer is empty");
args.approvedSigner.equals(defaultRoot).assertFalse("Approved signer is empty");
this.network.globalSlotSinceGenesis.requireBetween(UInt32.zero, args.multisig.info.deadlineSlot);
const updateSignerData = new UpdateSignerData({
oldRoot: Field.empty(),
newRoot: args.approvedSigner,
deadlineSlot: args.multisig.info.deadlineSlot
});
// we need 2 signatures to update signer, prevent to deadlock contract update
const right = updateSignerRight;
verifySignature(args.multisig.signatures, args.multisig.info.deadlineSlot, updateSigner, args.multisig.info, args.multisig.info.approvedUpgrader, updateSignerData.toFields(), right);
this.account.zkappUri.set(args.src);
this.account.tokenSymbol.set(args.symbol);
this.approvedSigner.set(args.approvedSigner);
this.protocol.set(args.protocol);
this.delegator.set(args.delegator);
const permissions = Permissions.default();
permissions.access = Permissions.proof();
permissions.setPermissions = Permissions.impossible();
permissions.setVerificationKey = Permissions.VerificationKey.proofDuringCurrentVersion();
this.account.permissions.set(permissions);
}
/**
* Upgrade to a new version
* @param multisig multisig data
* @param vk new verification key
*/
async updateVerificationKey(multisig, vk) {
const deadlineSlot = multisig.info.deadlineSlot;
const approvedSigner = this.approvedSigner.getAndRequireEquals();
multisig.info.approvedUpgrader.equals(approvedSigner).assertTrue("Incorrect signer list");
this.network.globalSlotSinceGenesis.requireBetween(UInt32.zero, deadlineSlot);
const upgradeInfo = new UpdateFactoryInfo({ newVkHash: vk.hash, deadlineSlot });
multisig.verifyUpdateFactory(upgradeInfo);
this.account.verificationKey.set(vk);
this.emitEvent("upgrade", new UpdateVerificationKeyEvent(vk.hash));
}
/**
* Update the list of approved signers
* @param multisig multisig data
* @param newRoot merkle root of the new list
*/
async updateApprovedSigner(multisig, newRoot) {
const oldRoot = this.approvedSigner.getAndRequireEquals();
multisig.info.approvedUpgrader.equals(oldRoot).assertTrue("Incorrect signer list");
const deadlineSlot = multisig.info.deadlineSlot;
this.network.globalSlotSinceGenesis.requireBetween(UInt32.zero, deadlineSlot);
const upgradeInfo = new UpdateSignerData({ oldRoot, newRoot, deadlineSlot });
multisig.verifyUpdateSigner(upgradeInfo);
this.approvedSigner.set(newRoot);
this.emitEvent("updateSigner", new UpdateSignerEvent(newRoot));
}
/**
* Update the protocol account address
* @param multisig multisig data
* @param newUser address of the new protocol collectord
*/
async setNewProtocol(multisig, newUser) {
const oldUser = this.protocol.getAndRequireEquals();
const deadlineSlot = multisig.info.deadlineSlot;
const approvedSigner = this.approvedSigner.getAndRequireEquals();
multisig.info.approvedUpgrader.equals(approvedSigner).assertTrue("Incorrect signer list");
this.network.globalSlotSinceGenesis.requireBetween(UInt32.zero, deadlineSlot);
const right = updateProtocolRight;
const upgradeInfo = new UpdateAccountInfo({ oldUser, newUser, right, deadlineSlot });
multisig.verifyUpdateProtocol(upgradeInfo);
this.protocol.set(newUser);
this.emitEvent("updateProtocol", new UpdateUserEvent(newUser));
}
/**
* Update the delgator address
* @param multisig multisig data
* @param newUser address of the new delegator
*/
async setNewDelegator(multisig, newUser) {
const oldUser = this.delegator.getAndRequireEquals();
const deadlineSlot = multisig.info.deadlineSlot;
const approvedSigner = this.approvedSigner.getAndRequireEquals();
multisig.info.approvedUpgrader.equals(approvedSigner).assertTrue("Incorrect signer list");
this.network.globalSlotSinceGenesis.requireBetween(UInt32.zero, deadlineSlot);
const right = updateDelegatorRight;
const upgradeInfo = new UpdateAccountInfo({ oldUser, newUser, right, deadlineSlot });
multisig.verifyUpdateDelegator(upgradeInfo);
this.delegator.set(newUser);
this.emitEvent("updateDelegator", new UpdateUserEvent(newUser));
}
/**
* Get protocol address
* @returns address of the protocol
*/
async getProtocol() {
const protocol = this.protocol.getAndRequireEquals();
return protocol;
}
/**
* Get delegator address
* @returns address of the delegator
*/
async getDelegator() {
const delegator = this.delegator.getAndRequireEquals();
return delegator;
}
/**
* Get approved signer
* @returns root of approved signer
*/
async getApprovedSigner() {
const approvedSigner = this.approvedSigner.getAndRequireEquals();
return approvedSigner;
}
/**
* Get pool verification key
* @returns the verification key of the pool contract
*/
async getPoolVK() {
return this.vkPool;
}
/**
* Get pool token holder verification key
* @returns the verification key of the pool token holder contract
*/
async getPoolTokenHolderVK() {
return this.vkPoolTokenHolder;
}
/**
* Method use by token allowance but it's not permissible to use it
* @param forest account forest to update
*/
async approveBase(forest) {
forest.isEmpty().assertTrue("You can't approve any token operation");
}
/**
* Create a new mina/token pool
* @param newAccount address of the new pool
* @param token token 1 for the mina pool
* @param signer who sign the argument
* @param signature who proves you can deploy this pool (only approved signer can deploy a pool)
* @param path merkle witness to check if signer is in the approved list
* @param right right of the signer
*/
async createPool(newAccount, token, signer, signature, path, right) {
token.isEmpty().assertFalse("Token is empty");
await this.createAccounts(newAccount, token, PublicKey.empty(), token, signer, signature, path, right, false);
}
/**
* Create a new token/token pool
* @param newAccount address of the new pool
* @param token 0 of the pool
* @param token 1 of the pool
* @param signer who sign the argument
* @param signature who proves you can deploy this pool (only approved signer can deploy a pool)
* @param path merkle witness to check if signer is in the approved list
* @param right right of the signer
*/
async createPoolToken(newAccount, token0, token1, signer, signature, path, right) {
token0.x.assertLessThan(token1.x, "Token 0 need to be lesser than token 1");
// create an address with the 2 public key as pool id
const fields = token0.toFields().concat(token1.toFields());
const hash = Poseidon.hashToGroup(fields);
const publicKey = PublicKey.fromGroup(hash);
publicKey.isEmpty().assertFalse("publicKey is empty");
await this.createAccounts(newAccount, publicKey, token0, token1, signer, signature, path, right, true);
}
async createAccounts(newAccount, token, token0, token1, signer, signature, path, right, isTokenPool) {
const tokenAccount = AccountUpdate.create(token, this.deriveTokenId());
// if the balance is not zero, so a pool already exist for this token
tokenAccount.account.balance.requireEquals(UInt64.zero);
// verify the signer has right to create the pool
signer.equals(PublicKey.empty()).assertFalse("Empty signer");
const signerHash = Poseidon.hash(signer.toFields());
const approvedSignerRoot = this.approvedSigner.getAndRequireEquals();
hasRight(right, deployPoolRight).assertTrue("Insufficient right to deploy a pool");
const [root, key] = path.computeRootAndKey(Poseidon.hash(right.toFields()));
root.assertEquals(approvedSignerRoot, "Invalid signer merkle root");
key.assertEquals(signerHash, "Invalid signer");
signature.verify(signer, newAccount.toFields()).assertTrue("Invalid signature");
// create a pool as this new address
const poolAccount = AccountUpdate.createSigned(newAccount);
// Require this account didn't already exist
poolAccount.account.isNew.requireEquals(Bool(true));
// set pool account vk and permission
poolAccount.body.update.verificationKey = { isSome: Bool(true), value: this.vkPool };
poolAccount.body.update.permissions = {
isSome: Bool(true),
value: {
...Permissions.default(),
// only proof to prevent signature owner to steal liquidity
access: Permissions.proof(),
setVerificationKey: Permissions.VerificationKey.proofDuringCurrentVersion(),
send: Permissions.proof(),
setDelegate: Permissions.proof(),
setPermissions: Permissions.impossible()
}
};
// set poolAccount initial state
const appState = this.createState(token0, token1);
poolAccount.body.update.appState = appState;
// Liquidity token default name
poolAccount.account.tokenSymbol.set("LUM");
// create a token holder as this new address
if (isTokenPool) {
// only pool token need an account at token 0 address
await this.createPoolHolderAccount(newAccount, token0, appState);
}
await this.createPoolHolderAccount(newAccount, token1, appState);
// create a liquidity token holder as this new address
const tokenId = TokenId.derive(newAccount);
const liquidityAccount = AccountUpdate.createSigned(newAccount, tokenId);
// Require this account didn't already exist
liquidityAccount.account.isNew.requireEquals(Bool(true));
liquidityAccount.body.update.permissions = {
isSome: Bool(true),
value: {
...Permissions.default(),
setVerificationKey: Permissions.VerificationKey.impossibleDuringCurrentVersion(),
// This is necessary in order to allow burn circulation supply without signature
send: Permissions.none(),
setPermissions: Permissions.impossible()
}
};
// we mint one token to check if this pool exist
this.internal.mint({ address: tokenAccount, amount: UInt64.one });
await poolAccount.approve(liquidityAccount);
const sender = this.sender.getAndRequireSignature();
this.emitEvent("poolAdded", new PoolCreationEvent({ sender, signer, poolAddress: newAccount, token0Address: token0, token1Address: token1 }));
}
createState(token0, token1) {
const token0Fields = token0.toFields();
const token1Fields = token1.toFields();
const poolFactory = this.address.toFields();
const protocol = this.protocol.getAndRequireEquals();
const protocolFields = protocol.toFields();
return [
{ isSome: Bool(true), value: token0Fields[0] },
{ isSome: Bool(true), value: token0Fields[1] },
{ isSome: Bool(true), value: token1Fields[0] },
{ isSome: Bool(true), value: token1Fields[1] },
{ isSome: Bool(true), value: poolFactory[0] },
{ isSome: Bool(true), value: poolFactory[1] },
{ isSome: Bool(true), value: protocolFields[0] },
{ isSome: Bool(true), value: protocolFields[1] }
];
}
async createPoolHolderAccount(newAccount, token, appState) {
const fungibleToken = new FungibleToken(token);
const poolHolderAccount = AccountUpdate.createSigned(newAccount, fungibleToken.deriveTokenId());
// Require this account didn't already exist
poolHolderAccount.account.isNew.requireEquals(Bool(true));
// set pool token holder account vk and permission
poolHolderAccount.body.update.verificationKey = { isSome: Bool(true), value: this.vkPoolTokenHolder };
poolHolderAccount.body.update.permissions = {
isSome: Bool(true),
value: {
...Permissions.default(),
setVerificationKey: Permissions.VerificationKey.proofDuringCurrentVersion(),
send: Permissions.proof(),
setPermissions: Permissions.impossible()
}
};
poolHolderAccount.body.update.appState = appState;
await fungibleToken.approveAccountUpdate(poolHolderAccount);
return poolHolderAccount;
}
}
__decorate([
state(Field),
__metadata("design:type", Object)
], PoolFactory.prototype, "approvedSigner", void 0);
__decorate([
state(PublicKey),
__metadata("design:type", Object)
], PoolFactory.prototype, "protocol", void 0);
__decorate([
state(PublicKey),
__metadata("design:type", Object)
], PoolFactory.prototype, "delegator", void 0);
__decorate([
method,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Multisig, VerificationKey]),
__metadata("design:returntype", Promise)
], PoolFactory.prototype, "updateVerificationKey", null);
__decorate([
method,
__metadata("design:type", Function),
__metadata("design:paramtypes", [MultisigSigner, Field]),
__metadata("design:returntype", Promise)
], PoolFactory.prototype, "updateApprovedSigner", null);
__decorate([
method,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Multisig, PublicKey]),
__metadata("design:returntype", Promise)
], PoolFactory.prototype, "setNewProtocol", null);
__decorate([
method,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Multisig, PublicKey]),
__metadata("design:returntype", Promise)
], PoolFactory.prototype, "setNewDelegator", null);
__decorate([
method.returns(PublicKey),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], PoolFactory.prototype, "getProtocol", null);
__decorate([
method.returns(PublicKey),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], PoolFactory.prototype, "getDelegator", null);
__decorate([
method.returns(Field),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], PoolFactory.prototype, "getApprovedSigner", null);
__decorate([
method.returns(VerificationKey),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], PoolFactory.prototype, "getPoolVK", null);
__decorate([
method.returns(VerificationKey),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], PoolFactory.prototype, "getPoolTokenHolderVK", null);
__decorate([
method,
__metadata("design:type", Function),
__metadata("design:paramtypes", [AccountUpdateForest]),
__metadata("design:returntype", Promise)
], PoolFactory.prototype, "approveBase", null);
__decorate([
method,
__metadata("design:type", Function),
__metadata("design:paramtypes", [PublicKey,
PublicKey,
PublicKey,
Signature,
MerkleMapWitness,
Field]),
__metadata("design:returntype", Promise)
], PoolFactory.prototype, "createPool", null);
__decorate([
method,
__metadata("design:type", Function),
__metadata("design:paramtypes", [PublicKey,
PublicKey,
PublicKey,
PublicKey,
Signature,
MerkleMapWitness,
Field]),
__metadata("design:returntype", Promise)
], PoolFactory.prototype, "createPoolToken", null);
/**
* Function to multiply one Uint64 by another and divide the result,
* We check for overflow on the final result to avoid a premature overflow error.
* @param a The multiplicand
* @param b The multiplier
* @param denominator The divisor
* @returns the quotient and the remainder
*/
function mulDivMod(a, b, denominator) {
const x = a.value.mul(b.value);
let y_ = denominator.value;
if (x.isConstant() && y_.isConstant()) {
const xn = x.toBigInt();
const yn = y_.toBigInt();
const q = xn / yn;
const r = xn - q * yn;
return {
quotient: new UInt64(q),
rest: new UInt64(r)
};
}
y_ = y_.seal();
const q = Provable.witness(UInt64, () => {
const result = new Field(x.toBigInt() / y_.toBigInt());
return UInt64.Unsafe.fromField(result);
});
const r = x.sub(q.value.mul(y_)).seal();
Gadgets.rangeCheckN(UInt64.NUM_BITS, r);
const r_ = UInt64.Unsafe.fromField(new Field(r.value));
r_.assertLessThan(UInt64.Unsafe.fromField(new Field(y_.value)));
return { quotient: q, rest: r_ };
}
/**
* Function to multiply one Uint64 by another and divide the result,
* We check for overflow on the final result to avoid a premature overflow error.
* @param a The multiplicand
* @param b The multiplier
* @param denominator The divisor
* @returns The 64-bit result
*/
function mulDiv(a, b, denominator) {
return mulDivMod(a, b, denominator).quotient;
}
/**
* Check if token is in order and don't be empty
* @param pool pool data
* @param isMinaPool is mina/token pool
* @returns true if correct
*/
function checkToken(pool, isMinaPool) {
const token0 = pool.token0.getAndRequireEquals();
const token1 = pool.token1.getAndRequireEquals();
// token 0 need to be empty on mina pool
token0.equals(PublicKey.empty()).assertEquals(isMinaPool, isMinaPool ? "Not a mina pool" : "Invalid token 0 address");
token1.equals(PublicKey.empty()).assertFalse("Invalid token 1 address");
return [token0, token1];
}
/**
* Event emitted when a swap is validated
*/
class SwapEvent extends Struct({
sender: PublicKey,
amountIn: UInt64,
amountOut: UInt64
}) {
constructor(value) {
super(value);
}
}
/**
* Event emitted when the contract receive mina in case of swap
*/
class ReceiveMinaEvent extends Struct({
sender: PublicKey,
amountMinaIn: UInt64
}) {
constructor(value) {
super(value);
}
}
/**
* Event emitted when an user add liquidity
*/
class AddLiquidityEvent extends Struct({
sender: PublicKey,
amountToken0In: UInt64,
amountToken1In: UInt64,
amountLiquidityOut: UInt64
}) {
constructor(value) {
super(value);
}
}
/**
* Event emitted when liquidity balance changed
*/
class BalanceEvent extends Struct({
address: PublicKey,
amount: Int64
}) {
constructor(value) {
super(value);
}
}
/**
* Event emitted when liquidity was burned
*/
class BurnLiqudityEvent extends Struct({
sender: PublicKey,
amountMinaOut: UInt64,
amountLiquidity: UInt64
}) {
constructor(value) {
super(value);
}
}
/**
* Main Pool contract for Lumina dex
*/
class Pool extends TokenContract {
constructor() {
super(...arguments);
/**
* Address of first token in the pool (ordered by address)
* PublicKey.empty() in case of native mina
*/
this.token0 = State();
/**
* Address of second token in the pool
* Can't be empty
*/
this.token1 = State();
/**
* Pool factory contract address
*/
this.poolFactory = State();
/**
* Protocol address stored in the pool to not exceed account update limit on swap
*/
this.protocol = State();
/**
* List of pool events
*/
this.events = {
swap: SwapEvent,
addLiquidity: AddLiquidityEvent,
balanceChange: BalanceEvent,
updateDelegator: UpdateUserEvent,
updateProtocol: UpdateUserEvent,
upgrade: UpdateVerificationKeyEvent,
burnLiquidity: BurnLiqudityEvent,
receiveMina: ReceiveMinaEvent
};
}
/**
* Frontend max fee, 0.10%
*/
static { this.maxFee = UInt64.from(10); }
/**
* Minimun liquidity in the pool, 1000
*/
static { this.minimumLiquidity = UInt64.from(1000); }
/**
* We declare the factory contract as a static property so that it can be easily replaced in case of factory upgrade
*/
static { this.FactoryContract = PoolFactory; }
/**
* This method can't be called directly, deploy new pool from pool factory instead
*/
async deploy() {
await super.deploy();
Bool(false).assertTrue("You can't directly deploy a pool");
}
/**
* Upgrade to a new version, necessary due to o1js breaking verification key compatibility between versions
*/
async updateVerificationKey() {
const factoryAddress = this.poolFactory.getAndRequireEquals();
const factory = new Pool.FactoryContract(factoryAddress);
const vk = await factory.getPoolVK();
this.account.verificationKey.set(vk);
this.emitEvent("upgrade", new UpdateVerificationKeyEvent(vk.hash));
}
/**
* Update the delegator account address from pool factory
*/
async setDelegator() {
const poolFactoryAddress = this.poolFactory.getAndRequireEquals();
const poolFactory = new Pool.FactoryContract(poolFactoryAddress);
const delegator = await poolFactory.getDelegator();
const currentDelegator = this.account.delegate.getAndRequireEquals();
Provable.asProver(() => {
currentDelegator.equals(delegator).assertFalse("Delegator already defined");
});
this.account.delegate.set(delegator);
this.emitEvent("updateDelegator", new UpdateUserEvent(delegator));
}
/**
* Update the protocol account address from pool factory
*/
async setProtocol() {
const protocol = await this.getProtocolAddress();
const currentProtocol = this.protocol.getAndRequireEquals();
Provable.asProver(() => {
currentProtocol.equals(protocol).assertFalse("Protocol already defined");
});
this.protocol.set(protocol);
this.emitEvent("updateProtocol", new UpdateUserEvent(protocol));
}
/** Approve `AccountUpdate`s that have been created outside of the token contract.
*
* @argument {AccountUpdateForest} updates - The `AccountUpdate`s to approve. Note that the forest size is limited by the base token contract, @see TokenContract.MAX_ACCOUNT_UPDATES The current limit is 9.
*/
async approveBase(updates) {
let totalBalance = Int64.from(0);
this.forEachUpdate(updates, (update, usesToken) => {
// Make sure that the account permissions are not changed
this.checkPermissionsUpdate(update);
this.emitEventIf(usesToken, "balanceChange", new BalanceEvent({ address: update.publicKey, amount: update.balanceChange }));
// Don't allow transfers to/from the account that's tracking circulation
update.publicKey.equals(this.address).and(usesToken).assertFalse("Can't transfer to/from the circulation account");
totalBalance = Provable.if(usesToken, totalBalance.add(update.balanceChange), totalBalance);
totalBalance.isPositive().assertFalse("Flash-minting or unbalanced transaction detected");
});
totalBalance.assertEquals(Int64.zero, "Unbalanced transaction");
}
checkPermissionsUpdate(update) {
const permissions = update.update.permissions;
const { access, receive } = permissions.value;
const accessIsNone = Provable.equal(Types.AuthRequired, access, Permissions.none());
const receiveIsNone = Provable.equal(Types.AuthRequired, receive, Permissions.none());
const updateAllowed = accessIsNone.and(receiveIsNone);
assert(updateAllowed.or(permissions.isSome.not()), "Can't change permissions for access or receive on token accounts");
}
/**
* Transfer liquidity from an account to another
* @param from account from
* @param to account to
* @param amount amount to tranfer
*/
async transfer(from, to, amount) {
from.equals(this.address).assertFalse("Can't transfer to/from the circulation account");
to.equals(this.address).assertFalse("Can't transfer to/from the circulation account");
this.internal.send({ from, to, amount });
}
/**
* Call it on the first time liquidity is supplied to the mina/token pool
* @param amountMina mina to add to the pool
* @param amountToken token to add to the pool
* @returns liquity amount minted
*/
async supplyFirstLiquidities(amountMina, amountToken) {
const liquidityUser = await this.supply(amountMina, amountToken, UInt64.zero, UInt64.zero, UInt64.zero, true, true);
return liquidityUser;
}
/**
* Supply liquidity to the mina/token pool if it's not the first time
* The reserves max and supply min permit concurrent call, use slippage mechanism to calculate it
* @param amountMina mina to add to the pool
* @param amountToken token to add to the pool
* @param reserveMinaMax reserve max of mina in the pool
* @param reserveTokenMax reserve max