fix: bridge apower helper v1 01 (#708)

* feat: swap-helper-bridged-v1-1 (#686)

* feat: add deps

* feat: flash-loan susdt-xusd (#687)

* Feat/flash loan susdt xusd (#688)

* feat: flash-loan susdt-xusd

* flashloan user added

* conflict fixed

* feat: launchpad v1-03 (#689)

* chore: ordinals-bluewheel (#690)

* feat: launchpad v1-03

* chore: ordinals-bluewheel

* feat: event-claim-helper

* feat: event claim helper (#692)



* TODO: test suite

* feat: event claim helper (#693)

* working

* working;

* TODO: test suite

* test working

* working

* feat: event claim helper (#694)

* Update Clarinet.json

* feat: non-breaking dev into main (#635)

* feat: stable-swap-pool anchored to xusd

* minor refactoring; test added

* tests added

* minor refactoring

* chore: diasable non-prod contract tests

* ci updated

* ci updated

* ci updated

* feat: wip ci

* feat: use curl for clarity manager

* feat: fix path issue

* curl added to ci

* clean up ci

* requirement changed to mainnet contract

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* Revert "feat: stable-swap-pool"

* Revert "Revert "feat: stable-swap-pool"" (#633)

* chore: short intro comments added to stable-swap-pool

* fix: oracle returns proper price (#634)

* tests updated to clarinet 1.0.0 requirements

* Revert "tests updated to clarinet 1.0.0 requirements"

This reverts commit 3b95b93cd8.

* Revert "Revert "tests updated to clarinet 1.0.0 requirements""

This reverts commit 6677cd4d8f.

* feat: merge dev

* ci updated

* fix: ci (#636)

Co-authored-by: Zitao Xiong <caoer115@gmail.com>

* working

* working;

* TODO: test suite

* test working

* working

* claim-for-claimer

* set-temp-timestamp

* set-temp-timestamp

* test added

---------

Co-authored-by: Zitao Xiong <caoer115@gmail.com>

* feat: event claim helper v1 01 (#695)

* Update Clarinet.json

* feat: non-breaking dev into main (#635)

* feat: stable-swap-pool anchored to xusd

* minor refactoring; test added

* tests added

* minor refactoring

* chore: diasable non-prod contract tests

* ci updated

* ci updated

* ci updated

* feat: wip ci

* feat: use curl for clarity manager

* feat: fix path issue

* curl added to ci

* clean up ci

* requirement changed to mainnet contract

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* auto-alex test updated

* Revert "feat: stable-swap-pool"

* Revert "Revert "feat: stable-swap-pool"" (#633)

* chore: short intro comments added to stable-swap-pool

* fix: oracle returns proper price (#634)

* tests updated to clarinet 1.0.0 requirements

* Revert "tests updated to clarinet 1.0.0 requirements"

This reverts commit 3b95b93cd8.

* Revert "Revert "tests updated to clarinet 1.0.0 requirements""

This reverts commit 6677cd4d8f.

* feat: merge dev

* ci updated

* fix: ci (#636)

Co-authored-by: Zitao Xiong <caoer115@gmail.com>

* working

* working;

* TODO: test suite

* test working

* working

* claim-for-claimer

* set-temp-timestamp

* set-temp-timestamp

* test added

* even-claim-helper-v1-01

* test

---------

Co-authored-by: Zitao Xiong <caoer115@gmail.com>

* feat: bridge tokens (#697)

* feat: add contracts from bridge

* fix: even claim helper test (#698)

* fix: event-claim-helper test

* fix: event-claim-helper test

* fix: event-claim-helper test

* fix: timestamp

* chore: amm-swap-pool comments

---------

Co-authored-by: fiftyeightandeight <alexd@alexgo.io>

* feat: launchpad with liquidity lock (#700)

* initial coding done

* launchpad-liquidity-lock => liquidity-launchpad

* feat: brc20-chax (#702)

* chore: launchpad-with-pool-lock (#703)

* feat: brc20-chax

* liquidity-launchpad => launchpad-with-pool-lock

* feat: bridged-apower-helper (#704)

* feat: bridged-apower-helper

* ci

* feat: bridge-apower-helper-v1-01 (#705)

* remove bridge-apower-helper-v1-01

* feat: missing file (#706)

* feat: add clarinet for brc20-chax

* clarinet.toml

---------

Co-authored-by: fiftyeightandeight <443225+fiftyeightandeight@users.noreply.github.com>

* fix: bridge-apower-helper-v1-01

---------

Co-authored-by: Kyle Fang <zhigang1992@gmail.com>
Co-authored-by: Zitao Xiong <caoer115@gmail.com>
This commit is contained in:
fiftyeightandeight
2023-11-01 10:35:18 +08:00
committed by GitHub
parent 2f03e34f36
commit a7b3f4aa4d
8 changed files with 1143 additions and 4 deletions

View File

@@ -20,7 +20,7 @@ jobs:
run: |
cd clarity && ./scripts/clarinet_manager.sh install
- name: "Check contract"
uses: docker://hirosystems/clarinet:1.5.4
uses: docker://hirosystems/clarinet:1.7.1
with:
entrypoint: "bash"
args: -c "cd clarity && clarinet check"
@@ -38,7 +38,7 @@ jobs:
run: |
echo "ci_env=$(bash <(curl -s https://codecov.io/env))" >> $GITHUB_ENV
- name: "Execute test suite"
uses: docker://hirosystems/clarinet:1.5.4
uses: docker://hirosystems/clarinet:1.7.1
with:
entrypoint: "bash"
#args: -c "cd clarity && clarinet test --coverage && curl -Os https://uploader.codecov.io/latest/linux/codecov && chmod +x codecov && ./codecov -t ${{ secrets.CODECOV_TOKEN }} -f coverage.lcov"

View File

@@ -1143,6 +1143,10 @@ depends_on = [ "auto-alex-v2", "token-apower", "brc20-db20" ]
path = "contracts/token/brc20-db20.clar"
depends_on = []
[contracts.brc20-chax]
path = "contracts/token/brc20-chax.clar"
depends_on = []
[contracts.flash-loan-user-amm-to-fixed-susdt-xusd]
path = "contracts/flash-loan-user-amm-to-fixed-susdt-xusd.clar"
@@ -1163,7 +1167,17 @@ path = "contracts/helpers/event-claim-helper-v1-01.clar"
clarity_version = 2
epoch = "2.1"
[contracts.liquidity-launchpad]
path = "contracts/pool/liquidity-launchpad.clar"
[contracts.launchpad-with-pool-lock]
path = "contracts/pool/launchpad-with-pool-lock.clar"
[contracts.bridge-apower-helper]
path = "contracts/helpers/bridge-apower-helper.clar"
clarity_version = 2
epoch = "2.4"
[contracts.bridge-apower-helper-v1-01]
path = "contracts/helpers/bridge-apower-helper-v1-01.clar"
clarity_version = 2
epoch = "2.4"

View File

@@ -0,0 +1,111 @@
(impl-trait .trait-ownable.ownable-trait)
(use-trait ft-trait .trait-sip-010.sip-010-trait)
(define-constant ERR-NOT-AUTHORIZED (err u1000))
(define-constant ERR-INVALID-TIMESTAMP (err u1001))
(define-constant ERR-UNKNOWN-EVENT-ID (err u1002))
(define-constant ERR-GET-BLOCK-INFO (err u1003))
(define-constant ERR-TOKEN-MISMATCH (err u1004))
(define-constant ONE_8 u100000000)
(define-constant MAX_UINT u340282366920938463463374607431768211455)
;; (define-constant apower .token-apower)
(define-data-var contract-owner principal tx-sender)
(define-data-var event-nonce uint u0)
(define-map events uint { bridged-token: principal, apower-per-bridged: uint, apower-cap: uint, start-timestamp: uint, end-timestamp: uint })
(define-map claimed { event-id: uint, user: principal } uint)
;; governance functions
(define-public (set-contract-owner (owner principal))
(begin
(try! (check-is-owner))
(ok (var-set contract-owner owner))
)
)
(define-public (create-event (bridged-token principal) (apower-per-bridged uint) (start-timestamp uint) (end-timestamp uint) (apower-cap (optional uint)))
(let
(
(event-id (+ (var-get event-nonce) u1))
)
(try! (check-is-owner))
(asserts! (< start-timestamp end-timestamp) ERR-INVALID-TIMESTAMP)
(map-set events event-id { bridged-token: bridged-token, apower-per-bridged: apower-per-bridged, apower-cap: (match apower-cap value value MAX_UINT), start-timestamp: start-timestamp, end-timestamp: end-timestamp })
(var-set event-nonce event-id)
(ok event-id)
)
)
;; read-only functions
(define-read-only (block-timestamp)
(ok (unwrap! (get-block-info? time (- block-height u1)) ERR-GET-BLOCK-INFO)))
(define-read-only (get-contract-owner)
(ok (var-get contract-owner)))
(define-read-only (get-event-details-or-fail (event-id uint))
(ok (unwrap! (map-get? events event-id) ERR-UNKNOWN-EVENT-ID)))
(define-read-only (get-claimed-or-default (event-id uint) (user principal))
(default-to u0 (map-get? claimed { event-id: event-id, user: user })))
;; external functions
(define-public (transfer-to-wrap
(event-id uint)
(order
{
to: uint,
token: uint,
amount-in-fixed: uint,
chain-id: uint,
salt: (buff 256)
}
)
(token-trait <ft-trait>)
(signature-packs (list 100 { signer: principal, order-hash: (buff 32), signature: (buff 65)})))
(let
(
(current-timestamp (try! (block-timestamp)))
(event-details (try! (get-event-details-or-fail event-id)))
(recipient (try! (contract-call? .bridge-endpoint-v1-02 user-from-id-or-fail (get to order))))
(apower-claimed (get-claimed-or-default event-id recipient))
(apower-excess (- (get apower-cap event-details) apower-claimed))
(apower-to-mint (min apower-excess (mul-down (get amount-in-fixed order) (get apower-per-bridged event-details))))
)
(asserts! (is-eq (contract-of token-trait) (get bridged-token event-details)) ERR-TOKEN-MISMATCH)
;; ;; if timestamp invalid, instead of reverting, it skips and process wrap.
(and
(>= current-timestamp (get start-timestamp event-details))
(<= current-timestamp (get end-timestamp event-details))
(> apower-to-mint u0)
(as-contract (try! (contract-call? .token-apower mint-fixed apower-to-mint recipient))))
(map-set claimed { event-id: event-id, user: recipient } (+ apower-claimed apower-to-mint))
(contract-call? .bridge-endpoint-v1-02 transfer-to-wrap order token-trait signature-packs)
)
)
;; internal functions
(define-private (check-is-owner)
(ok (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-AUTHORIZED)))
(define-private (mul-down (a uint) (b uint))
(/ (* a b) ONE_8))
(define-private (div-down (a uint) (b uint))
(if (is-eq a u0)
u0
(/ (* a ONE_8) b)
))
(define-private (max (a uint) (b uint))
(if (<= a b) b a))
(define-private (min (a uint) (b uint))
(if (<= a b) a b))

View File

@@ -0,0 +1,111 @@
(impl-trait .trait-ownable.ownable-trait)
(use-trait ft-trait .trait-sip-010.sip-010-trait)
(define-constant ERR-NOT-AUTHORIZED (err u1000))
(define-constant ERR-INVALID-TIMESTAMP (err u1001))
(define-constant ERR-UNKNOWN-EVENT-ID (err u1002))
(define-constant ERR-GET-BLOCK-INFO (err u1003))
(define-constant ERR-TOKEN-MISMATCH (err u1004))
(define-constant ONE_8 u100000000)
(define-constant MAX_UINT u340282366920938463463374607431768211455)
(define-constant apower .token-apower)
(define-data-var contract-owner principal tx-sender)
(define-data-var event-nonce uint u0)
(define-map events uint { bridged-token: principal, apower-per-bridged: uint, apower-cap: uint, start-timestamp: uint, end-timestamp: uint })
(define-map claimed { event-id: uint, user: principal } uint)
;; governance functions
(define-public (set-contract-owner (owner principal))
(begin
(try! (check-is-owner))
(ok (var-set contract-owner owner))
)
)
(define-public (create-event (bridged-token principal) (apower-per-bridged uint) (start-timestamp uint) (end-timestamp uint) (apower-cap (optional uint)))
(let
(
(event-id (+ (var-get event-nonce) u1))
)
(try! (check-is-owner))
(asserts! (< start-timestamp end-timestamp) ERR-INVALID-TIMESTAMP)
(map-set events event-id { bridged-token: bridged-token, apower-per-bridged: apower-per-bridged, apower-cap: (match apower-cap value value MAX_UINT), start-timestamp: start-timestamp, end-timestamp: end-timestamp })
(var-set event-nonce event-id)
(ok event-id)
)
)
;; read-only functions
(define-read-only (block-timestamp)
(ok (unwrap! (get-block-info? time (- block-height u1)) ERR-GET-BLOCK-INFO)))
(define-read-only (get-contract-owner)
(ok (var-get contract-owner)))
(define-read-only (get-event-details-or-fail (event-id uint))
(ok (unwrap! (map-get? events event-id) ERR-UNKNOWN-EVENT-ID)))
(define-read-only (get-claimed-or-default (event-id uint) (user principal))
(default-to u0 (map-get? claimed { event-id: event-id, user: user })))
;; external functions
(define-public (transfer-to-wrap
(event-id uint)
(order
{
to: uint,
token: uint,
amount-in-fixed: uint,
chain-id: uint,
salt: (buff 256)
}
)
(token-trait <ft-trait>)
(signature-packs (list 100 { signer: principal, order-hash: (buff 32), signature: (buff 65)})))
(let
(
(current-timestamp (try! (block-timestamp)))
(event-details (try! (get-event-details-or-fail event-id)))
(recipient (try! (contract-call? .bridge-endpoint-v1-02 user-from-id-or-fail (get to order))))
(apower-claimed (get-claimed-or-default event-id recipient))
(apower-excess (- (get apower-cap event-details) apower-claimed))
(apower-to-mint (min apower-excess (mul-down (get amount-in-fixed order) (get apower-per-bridged event-details))))
)
(asserts! (is-eq (contract-of token-trait) (get bridged-token event-details)) ERR-TOKEN-MISMATCH)
;; if timestamp invalid, instead of reverting, it skips and process wrap.
(and
(>= current-timestamp (get start-timestamp event-details))
(<= current-timestamp (get end-timestamp event-details))
(> apower-to-mint u0)
(as-contract (try! (contract-call? apower mint-fixed apower-to-mint recipient))))
(map-set claimed { event-id: event-id, user: recipient } (+ apower-claimed apower-to-mint))
(contract-call? .bridge-endpoint-v1-02 transfer-to-wrap order token-trait signature-packs)
)
)
;; internal functions
(define-private (check-is-owner)
(ok (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-AUTHORIZED)))
(define-private (mul-down (a uint) (b uint))
(/ (* a b) ONE_8))
(define-private (div-down (a uint) (b uint))
(if (is-eq a u0)
u0
(/ (* a ONE_8) b)
))
(define-private (max (a uint) (b uint))
(if (<= a b) b a))
(define-private (min (a uint) (b uint))
(if (<= a b) a b))

View File

@@ -0,0 +1,185 @@
(use-trait ft-trait .trait-sip-010.sip-010-trait)
(use-trait sft-trait .trait-semi-fungible.semi-fungible-trait)
(define-constant err-unknown-launch (err u2045))
(define-constant err-block-height-not-reached (err u2042))
(define-constant err-not-authorized (err u1000))
(define-constant ONE_8 u100000000)
(define-data-var contract-owner principal tx-sender)
(define-map locked uint { launch-owner: principal, pct: uint, period: uint, pool-id: uint })
;; governance functions
(define-public (set-contract-owner (owner principal))
(begin
(try! (check-is-owner))
(ok (var-set contract-owner owner))
)
)
(define-public (create-pool
(launch-token-trait <ft-trait>)
(payment-token-trait <ft-trait>)
(offering
{
launch-owner: principal,
launch-tokens-per-ticket: uint,
price-per-ticket-in-fixed: uint,
activation-threshold: uint,
registration-start-height: uint,
registration-end-height: uint,
claim-end-height: uint,
apower-per-ticket-in-fixed: (list 6 {apower-per-ticket-in-fixed: uint, tier-threshold: uint}),
registration-max-tickets: uint,
fee-per-ticket-in-fixed: uint
})
(locked-params { pct: uint, period: uint })
)
(let
(
(launch-id (try! (contract-call? .alex-launchpad-v1-3 create-pool launch-token-trait payment-token-trait (merge offering { launch-owner: (as-contract tx-sender) }))))
)
(try! (contract-call? .alex-vault-v1-1 set-approved-token (contract-of launch-token-trait) true))
(try! (contract-call? .alex-vault-v1-1 set-approved-token (contract-of payment-token-trait) true))
(map-set locked launch-id (merge locked-params { launch-owner: (get launch-owner offering), pool-id: u0 }))
(ok launch-id)
)
)
(define-public (transfer-all-to-owner (token-trait <ft-trait>))
(let
(
(balance (try! (contract-call? token-trait get-balance-fixed (as-contract tx-sender))))
)
(try! (check-is-owner))
(ok (and (> balance u0) (as-contract (try! (contract-call? token-trait transfer-fixed balance tx-sender (var-get contract-owner) none)))))
)
)
(define-public (transfer-all-semi-to-owner (token-trait <sft-trait>) (token-id uint))
(let
(
(balance (try! (contract-call? token-trait get-balance-fixed token-id (as-contract tx-sender))))
)
(try! (check-is-owner))
(ok (and (> balance u0) (as-contract (try! (contract-call? token-trait transfer-fixed token-id balance tx-sender (var-get contract-owner))))))
)
)
;; privileged functions
(define-public (add-to-position (launch-id uint) (tickets uint) (launch-token-trait <ft-trait>))
(let
(
(offering (unwrap! (get-launch launch-id) err-unknown-launch))
(locked-details (unwrap! (get-locked-details launch-id) err-unknown-launch))
)
(asserts! (or (is-eq (get launch-owner locked-details) tx-sender) (is-ok (check-is-owner))) err-not-authorized)
(try! (contract-call? launch-token-trait transfer-fixed (* (get launch-tokens-per-ticket offering) tickets ONE_8) tx-sender (as-contract tx-sender) none))
(as-contract (try! (contract-call? .alex-launchpad-v1-3 add-to-position launch-id tickets launch-token-trait)))
(contract-call? launch-token-trait transfer-fixed (mul-down (* (get launch-tokens-per-ticket offering) tickets ONE_8) (get pct locked-details)) tx-sender (as-contract tx-sender) none)
)
)
;; read-only functions
(define-read-only (get-launch (launch-id uint))
(unwrap-panic (contract-call? .alex-launchpad-v1-3 get-launch launch-id))
)
(define-read-only (get-locked-details (launch-id uint))
(map-get? locked launch-id)
)
(define-read-only (get-contract-owner)
(ok (var-get contract-owner))
)
;; public functions
(define-public (claim (launch-id uint) (input (list 200 principal)) (launch-token-trait <ft-trait>) (payment-token-trait <ft-trait>))
(let
(
(offering (unwrap! (get-launch launch-id) err-unknown-launch))
(launch-token (get launch-token offering))
(payment-token (get payment-token offering))
(locked-details (unwrap! (get-locked-details launch-id) err-unknown-launch))
(launch-token-amount (* (get launch-tokens-per-ticket offering) (len input) (get pct locked-details)))
(fee-per-ticket (mul-down (get price-per-ticket-in-fixed offering) (get fee-per-ticket-in-fixed offering)))
(net-price-per-ticket (- (get price-per-ticket-in-fixed offering) fee-per-ticket))
(payment-token-amount (mul-down (* net-price-per-ticket (len input)) (get pct locked-details)))
)
(try! (contract-call? .alex-launchpad-v1-3 claim launch-id input launch-token-trait payment-token-trait))
(if (is-some (contract-call? .amm-swap-pool-v1-1 get-pool-exists payment-token launch-token ONE_8))
(begin
(as-contract (try! (contract-call? .amm-swap-pool-v1-1 add-to-position payment-token-trait launch-token-trait ONE_8 payment-token-amount (some launch-token-amount))))
true
)
(let
(
(pool-created (as-contract (try! (contract-call? .amm-swap-pool-v1-1 create-pool payment-token-trait launch-token-trait ONE_8 tx-sender payment-token-amount launch-token-amount))))
(pool-details (unwrap-panic (contract-call? .amm-swap-pool-v1-1 get-pool-exists payment-token launch-token ONE_8)))
)
(as-contract (try! (contract-call? .amm-swap-pool-v1-1 set-fee-rate-x payment-token launch-token ONE_8 u500000)))
(as-contract (try! (contract-call? .amm-swap-pool-v1-1 set-fee-rate-y payment-token launch-token ONE_8 u500000)))
(as-contract (try! (contract-call? .amm-swap-pool-v1-1 set-max-in-ratio payment-token launch-token ONE_8 u3000000)))
(as-contract (try! (contract-call? .amm-swap-pool-v1-1 set-max-out-ratio payment-token launch-token ONE_8 u3000000)))
(as-contract (try! (contract-call? .amm-swap-pool-v1-1 set-oracle-enabled payment-token launch-token ONE_8 true)))
(as-contract (try! (contract-call? .amm-swap-pool-v1-1 set-oracle-average payment-token launch-token ONE_8 u9900000)))
(map-set locked launch-id (merge locked-details { pool-id: (get pool-id pool-details) }))
)
)
(ok { dx: payment-token-amount, dy: launch-token-amount })
)
)
;; redeem LP after lock period
;; assert block-height > end of launch + locked-period
;; redeem liquidity tokens
;; send launch-/payment-tokens to launch-owner
(define-public (reduce-position (launch-id uint) (launch-token-trait <ft-trait>) (payment-token-trait <ft-trait>) (percent uint))
(let
(
(offering (unwrap! (get-launch launch-id) err-unknown-launch))
(locked-details (unwrap! (get-locked-details launch-id) err-unknown-launch))
(reduced (as-contract (try! (contract-call? .amm-swap-pool-v1-1 reduce-position payment-token-trait launch-token-trait ONE_8 percent))))
)
(asserts! (> block-height (+ (get registration-end-height offering) (get period locked-details))) err-block-height-not-reached)
(as-contract (try! (contract-call? payment-token-trait transfer-fixed (get dx reduced) tx-sender (get launch-owner locked-details) none)))
(as-contract (try! (contract-call? launch-token-trait transfer-fixed (get dy reduced) tx-sender (get launch-owner locked-details) none)))
(ok reduced)
)
)
;; private functions
(define-private (check-is-owner)
(ok (asserts! (is-eq tx-sender (var-get contract-owner)) err-not-authorized))
)
;; @desc mul-down
;; @params a
;; @params b
;; @returns uint
(define-private (mul-down (a uint) (b uint))
(/ (* a b) ONE_8)
)
;; @desc div-down
;; @params a
;; @params b
;; @returns uint
(define-private (div-down (a uint) (b uint))
(if (is-eq a u0)
u0
(/ (* a ONE_8) b)
)
)
;; contract initialisation
;; (set-contract-owner .executor-dao)

View File

@@ -0,0 +1,228 @@
(define-constant ERR-NOT-AUTHORIZED (err u1000))
(define-constant ONE_8 u100000000)
;; -- token implementation
(define-fungible-token brc20-chax)
(define-data-var contract-owner principal tx-sender)
(define-data-var token-name (string-ascii 32) "CHAX (BRC20)")
(define-data-var token-symbol (string-ascii 32) "CHAX")
(define-data-var token-uri (optional (string-utf8 256)) (some u"https://cdn.alexlab.co/metadata/brc20-chax.json"))
(define-data-var token-decimals uint u8)
(define-map approved-contracts principal bool)
;; read-only calls
(define-read-only (get-contract-owner)
(ok (var-get contract-owner))
)
(define-read-only (get-name)
(ok (var-get token-name))
)
(define-read-only (get-symbol)
(ok (var-get token-symbol))
)
(define-read-only (get-decimals)
(ok (var-get token-decimals))
)
(define-read-only (get-balance (who principal))
(ok (ft-get-balance brc20-chax who))
)
(define-read-only (get-total-supply)
(ok (ft-get-supply brc20-chax))
)
(define-read-only (get-token-uri)
(ok (var-get token-uri))
)
;; @desc fixed-to-decimals
;; @params amount
;; @returns uint
(define-read-only (fixed-to-decimals (amount uint))
(/ (* amount (pow-decimals)) ONE_8)
)
;; @desc get-total-supply-fixed
;; @params token-id
;; @returns (response uint)
(define-read-only (get-total-supply-fixed)
(ok (decimals-to-fixed (unwrap-panic (get-total-supply))))
)
;; @desc get-balance-fixed
;; @params token-id
;; @params who
;; @returns (response uint)
(define-read-only (get-balance-fixed (account principal))
(ok (decimals-to-fixed (unwrap-panic (get-balance account))))
)
;; governance calls
(define-public (set-contract-owner (owner principal))
(begin
(try! (check-is-owner))
(ok (var-set contract-owner owner))
)
)
(define-public (set-name (new-name (string-ascii 32)))
(begin
(try! (check-is-owner))
(ok (var-set token-name new-name))
)
)
(define-public (set-symbol (new-symbol (string-ascii 10)))
(begin
(try! (check-is-owner))
(ok (var-set token-symbol new-symbol))
)
)
(define-public (set-decimals (new-decimals uint))
(begin
(try! (check-is-owner))
(ok (var-set token-decimals new-decimals))
)
)
(define-public (set-token-uri (new-uri (optional (string-utf8 256))))
(begin
(try! (check-is-owner))
(ok (var-set token-uri new-uri))
)
)
(define-public (add-approved-contract (new-approved-contract principal))
(begin
(try! (check-is-owner))
(ok (map-set approved-contracts new-approved-contract true))
)
)
(define-public (set-approved-contract (owner principal) (approved bool))
(begin
(try! (check-is-owner))
(ok (map-set approved-contracts owner approved))
)
)
;; priviliged calls
;; @desc mint
;; @restricted ContractOwner/Approved Contract
;; @params token-id
;; @params amount
;; @params recipient
;; @returns (response bool)
(define-public (mint (amount uint) (recipient principal))
(begin
(asserts! (or (is-ok (check-is-approved)) (is-ok (check-is-owner))) ERR-NOT-AUTHORIZED)
(ft-mint? brc20-chax amount recipient)
)
)
;; @desc burn
;; @restricted ContractOwner/Approved Contract
;; @params token-id
;; @params amount
;; @params sender
;; @returns (response bool)
(define-public (burn (amount uint) (sender principal))
(begin
(asserts! (or (is-ok (check-is-approved)) (is-ok (check-is-owner))) ERR-NOT-AUTHORIZED)
(ft-burn? brc20-chax amount sender)
)
)
;; @desc mint-fixed
;; @params token-id
;; @params amount
;; @params recipient
;; @returns (response bool)
(define-public (mint-fixed (amount uint) (recipient principal))
(mint (fixed-to-decimals amount) recipient)
)
;; @desc burn-fixed
;; @params token-id
;; @params amount
;; @params sender
;; @returns (response bool)
(define-public (burn-fixed (amount uint) (sender principal))
(burn (fixed-to-decimals amount) sender)
)
(define-public (mint-fixed-many (recipients (list 200 { amount: uint, to: principal})))
(fold mint-many-iter recipients (ok true))
)
;; public calls
(define-public (transfer (amount uint) (sender principal) (recipient principal) (memo (optional (buff 34))))
(begin
(asserts! (is-eq sender tx-sender) ERR-NOT-AUTHORIZED)
(try! (ft-transfer? brc20-chax amount sender recipient))
(match memo to-print (print to-print) 0x)
(ok true)
)
)
;; @desc transfer-fixed
;; @params token-id
;; @params amount
;; @params sender
;; @params recipient
;; @returns (response bool)
(define-public (transfer-fixed (amount uint) (sender principal) (recipient principal) (memo (optional (buff 34))))
(transfer (fixed-to-decimals amount) sender recipient memo)
)
(define-public (transfer-fixed-many (recipients (list 200 { amount: uint, to: principal})))
(fold transfer-many-iter recipients (ok true))
)
;; private calls
(define-private (check-is-owner)
(ok (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-AUTHORIZED))
)
(define-private (check-is-approved)
(ok (asserts! (default-to false (map-get? approved-contracts tx-sender)) ERR-NOT-AUTHORIZED))
)
;; @desc decimals-to-fixed
;; @params amount
;; @returns uint
(define-private (decimals-to-fixed (amount uint))
(/ (* amount ONE_8) (pow-decimals))
)
;; @desc pow-decimals
;; @returns uint
(define-private (pow-decimals)
(pow u10 (unwrap-panic (get-decimals)))
)
(define-private (mint-many-iter (recipient { amount: uint, to: principal }) (previous-response (response bool uint)))
(match previous-response prev-ok (mint-fixed (get amount recipient) (get to recipient)) prev-err previous-response)
)
(define-private (transfer-many-iter (recipient { amount: uint, to: principal }) (previous-response (response bool uint)))
(match previous-response prev-ok (transfer-fixed (get amount recipient) tx-sender (get to recipient) none) prev-err previous-response)
)
;; contract initialisation
;; (set-contract-owner .executor-dao)

View File

@@ -0,0 +1,383 @@
import {
ONE_8,
Clarinet,
Tx,
types,
assertEquals,
prepareStandardTest,
contractPrincipal,
extractBounds,
extractParameters,
determineApower,
} from "./models/launchpad-with-pool-lock.ts";
import type {
Chain,
Account,
StandardTestParameters,
} from "./models/launchpad-with-pool-lock.ts";
import {
determineWinners,
determineLosers,
IdoParameters,
IdoParticipant,
} from "../scripts/launchpad.ts";
const parameters = {
totalIdoTokens: 40000,
idoOwner: undefined,
ticketsForSale: 801,
idoTokensPerTicket: 50,
pricePerTicketInFixed: 5000000000,
activationThreshold: 1,
ticketRecipients: undefined,
registrationStartHeight: 20,
registrationEndHeight: 30,
claimEndHeight: 40,
apowerPerTicketInFixed: [
{ tierThreshold: 5, apowerPerTicketInFixed: 10 * ONE_8 },
{ tierThreshold: 10, apowerPerTicketInFixed: 50 * ONE_8 },
{ tierThreshold: 20, apowerPerTicketInFixed: 100 * ONE_8 },
{ tierThreshold: 30, apowerPerTicketInFixed: 150 * ONE_8 },
{ tierThreshold: 40, apowerPerTicketInFixed: 200 * ONE_8 },
{ tierThreshold: 50, apowerPerTicketInFixed: 250 * ONE_8 },
],
registrationMaxTickets: 999999999999,
feePerTicketInFixed: 0,
lockPct: 0.1e8,
lockPeriod: 20
};
Clarinet.test({
name: "launchpad-with-pool-lock : example claim walk test",
async fn(chain: Chain, accounts: Map<string, Account>) {
const [
deployer,
accountA,
accountB,
accountC,
accountD,
accountE,
accountF,
accountG,
] = [
"deployer",
"wallet_1",
"wallet_2",
"wallet_3",
"wallet_4",
"wallet_5",
"wallet_6",
"wallet_7",
].map((wallet) => accounts.get(wallet)!);
let winners_list: number[] = [];
const approved_operator = accountC;
const third = chain.mineBlock([
Tx.contractCall(
"alex-launchpad-v1-3",
"add-approved-operator",
[types.principal(approved_operator.address)],
deployer.address
),
]);
const registrationStartHeight = 20;
const registrationEndHeight = registrationStartHeight + 10;
const claimEndHeight = registrationEndHeight + 100;
const ticketRecipients = [
{ recipient: accountA, amount: 10 },
{ recipient: accountB, amount: 4000 },
{ recipient: accountC, amount: 2000 },
{ recipient: accountD, amount: 50000 },
{ recipient: accountE, amount: 1010 },
{ recipient: accountF, amount: 100000 },
{ recipient: accountG, amount: 10 },
];
const params: StandardTestParameters = {
...parameters,
totalIdoTokens: 50000,
idoOwner: accountA,
ticketsForSale: 500,
idoTokensPerTicket: 100,
pricePerTicketInFixed: 33e8,
activationThreshold: 500,
ticketRecipients: ticketRecipients,
registrationStartHeight: registrationStartHeight,
registrationEndHeight: registrationEndHeight,
claimEndHeight: claimEndHeight,
};
const preparation = prepareStandardTest(chain, params, deployer);
preparation.blocks.map((block) =>
block.receipts.map(({ result }) => result.expectOk())
);
const { idoId } = preparation;
chain.mineEmptyBlockUntil(registrationStartHeight);
const registrations = chain.mineBlock(
ticketRecipients.map((entry) =>
Tx.contractCall(
"alex-launchpad-v1-3",
"register",
[
types.uint(idoId),
types.uint(entry.amount),
types.principal(contractPrincipal(deployer, "token-wstx")),
],
(entry.recipient as Account).address ||
(entry.recipient as unknown as string)
)
)
);
// console.log(registrations.receipts);
registrations.receipts.map(({ result }) => result.expectOk());
assertEquals(registrations.receipts.length, ticketRecipients.length);
for (let i = 0; i < ticketRecipients.length; i++) {
registrations.receipts[i].events.expectSTXTransferEvent(
((ticketRecipients[i]["amount"] *
params["pricePerTicketInFixed"]) /
ONE_8) *
1e6,
ticketRecipients[i]["recipient"].address,
deployer.address + ".alex-launchpad-v1-3"
);
registrations.receipts[i].events.expectFungibleTokenBurnEvent(
determineApower(
ticketRecipients[i]["amount"],
params["apowerPerTicketInFixed"]
),
ticketRecipients[i]["recipient"].address,
"apower"
);
}
const bounds = registrations.receipts.map((receipt) =>
extractBounds(receipt.result)
);
chain.mineEmptyBlockUntil(registrationEndHeight + 2);
const parametersFromChain = chain.callReadOnlyFn(
"alex-launchpad-v1-3",
"get-offering-walk-parameters",
[types.uint(idoId)],
deployer.address
);
const idoParameters: IdoParameters = extractParameters(
parametersFromChain.result
);
const idoParticipants: IdoParticipant[] = ticketRecipients.map(
(entry, index) => ({
participant: entry.recipient.address,
...bounds[index],
})
);
// console.log(idoParameters);
// console.log(idoParticipants);
const dx_expected = (params.pricePerTicketInFixed - params.feePerTicketInFixed) * params.ticketsForSale * params.lockPct / ONE_8;
const dy_expected = params.idoTokensPerTicket * params.ticketsForSale * params.lockPct;
let dx_actual = 0;
let dy_actual = 0;
// console.log("determining winners...");
const winners = determineWinners(idoParameters, idoParticipants);
// console.log(winners);
let maxChunkSize = 200;
for (
let index = 0;
index < winners.winners.length;
index += maxChunkSize
) {
let winners_sliced = winners.winners.slice(index, index + maxChunkSize);
// console.log(winners_sliced[0], winners_sliced[winners_sliced.length - 1]);
const claim = chain.mineBlock([
Tx.contractCall(
"launchpad-with-pool-lock",
"claim",
[
types.uint(idoId),
types.list(winners_sliced.map(types.principal)),
types.principal(contractPrincipal(deployer, "token-wban")),
types.principal(contractPrincipal(deployer, "token-wstx")),
],
accountC.address
),
]);
winners_list.push(winners.winners.length);
let events = claim.receipts[0].events;
const liquidity = claim.receipts[0].result.expectOk().expectTuple();
const dx = parseInt(liquidity.dx.substring(1));
const dy = parseInt(liquidity.dy.substring(1));
dx_actual += dx;
dy_actual += dy;
console.log(`${index}, dx = ${dx_actual} vs ${dx_expected}, dy = ${dy_actual} vs. ${dy_expected}`);
// assertEquals(events.length, 1 + winners_sliced.length);
events.expectSTXTransferEvent(
((params["pricePerTicketInFixed"] * winners_sliced.length) /
ONE_8) *
1e6,
deployer.address + ".alex-launchpad-v1-3",
deployer.address + ".launchpad-with-pool-lock"
);
events.expectSTXTransferEvent(
dx / ONE_8 * 1e6,
deployer.address + '.launchpad-with-pool-lock',
deployer.address + '.alex-vault-v1-1'
);
events.expectFungibleTokenTransferEvent(
dy / ONE_8 * 1e6,
deployer.address + '.launchpad-with-pool-lock',
deployer.address + '.alex-vault-v1-1',
"banana"
)
for (let j = 1; j < 1 + winners_sliced.length; j++) {
events.expectFungibleTokenTransferEvent(
params["idoTokensPerTicket"] * 1e6,
deployer.address + ".alex-launchpad-v1-3",
winners_sliced[j - 1],
"banana"
);
}
}
assertEquals(dx_actual, dx_expected);
assertEquals(dy_actual, dy_expected);
chain.mineEmptyBlockUntil(registrationEndHeight + 19);
const reduceFail = chain.mineBlock([
Tx.contractCall(
"launchpad-with-pool-lock",
"reduce-position",
[
types.uint(idoId),
types.principal(contractPrincipal(deployer, "token-wban")),
types.principal(contractPrincipal(deployer, "token-wstx")),
types.uint(ONE_8)
],
accountC.address
),
]);
reduceFail.receipts[0].result.expectErr().expectUint(2042);
chain.mineEmptyBlockUntil(claimEndHeight);
// console.log("determining losers...");
const losers = determineLosers(idoParameters, idoParticipants);
let losers_list = losers.losers.map((e) => {
return e.recipient;
});
for (let index = 0; index < idoParticipants.length; index++) {
let participant = idoParticipants[index]["participant"];
let won =
winners.winners.indexOf(participant) == -1
? 0
: winners.winners.lastIndexOf(participant) -
winners.winners.indexOf(participant) +
1;
let lost =
losers_list.indexOf(participant) == -1
? 0
: losers.losers[losers_list.indexOf(participant)]["amount"];
console.log(
participant,
"registered:",
won + lost,
"won:",
won,
"lost:",
lost
);
assertEquals(ticketRecipients[index]["amount"], won + lost);
}
maxChunkSize = 5;
for (let index = 0; index < losers.losers.length; index += maxChunkSize) {
let losers_sliced = losers.losers.slice(index, index + maxChunkSize);
// console.log(losers_sliced);
const claim = chain.mineBlock([
Tx.contractCall(
"alex-launchpad-v1-3",
"refund",
[
types.uint(idoId),
types.list(
losers_sliced.map((e) => {
return types.tuple({
recipient: types.principal(e.recipient),
amount: types.uint(
e.amount * params["pricePerTicketInFixed"]
),
});
})
),
types.principal(contractPrincipal(deployer, "token-wstx")),
],
accountC.address
),
]);
let events = claim.receipts[0].events;
// console.log(index, claim.receipts[0].result);
assertEquals(events.length, losers_sliced.length);
for (let j = 0; j < events.length; j++) {
events.expectSTXTransferEvent(
((losers_sliced[j]["amount"] *
params["pricePerTicketInFixed"]) /
ONE_8) *
1e6,
deployer.address + ".alex-launchpad-v1-3",
losers_sliced[j]["recipient"]
);
}
}
const reduceOk = chain.mineBlock([
Tx.contractCall(
"launchpad-with-pool-lock",
"reduce-position",
[
types.uint(idoId),
types.principal(contractPrincipal(deployer, "token-wban")),
types.principal(contractPrincipal(deployer, "token-wstx")),
types.uint(ONE_8)
],
accountC.address
),
]);
reduceOk.receipts[0].result.expectOk();
const events = reduceOk.receipts[0].events;
events.expectSTXTransferEvent(
dx_expected / ONE_8 * 1e6,
deployer.address + '.alex-vault-v1-1',
deployer.address + '.launchpad-with-pool-lock'
)
events.expectSTXTransferEvent(
dx_expected / ONE_8 * 1e6,
deployer.address + '.launchpad-with-pool-lock',
accountA.address
)
events.expectFungibleTokenTransferEvent(
dy_expected / ONE_8 * 1e6,
deployer.address + '.alex-vault-v1-1',
deployer.address + '.launchpad-with-pool-lock',
"banana"
)
events.expectFungibleTokenTransferEvent(
dy_expected / ONE_8 * 1e6,
deployer.address + '.launchpad-with-pool-lock',
accountA.address,
"banana"
)
},
});

View File

@@ -0,0 +1,107 @@
import { Clarinet, Tx, types } from 'https://deno.land/x/clarinet@v0.34.0/index.ts';
import type { Chain, Account } from 'https://deno.land/x/clarinet@v0.34.0/index.ts';
import { assertEquals } from 'https://deno.land/std@0.166.0/testing/asserts.ts';
export { Clarinet, Tx, types, assertEquals };
export type { Chain, Account };
export const ONE_8 = 100000000;
export type TicketAllocation = { recipient: Account | string, amount: number };
export type StandardTestParameters = {
totalIdoTokens: number,
idoOwner: Account,
ticketsForSale: number,
idoTokensPerTicket: number,
pricePerTicketInFixed: number,
activationThreshold?: number,
registrationStartHeight?: number,
registrationEndHeight?: number,
claimEndHeight?: number,
ticketRecipients: TicketAllocation[],
apowerPerTicketInFixed: any[],
registrationMaxTickets: number,
feePerTicketInFixed: number,
lockPct: number,
lockPeriod: number
};
export const contractPrincipal = (address: Account | string, contractName: string) => `${(address as Account).address || address}.${contractName}`;
export function determineApower(tickets: number, apowerPerTicketInFixed: any[]){
let apower = 0;
let remaining_tickets = tickets;
for(let i = 0; i < apowerPerTicketInFixed.length; i++){
let tickets_to_process = (remaining_tickets < apowerPerTicketInFixed[i]['tierThreshold'] || i == apowerPerTicketInFixed.length - 1) ? remaining_tickets : apowerPerTicketInFixed[i]['tierThreshold'];
remaining_tickets -= tickets_to_process;
apower += apowerPerTicketInFixed[i]['apowerPerTicketInFixed'] * tickets_to_process;
}
return apower;
}
export function prepareStandardTest(chain: Chain, parameters: StandardTestParameters, deployer: Account) {
const {
totalIdoTokens,
idoOwner,
ticketsForSale,
idoTokensPerTicket,
pricePerTicketInFixed,
activationThreshold,
registrationStartHeight,
registrationEndHeight,
claimEndHeight,
ticketRecipients,
apowerPerTicketInFixed,
registrationMaxTickets,
feePerTicketInFixed,
lockPct,
lockPeriod
} = parameters;
const first = chain.mineBlock([
Tx.contractCall("alex-vault-v1-1", "set-approved-contract", [types.principal(deployer.address + '.amm-swap-pool-v1-1'), types.bool(true)], deployer.address),
Tx.contractCall("token-banana", "mint-fixed", [types.uint(totalIdoTokens * ticketsForSale * ONE_8), types.principal(idoOwner.address)], deployer.address),
Tx.contractCall("token-apower", "add-approved-contract", [types.principal(contractPrincipal(deployer, "alex-launchpad-v1-3"))], deployer.address),
...ticketRecipients.map(allocation => Tx.contractCall("token-apower", "mint-fixed", [types.uint(determineApower(allocation.amount, apowerPerTicketInFixed)), types.principal((allocation.recipient as Account).address || allocation.recipient as string)], deployer.address)),
Tx.contractCall("launchpad-with-pool-lock", "create-pool", [
types.principal(contractPrincipal(deployer, "token-wban")),
types.principal(contractPrincipal(deployer, "token-wstx")),
types.tuple({
"launch-owner": types.principal(idoOwner.address),
"launch-tokens-per-ticket": types.uint(idoTokensPerTicket),
"price-per-ticket-in-fixed": types.uint(pricePerTicketInFixed),
"activation-threshold": types.uint(activationThreshold || 1),
"registration-start-height": types.uint(registrationStartHeight || 0),
"registration-end-height": types.uint(registrationEndHeight || 10),
"claim-end-height": types.uint(claimEndHeight || 20),
"apower-per-ticket-in-fixed": types.list(apowerPerTicketInFixed.map(e => { return types.tuple( { 'apower-per-ticket-in-fixed': types.uint(e.apowerPerTicketInFixed), 'tier-threshold': types.uint(e.tierThreshold) } ) })),
"registration-max-tickets": types.uint(registrationMaxTickets),
"fee-per-ticket-in-fixed": types.uint(feePerTicketInFixed)
}),
types.tuple({ pct: types.uint(lockPct), period: types.uint(lockPeriod) })
], deployer.address),
]);
const idoId = parseInt(first.receipts[first.receipts.length - 1].result.expectOk().toString().substring(1));
assertEquals(isNaN(idoId), false, "failed to get Launch ID");
const second = chain.mineBlock([
Tx.contractCall("launchpad-with-pool-lock", "add-to-position", [types.uint(idoId), types.uint(ticketsForSale), types.principal(contractPrincipal(deployer, "token-wban"))], idoOwner.address),
]);
return { idoId, blocks: [first, second] };
}
export function extractBounds(registrationResponse: string) {
const tuple = registrationResponse.expectOk().expectTuple() as any;
return {
start: parseInt(tuple.start.substring(1)),
end: parseInt(tuple.end.substring(1))
};
}
export function extractParameters(parametersResponse: string) {
const tuple = parametersResponse.expectOk().expectTuple() as any;
return {
maxStepSize: parseInt(tuple["max-step-size"].substring(1)),
walkPosition: parseInt(tuple["walk-position"].substring(1)),
ticketsForSale: parseInt(tuple["total-tickets"].substring(1)),
activationThreshold: parseInt(tuple["activation-threshold"].substring(1))
};
}