@lumina-dex/contracts
Version:
Contracts for Lumina DEX.
182 lines (155 loc) • 5.16 kB
text/typescript
import {
AccountUpdate,
AccountUpdateForest,
Bool,
DeployArgs,
Field,
MerkleWitness,
method,
Permissions,
Poseidon,
PublicKey,
State,
state,
Struct,
TokenContract,
UInt64,
VerificationKey
} from "o1js"
import { UpdateVerificationKeyEvent } from "../indexpool"
import { UpdateInitEvent } from "./Farm"
export interface FarmRewardDeployProps extends Exclude<DeployArgs, undefined> {
merkleRoot: Field
token: PublicKey
owner: PublicKey
}
export class MintEvent extends Struct({
sender: PublicKey
}) {
constructor(value: {
sender: PublicKey
}) {
super(value)
}
}
export class ClaimEvent extends Struct({
user: PublicKey,
amount: UInt64
}) {
constructor(value: {
user: PublicKey
amount: UInt64
}) {
super(value)
}
}
/**
* support 2^32 different claimer (easily adjustable)
*/
export const claimerNumber = 32
export class FarmMerkleWitness extends MerkleWitness(claimerNumber) {}
/**
* we can't upgrade the contract before 1 day
*/
export const minTimeUnlockFarmReward = UInt64.from(86_400_000)
/**
* Farm reward contract
*/
export class FarmReward extends TokenContract {
owner = State<PublicKey>()
token = State<PublicKey>()
merkleRoot = State<Field>()
timeUnlock = State<UInt64>()
events = {
upgrade: UpdateVerificationKeyEvent,
upgradeInited: UpdateInitEvent,
claim: ClaimEvent,
mint: MintEvent
}
async deploy(args: FarmRewardDeployProps) {
this.account.isNew.requireEquals(Bool(true))
await super.deploy()
args.merkleRoot.equals(Field(0)).assertFalse("Merkle root is empty")
args.owner.isEmpty().assertFalse("Owner is empty")
this.owner.set(args.owner)
this.merkleRoot.set(args.merkleRoot)
this.token.set(args.token)
const permissions = Permissions.default()
permissions.access = Permissions.proof()
permissions.send = Permissions.proof()
permissions.setPermissions = Permissions.impossible()
permissions.setVerificationKey = Permissions.VerificationKey.proofDuringCurrentVersion()
this.account.permissions.set(permissions)
}
/** 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: AccountUpdateForest): Promise<void> {
updates.isEmpty().assertTrue("You can't manage the token")
}
/**
* Init Upgrade to a new version
* @param vk new verification key
*/
async initUpdate(startTime: UInt64) {
const owner = await this.owner.getAndRequireEquals()
// only owner can init a update
AccountUpdate.createSigned(owner)
this.network.timestamp.requireBetween(UInt64.zero, startTime)
// owner need to wait minimum 1 day before update this contract
const timeUnlock = startTime.add(minTimeUnlockFarmReward)
this.timeUnlock.set(timeUnlock)
this.emitEvent("upgradeInited", new UpdateInitEvent({ owner }))
}
/**
* Upgrade to a new version
* @param vk new verification key
*/
async updateVerificationKey(vk: VerificationKey) {
const timeUnlock = this.timeUnlock.getAndRequireEquals()
timeUnlock.assertGreaterThan(UInt64.zero, "Time unlock is not defined")
this.network.timestamp.requireBetween(timeUnlock, UInt64.MAXINT())
const owner = await this.owner.getAndRequireEquals()
// only owner can update a pool
AccountUpdate.createSigned(owner)
this.account.verificationKey.set(vk)
this.emitEvent("upgrade", new UpdateVerificationKeyEvent(vk.hash))
}
async claimReward(amount: UInt64, path: FarmMerkleWitness) {
const farmReward = new FarmReward(this.address)
const sender = this.sender.getAndRequireSignature()
const senderBalance = AccountUpdate.create(sender, farmReward.deriveTokenId())
senderBalance.account.balance.requireEquals(UInt64.zero)
const fieldSender = sender.toFields()
const hash = Poseidon.hash([fieldSender[0], fieldSender[1], amount.value])
const root = this.merkleRoot.getAndRequireEquals()
path.calculateRoot(hash).assertEquals(root, "Invalid request")
this.send({ to: sender, amount: amount })
// to prevent double withdraw we mint one token once a user withdraw
this.internal.mint({ address: sender, amount: UInt64.one })
this.emitEvent("mint", new MintEvent({ sender }))
this.emitEvent("claim", new ClaimEvent({ user: sender, amount }))
}
/**
* Don't call this method directly
*/
async mint(sender: PublicKey) {
const caller = this.sender.getAndRequireSignature()
caller.assertEquals(sender)
// check user never withdraw
const senderBalance = AccountUpdate.create(sender, this.deriveTokenId())
senderBalance.account.balance.requireEquals(UInt64.zero)
this.internal.mint({ address: senderBalance, amount: UInt64.one })
this.emitEvent("mint", new MintEvent({ sender }))
}
}