@lumina-dex/contracts
Version:
Contracts for Lumina DEX.
1,103 lines (1,091 loc) • 101 kB
JavaScript
import { FungibleToken } from 'mina-fungible-token';
export * from 'mina-fungible-token';
import { UInt64, Provable, Field, Gadgets, Struct, Bool, Poseidon, UInt32, PublicKey, MerkleMapWitness, Signature, Int64, TokenContract, State, Types, Permissions, assert, AccountUpdate, TokenId, state, method, VerificationKey, AccountUpdateForest, MerkleMap, SmartContract, Mina, 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;
};
/**
* 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;
}
/**
* Signature right to update pool information
*/
class SignatureRight extends Struct({
deployPool: Bool,
uppdatePool: Bool,
updateSigner: Bool,
updateProtocol: Bool,
updateDelegator: Bool,
updateFactory: Bool
}) {
constructor(deployPool, uppdatePool, updateSigner, updateProtocol, updateDelegator, updateFactory) {
super({
deployPool,
uppdatePool,
updateSigner,
updateProtocol,
updateDelegator,
updateFactory
});
}
toFields() {
return this.deployPool.toFields().concat(this.uppdatePool.toFields().concat(this.updateSigner.toFields().concat(this.updateProtocol.toFields().concat(this.updateDelegator.toFields().concat(this.updateFactory.toFields())))));
}
static canUpdatePool() {
return new SignatureRight(Bool(false), Bool(true), Bool(false), Bool(false), Bool(false), Bool(false));
}
static canUpdateDelegator() {
return new SignatureRight(Bool(false), Bool(false), Bool(false), Bool(false), Bool(true), Bool(false));
}
static canUpdateProtocol() {
return new SignatureRight(Bool(false), Bool(false), Bool(false), Bool(true), Bool(false), Bool(false));
}
static canUpdateSigner() {
return new SignatureRight(Bool(false), Bool(false), Bool(true), Bool(false), Bool(false), Bool(false));
}
static canUpdateFactory() {
return new SignatureRight(Bool(false), Bool(false), Bool(false), Bool(false), Bool(false), Bool(true));
}
static canDeployPool() {
return new SignatureRight(Bool(true), Bool(false), Bool(false), Bool(false), Bool(false), Bool(false));
}
/**
* Check if the user right match the necessary right
* @param right user right
*/
hasRight(right) {
const newRight = new SignatureRight(right.deployPool.and(this.deployPool), right.uppdatePool.and(this.uppdatePool), right.updateSigner.and(this.updateSigner), right.updateProtocol.and(this.updateProtocol), right.updateDelegator.and(this.updateDelegator), right.updateFactory.and(this.updateFactory));
return newRight.hash().equals(right.hash());
}
/**
* hash store in the signer merkle map
*/
hash() {
return Poseidon.hash(this.toFields());
}
}
/**
* 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 this.newVkHash.toFields().concat(this.deadlineSlot.toFields());
}
hash() {
return Poseidon.hash(this.toFields());
}
}
/**
* Information needed to update the pool verification key
*/
class UpgradeInfo extends Struct({
// contract to upgrade
contractAddress: PublicKey,
// subaccount to upgrade
tokenId: Field,
// 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 this.contractAddress.toFields().concat(this.tokenId.toFields().concat(this.newVkHash.toFields().concat(this.deadlineSlot.toFields())));
}
hash() {
return Poseidon.hash(this.toFields());
}
}
/**
* Information needed to update the delegator/protocol
*/
class UpdateAccountInfo extends Struct({
// old account address
oldUser: PublicKey,
// new account address
newUser: PublicKey,
// 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 this.oldUser.toFields().concat(this.newUser.toFields().concat(this.deadlineSlot.toFields()));
}
hash() {
return Poseidon.hash(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 this.oldRoot.toFields().concat(this.newRoot.toFields().concat(this.deadlineSlot.toFields()));
}
hash() {
return Poseidon.hash(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 this.messageHash.toFields();
}
}
/**
* 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: SignatureRight
}) {
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 = this.right.hash();
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, 3)
}) {
constructor(value) {
super(value);
}
/**
* Check if the signature match the current user and data subbit
* @param data needed to verify the signature
*/
verifyUpdateFactory(updateInfo) {
const right = SignatureRight.canUpdateFactory();
verifySignature(this.signatures, updateInfo.deadlineSlot, this.info, this.info.approvedUpgrader, updateInfo.toFields(), right);
}
/**
* Check if the signature match the current user and data subbit
* @param data needed to verify the signature
*/
verifyUpdatePool(upgradeInfo) {
const right = SignatureRight.canUpdatePool();
verifySignature(this.signatures, upgradeInfo.deadlineSlot, this.info, this.info.approvedUpgrader, upgradeInfo.toFields(), right);
}
/**
* Check if the signature match the current user and data subbit
* @param data needed to verify the signature
*/
verifyUpdateDelegator(updateInfo) {
const right = SignatureRight.canUpdateDelegator();
verifySignature(this.signatures, updateInfo.deadlineSlot, this.info, this.info.approvedUpgrader, updateInfo.toFields(), right);
}
/**
* Check if the signature match the current user and data subbit
* @param data needed to verify the signature
*/
verifyUpdateProtocol(updateInfo) {
const right = SignatureRight.canUpdateProtocol();
verifySignature(this.signatures, updateInfo.deadlineSlot, 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, 3),
newSignatures: Provable.Array(SignatureInfo, 3)
}) {
constructor(value) {
super(value);
}
/**
* Check if the signature match the current user and data subbit
* @param data needed to verify the signature
*/
verifyUpdateSigner(upgradeInfo) {
const right = SignatureRight.canUpdateSigner();
upgradeInfo.oldRoot.equals(upgradeInfo.newRoot).assertFalse("Can't reuse same merkle");
verifySignature(this.signatures, upgradeInfo.deadlineSlot, this.info, this.info.approvedUpgrader, upgradeInfo.toFields(), right);
verifySignature(this.newSignatures, upgradeInfo.deadlineSlot, this.info, upgradeInfo.newRoot, upgradeInfo.toFields(), right);
}
}
/**
* Check if the 3 signatures are valid
*/
function verifySignature(signatures, deadlineSlot, info, root, data, right) {
info.deadlineSlot.equals(deadlineSlot).assertTrue("Deadline doesn't match");
// check the signature come from 3 different users
signatures[0].user.equals(signatures[1].user).assertFalse("Can't include same signer");
signatures[1].user.equals(signatures[2].user).assertFalse("Can't include same signer");
signatures[0].user.equals(signatures[2].user).assertFalse("Can't include same signer");
const hash = Poseidon.hash(data);
for (let index = 0; index < signatures.length; index++) {
const element = signatures[index];
// check if he can upgrade a pool
element.right.hasRight(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");
}
}
/**
* 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); }
/**
* 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
* @param multisig multisig data
* @param vk new verification key
*/
async updateVerificationKey(multisig, vk) {
const factoryAddress = this.poolFactory.getAndRequireEquals();
const factory = new PoolFactory(factoryAddress);
const merkle = await factory.getApprovedSigner();
multisig.info.approvedUpgrader.equals(merkle).assertTrue("Incorrect signer list");
const deadlineSlot = multisig.info.deadlineSlot;
// we can update only before the deadline to prevent signature reuse
this.network.globalSlotSinceGenesis.requireBetween(UInt32.zero, deadlineSlot);
const upgradeInfo = new UpgradeInfo({
contractAddress: this.address,
tokenId: this.tokenId,
newVkHash: vk.hash,
deadlineSlot
});
multisig.verifyUpdatePool(upgradeInfo);
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 PoolFactory(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 of token in the pool
* @param supplyMin minimun liquidity in the pool
* @returns liquity amount minted
*/
async supplyLiquidity(amountMina, amountToken, reserveMinaMax, reserveTokenMax, supplyMin) {
const liquidityUser = await this.supply(amountMina, amountToken, reserveMinaMax, reserveTokenMax, supplyMin, true, false);
return liquidityUser;
}
/**
* Method to call on the first time liquidity is supplied to the token pool
* @param amountToken0 amount of token 0 to add to the pool
* @param amountToken1 amount of token 1 to add to the pool
* @returns liquity amount minted
*/
async supplyFirstLiquiditiesToken(amountToken0, amountToken1) {
const liquidityUser = await this.supply(amountToken0, amountToken1, UInt64.zero, UInt64.zero, UInt64.zero, false, true);
return liquidityUser;
}
/**
* Supply liquidity to the token/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 amountToken0 amount of token 0 to add to the pool
* @param amountToken1 amount of token 1 to add to the pool
* @param reserveMinaMax reserve max of mina in the pool
* @param reserveTokenMax reserve max of token in the pool
* @param supplyMin minimun liquidity in the pool
* @returns liquity amount minted
*/
async supplyLiquidityToken(amountToken0, amountToken1, reserveToken0Max, reserveToken1Max, supplyMin) {
const liquidityUser = await this.supply(amountToken0, amountToken1, reserveToken0Max, reserveToken1Max, supplyMin, false, false);
return liquidityUser;
}
/**
* Swap token to mina
* @param frontend address who collect the frontend fees
* @param taxFeeFrontend fees applied by the frontend
* @param amountTokenIn amount of token to swap
* @param amountMinaOutMin minimum mina to received
* @param balanceInMax minimum balance of token in the pool
* @param balanceOutMin maximum balance of mina in the pool
*/
async swapFromTokenToMina(frontend, taxFeeFrontend, amountTokenIn, amountMinaOutMin, balanceInMax, balanceOutMin) {
amountTokenIn.assertGreaterThan(UInt64.zero, "Amount in can't be zero");
balanceOutMin.assertGreaterThan(UInt64.zero, "Balance min can't be zero");
balanceInMax.assertGreaterThan(UInt64.zero, "Balance max can't be zero");
amountMinaOutMin.assertGreaterThan(UInt64.zero, "Amount out can't be zero");
amountMinaOutMin.assertLessThan(balanceOutMin, "Amount out exceeds reserves");
taxFeeFrontend.assertLessThanOrEqual(Pool.maxFee, "Frontend fee exceed max fees");
// token 0 need to be empty on mina pool
const [, token1] = checkToken(this, true);
const tokenContract = new FungibleToken(token1);
const tokenAccount = AccountUpdate.create(this.address, tokenContract.deriveTokenId());
tokenAccount.account.balance.requireBetween(UInt64.one, balanceInMax);
this.account.balance.requireBetween(balanceOutMin, UInt64.MAXINT());
const { feeLP, feeFrontend, feeProtocol, amountOut } = Pool.getAmountOut(taxFeeFrontend, amountTokenIn, balanceInMax, balanceOutMin);
amountOut.assertGreaterThanOrEqual(amountMinaOutMin, "Insufficient amount out");
// send token to the pool
await this.sendTokenAccount(tokenAccount, token1, amountTokenIn);
// send mina to user
const sender = this.sender.getUnconstrained();
await this.send({ to: sender, amount: amountOut });
// send mina to frontend (if not empty)
const frontendReceiver = Provable.if(frontend.equals(PublicKey.empty()), this.address, frontend);
await this.send({ to: frontendReceiver, amount: feeFrontend });
// send mina to protocol
const protocol = this.protocol.getAndRequireEquals();
const protocolReceiver = Provable.if(protocol.equals(PublicKey.empty()), this.address, protocol);
await this.send({ to: protocolReceiver, amount: feeProtocol });
this.emitEvent("swap", new SwapEvent({ sender, amountIn: amountTokenIn, amountOut }));
}
/**
* Don't call this method directly, use pool token holder or you will just lost mina
* @param sender use in the previous method
* @param amountMinaIn mina amount in
* @param balanceInMax actual reserve max in
*/
async swapFromMinaToToken(sender, protocol, amountMinaIn, balanceInMax) {
amountMinaIn.assertGreaterThan(UInt64.zero, "Amount in can't be zero");
balanceInMax.assertGreaterThan(UInt64.zero, "Balance max can't be zero");
checkToken(this, true);
// check if the protocol address is correct
this.protocol.requireEquals(protocol);
this.account.balance.requireBetween(UInt64.one, balanceInMax);
const methodSender = this.sender.getUnconstrained();
methodSender.assertEquals(sender);
const senderSigned = AccountUpdate.createSigned(sender);
await senderSigned.send({ to: this.self, amount: amountMinaIn });
this.emitEvent("receiveMina", new ReceiveMinaEvent({ sender, amountMinaIn }));
}
/**
* Don't call this method directly, use withdrawLiquidity from PoolTokenHolder
*/
async withdrawLiquidity(sender, liquidityAmount, amountMinaMin, reserveMinaMin, supplyMax) {
reserveMinaMin.assertGreaterThan(UInt64.zero, "Reserve mina min can't be zero");
amountMinaMin.assertGreaterThan(UInt64.zero, "Amount mina out can't be zero");
this.account.balance.requireBetween(reserveMinaMin, UInt64.MAXINT());
const methodSender = this.sender.getUnconstrained();
methodSender.assertEquals(sender);
await this.burnLiquidity(sender, liquidityAmount, supplyMax, true);
const amountMina = mulDiv(liquidityAmount, reserveMinaMin, supplyMax);
amountMina.assertGreaterThanOrEqual(amountMinaMin, "Insufficient amount mina out");
// send mina to user
const receiverAccount = AccountUpdate.createSigned(sender);
await this.send({ to: receiverAccount, amount: amountMina });
this.emitEvent("burnLiquidity", new BurnLiqudityEvent({ sender, amountMinaOut: amountMina, amountLiquidity: liquidityAmount }));
return amountMina;
}
/**
* Don't call this method directly, use withdrawLiquidityToken from PoolTokenHolder
*/
async burnLiquidityToken(sender, liquidityAmount, supplyMax) {
const methodSender = this.sender.getUnconstrained();
methodSender.assertEquals(sender);
await this.burnLiquidity(sender, liquidityAmount, supplyMax, false);
this.emitEvent("burnLiquidity", new BurnLiqudityEvent({ sender, amountMinaOut: UInt64.zero, amountLiquidity: liquidityAmount }));
}
async burnLiquidity(sender, liquidityAmount, supplyMax, isMinaPool) {
liquidityAmount.assertGreaterThan(UInt64.zero, "Liquidity amount can't be zero");
supplyMax.assertGreaterThan(UInt64.zero, "Supply max can't be zero");
// token 0 need to be empty on mina pool
checkToken(this, isMinaPool);
sender.equals(this.address).assertFalse("Can't transfer to/from the pool account");
const liquidityAccount = AccountUpdate.create(this.address, this.deriveTokenId());
liquidityAccount.account.balance.requireBetween(UInt64.one, supplyMax);
// burn liquidity from user and current supply
liquidityAccount.balanceChange = Int64.fromUnsigned(liquidityAmount).neg();
await this.internal.burn({ address: sender, amount: liquidityAmount });
}
async supply(amountToken0, amountToken1, reserveToken0Max, reserveToken1Max, supplyMin, isMinaPool, isFirstSupply) {
const circulationUpdate = AccountUpdate.create(this.address, this.deriveTokenId());
if (!isFirstSupply) {
reserveToken1Max.assertGreaterThan(UInt64.zero, "Reserve token 1 max can't be zero");
reserveToken0Max.assertGreaterThan(UInt64.zero, "Reserve token 0 max can't be zero");
supplyMin.assertGreaterThan(UInt64.zero, "Supply min can't be zero");
}
amountToken0.assertGreaterThan(UInt64.zero, "Amount token 0 can't be zero");
amountToken1.assertGreaterThan(UInt64.zero, "Amount token 1 can't be zero");
const [token0, token1] = checkToken(this, isMinaPool);
let liquidityAmount = UInt64.zero;
let liquidityUser = UInt64.zero;
const sender = this.sender.getUnconstrained();
sender.equals(this.address).assertFalse("Can't transfer to/from the pool account");
const tokenId0 = TokenId.derive(token0);
const tokenId1 = TokenId.derive(token1);
const token0Account = isMinaPool ? this.self : AccountUpdate.create(this.address, tokenId0);
const token1Account = AccountUpdate.create(this.address, tokenId1);
if (isFirstSupply) {
const balanceLiquidity = circulationUpdate.account.balance.getAndRequireEquals();
// calculate liquidity token output simply as liquidityAmount = amountA + amountB
balanceLiquidity.equals(UInt64.zero).assertTrue("First liquidities already supplied");
liquidityAmount = amountToken0.add(amountToken1);
// on first mint remove minimal liquidity amount to prevent from inflation attack
liquidityUser = liquidityAmount.sub(Pool.minimumLiquidity);
}
else {
token0Account.account.balance.requireBetween(UInt64.one, reserveToken0Max);
token1Account.account.balance.requireBetween(UInt64.one, reserveToken1Max);
circulationUpdate.account.balance.requireBetween(supplyMin, UInt64.MAXINT());
// calculate liquidity token output simply as amountX * liquiditySupply / reserveX
const liquidityToken0 = mulDiv(amountToken0, supplyMin, reserveToken0Max);
const liquidityToken1 = mulDiv(amountToken1, supplyMin, reserveToken1Max);
// 0.1 % of error max between these 2 liquidities, prevent to send too much token or mina
const percentError = liquidityToken0.div(1000);
const liquidityMin = liquidityToken0.sub(percentError);
const liquidityMax = liquidityToken0.add(percentError);
liquidityMin.assertLessThanOrEqual(liquidityToken1, "Too much token 0 supplied");
liquidityMax.assertGreaterThanOrEqual(liquidityToken1, "Too much token 1 supplied");
liquidityAmount = Provable.if(liquidityToken0.lessThan(liquidityToken1), liquidityToken0, liquidityToken1);
liquidityAmount.assertGreaterThan(UInt64.zero, "Liquidity out can't be zero");
liquidityUser = liquidityAmount;
}
if (isMinaPool) {
// send mina to the pool
const senderUpdate = AccountUpdate.createSigned(sender);
senderUpdate.send({ to: this.self, amount: amountToken0 });
}
else {
// send token 0 to the pool
await this.sendTokenAccount(token0Account, token0, amountToken0);
}
// send token 1 to the pool
await this.sendTokenAccount(token1Account, token1, amountToken1);
this.internal.mint({ address: sender, amount: liquidityUser });
this.internal.mint({ address: circulationUpdate, amount: liquidityAmount });
this.emitEvent("addLiquidity", new AddLiquidityEvent({
sender,
amountToken0In: amountToken0,
amountToken1In: amountToken1,
amountLiquidityOut: liquidityUser
}));
return liquidityUser;
}
async sendTokenAccount(tokenAccount, tokenAddress, amount) {
const tokenContract = new FungibleToken(tokenAddress);
const sender = this.sender.getUnconstrained();
sender.equals(this.address).assertFalse("Can't transfer to/from the pool account");
// send token to the pool
const senderToken = AccountUpdate.createSigned(sender, tokenContract.deriveTokenId());
senderToken.send({ to: tokenAccount, amount: amount });
await tokenContract.approveAccountUpdates([senderToken, tokenAccount]);
}
async getProtocolAddress() {
const poolFactoryAddress = this.poolFactory.getAndRequireEquals();
const poolFactory = new PoolFactory(poolFactoryAddress);
return await poolFactory.getProtocol();
}
/**
* Calculate amount out on swap, use by pool and pool token holder contracts
* @param taxFeeFrontend fees applied by the frontend
* @param amountTokenIn amount of tokenIn to swap
* @param balanceInMax minimum balance of tokenIn in the pool
* @param balanceOutMin maximum balance of tokenOut in the pool
* @returns amount of token out
*/
static getAmountOut(taxFeeFrontend, amountTokenIn, balanceInMax, balanceOutMin) {
const amountOutBeforeFee = mulDiv(balanceOutMin, amountTokenIn, balanceInMax.add(amountTokenIn));
// 0.20% tax fee for liquidity provider directly on amount out
const feeLP = mulDiv(amountOutBeforeFee, UInt64.from(2), UInt64.from(1000));
// 0.15% fee max for the frontend
const feeFrontend = mulDiv(amountOutBeforeFee, taxFeeFrontend, UInt64.from(10000));
// 0.05% to the protocol
const feeProtocol = mulDiv(amountOutBeforeFee, UInt64.from(5), UInt64.from(10000));
const amountOut = amountOutBeforeFee.sub(feeLP).sub(feeFrontend).sub(feeProtocol);
return { feeLP, feeFrontend, feeProtocol, amountOut };
}
}
__decorate([
state(PublicKey),
__metadata("design:type", Object)
], Pool.prototype, "token0", void 0);
__decorate([
state(PublicKey),
__metadata("design:type", Object)
], Pool.prototype, "token1", void 0);
__decorate([
state(PublicKey),
__metadata("design:type", Object)
], Pool.prototype, "poolFactory", void 0);
__decorate([
state(PublicKey),
__metadata("design:type", Object)
], Pool.prototype, "protocol", void 0);
__decorate([
method,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Multisig, VerificationKey]),
__metadata("design:returntype", Promise)
], Pool.prototype, "updateVerificationKey", null);
__decorate([
method,
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], Pool.prototype, "setDelegator", null);
__decorate([
method,
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], Pool.prototype, "setProtocol", null);
__decorate([
method,
__metadata("design:type", Function),
__metadata("design:paramtypes", [AccountUpdateForest]),
__metadata("design:returntype", Promise)
], Pool.prototype, "approveBase", null);
__decorate([
method,
__metadata("design:type", Function),
__metadata("design:paramtypes", [PublicKey, PublicKey, UInt64]),
__metadata("design:returntype", Promise)
], Pool.prototype, "transfer", null);
__decorate([
method.returns(UInt64),
__metadata("design:type", Function),
__metadata("design:paramtypes", [UInt64, UInt64]),
__metadata("design:returntype", Promise)
], Pool.prototype, "supplyFirstLiquidities", null);
__decorate([
method.returns(UInt64),
__metadata("design:type", Function),
__metadata("design:paramtypes", [UInt64,
UInt64,
UInt64,
UInt64,
UInt64]),
__metadata("design:returntype", Promise)
], Pool.prototype, "supplyLiquidity", null);
__decorate([
method.returns(UInt64),
__metadata("design:type", Function),
__metadata("design:paramtypes", [UInt64, UInt64]),
__metadata("design:returntype", Promise)
], Pool.prototype, "supplyFirstLiquiditiesToken", null);
__decorate([
method.returns(UInt64),
__metadata("design:type", Function),
__metadata("design:paramtypes", [UInt64,
UInt64,
UInt64,
UInt64,
UInt64]),
__metadata("design:returntype", Promise)
], Pool.prototype, "supplyLiquidityToken", null);
__decorate([
method,
__metadata("design:type", Function),
__metadata("design:paramtypes", [PublicKey,
UInt64,
UInt64,
UInt64,
UInt64,
UInt64]),
__metadata("design:returntype", Promise)
], Pool.prototype, "swapFromTokenToMina", null);
__decorate([
method,
__metadata("design:type", Function),
__metadata("design:paramtypes", [PublicKey, PublicKey, UInt64, UInt64]),
__metadata("design:returntype", Promise)
], Pool.prototype, "swapFromMinaToToken", null);
__decorate([
method.returns(UInt64),
__metadata("design:type", Function),
__metadata("design:paramtypes", [PublicKey,
UInt64,
UInt64,
UInt64,
UInt64]),
__metadata("design:returntype", Promise)
], Pool.prototype, "withdrawLiquidity", null);
__decorate([
method,
__metadata("design:type", Function),
__metadata("design:paramtypes", [PublicKey, UInt64, UInt64]),
__metadata("design:returntype", Promise)
], Pool.prototype, "burnLiquidityToken", null);
/**
* Current verification key of pool contract
*/
const contractData = "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";
/**
* Current hash of verification key of pool contract
*/
const contractHash = Field(5594455694225844493828763509468165382923180297395536400245484306269052311024n);
/**
* Current verification key of pool token holder contract
*/
const contractHolderData = "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";
/**
* Current hash of verification key of pool token holder contract
*/
const contractHolderHash = Field(11549450361008775235242038734709709351447737775285423036140322613088171224061n);
/**
* 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
};
}
/**
* Method call when you deploy the pool factory contracts
* @param args default data stored in the contracts
*/
async deploy(args) {
await super.deploy(args);
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.multisigInfo.deadlineSlot);
const updateSignerData = new UpdateSignerData({
oldRoot: Field.empty(),
newRoot: args.approvedSigner,
deadlineSlot: args.multisigInfo.deadlineSlot
});
// we need 3 signatures to update signer, prevent to deadlock contract update
const right = SignatureRight.canUpdateSigner();
verifySignature(args.signatures, args.multisigInfo.deadlineSlot, args.multisigInfo, args.multisigInfo.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.app