(use-trait ft-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)
(use-trait pool-trait .pool-trait.pool-trait)
(use-trait gas-oracle-trait .gas-oracle-trait.gas-oracle-trait)
(use-trait messenger-trait .messenger-trait.messenger-trait)
(define-constant err-unauthorized (err u1000))
(define-constant err-deposit-amount-too-small (err u1001))
(define-constant err-token-not-supported (err u1002))
(define-constant err-wrong-token-for-pool (err u1003))
(define-constant err-already-initialized (err u1004))
(define-constant err-already-sent (err u1005))
(define-constant err-not-initialized (err u1006))
(define-constant err-not-enough-fee (err u1007))
(define-constant err-gas-usage-not-set (err u1008))
(define-constant err-bridge-source-not-registered (err u1009))
(define-constant err-already-processed (err u1010))
(define-constant err-no-message (err u1011))
(define-constant err-swap-prohibited (err u1012))
(define-constant err-wrong-gas-oracle (err u1013))
(define-constant err-messenger-not-registered (err u1014))
(define-constant err-wrong-messenger (err u1015))
(define-constant err-wrong-destination-chain (err u1016))
(define-constant err-unknown-chain-or-token (err u1017))
(define-constant err-wrong-buffer-length (err u1018))
(define-constant err-empty-buffer (err u1019))
(define-constant err-wrong-msg-length (err u1020))
(define-constant chain-precision u6)
(define-constant oracle-precision u18)
(define-constant zero-bytes-12 0x000000000000000000000000)
(define-constant zero-bytes-16 0x00000000000000000000000000000000)
(define-constant zero-bytes-32 0x0000000000000000000000000000000000000000000000000000000000000000)
(define-data-var this-chain-id (optional uint) none)
(define-data-var gas-oracle-principal (optional principal) none)
(define-data-var owner principal contract-caller)
(define-data-var stop-authority principal contract-caller)
(define-data-var rebalancer principal contract-caller)
(define-data-var can-swap bool false)
(define-map token-infos
(buff 32) ;; Token principal hash (keccak256(hash-bytes + name))
{
token: principal,
pool: principal,
from-gas-oracle-scaling-factor: uint,
bridging-fee-conversion-scaling-factor: uint,
}
)
(define-map processed-messages
(buff 32)
bool
)
(define-map messengers
uint
principal
)
(define-map sent-messages
(buff 32)
bool
)
(define-map other-bridges
uint
(buff 32)
)
(define-map other-bridge-tokens
{
chain-id: uint,
token-address: (buff 32),
}
bool
)
(define-map gas-usage-map
uint
uint
)
(define-public (init
(this-chain-id_ uint)
(gas-oracle principal)
)
(begin
(asserts! (is-none (var-get this-chain-id)) err-already-initialized)
(var-set this-chain-id (some this-chain-id_))
(var-set gas-oracle-principal (some gas-oracle))
(only-owner)
)
)
(define-public (swap-and-bridge
(pool-ref <pool-trait>)
(ft-ref <ft-trait>)
(messenger-ref <messenger-trait>)
(gas-oracle-ref <gas-oracle-trait>)
(amount uint)
(recipient (buff 32))
(destination-chain-id uint)
(receive-token (buff 32))
(nonce (buff 32))
(messenger-id uint)
(fee-native-amount uint)
(fee-token-amount uint)
)
(begin
(try! (assert-can-swap))
(try! (assert-gas-oracle gas-oracle-ref))
(try! (assert-messenger messenger-ref messenger-id))
(try! (assert-not-empty-buff-32 recipient))
(try! (assert-not-empty-buff-32 receive-token))
(try! (assert-not-empty-buff-32 nonce))
(asserts! (> amount fee-token-amount) err-deposit-amount-too-small)
(let (
(fee-tokens-in-native (try! (convert-bridging-fee-in-tokens-to-native ft-ref gas-oracle-ref
fee-token-amount
)))
(bridging-fee (+ fee-tokens-in-native fee-native-amount))
(amount-after-fee (- amount fee-token-amount))
(vusd-amount (try! (send-and-swap-to-vusd pool-ref ft-ref contract-caller amount-after-fee)))
)
(if (> fee-token-amount u0)
(try! (contract-call? ft-ref transfer fee-token-amount contract-caller
(as-contract contract-caller) none
))
true
)
(if (> fee-native-amount u0)
(try! (stx-transfer? fee-native-amount contract-caller
(as-contract contract-caller)
))
true
)
(print {
event: "TokensSent",
token: (contract-of ft-ref),
sender: contract-caller,
amount: vusd-amount,
recipient: recipient,
destinationChainId: destination-chain-id,
receiveToken: receive-token,
nonce: nonce,
messenger: messenger-id,
})
(let (
(send-fee (try! (send-tokens messenger-ref gas-oracle-ref vusd-amount recipient
destination-chain-id receive-token nonce messenger-id bridging-fee
)))
(bridge-tx-cost (get bridge-tx-cost send-fee))
(message-tx-cost (get message-tx-cost send-fee))
)
(print {
event: "ReceiveFee",
bridgeTransactionCost: bridge-tx-cost,
messageTransactionCost: message-tx-cost,
userPayNative: fee-native-amount,
userPayTokens: fee-token-amount,
totalFeeNative: bridging-fee,
feeTokenInNative: fee-tokens-in-native,
})
(ok true)
)
)
)
)
(define-public (receive-tokens
(messenger-ref <messenger-trait>)
(amount uint)
(recipient principal)
(source-chain-id uint)
(pool-ref <pool-trait>)
(ft-ref <ft-trait>)
(nonce (buff 32))
(messenger-id uint)
(receive-amount-min uint)
(extra-gas uint)
)
(begin
(try! (assert-can-swap))
(try! (assert-messenger messenger-ref messenger-id))
(let (
(another-bridge (unwrap! (map-get? other-bridges source-chain-id)
err-bridge-source-not-registered
))
(token-address (contract-of ft-ref))
(token-principal-hash (principal-hash token-address))
(recipient-buff32 (principal-to-buff32 recipient))
(this-chain-id_ (unwrap! (var-get this-chain-id) err-not-initialized))
(message-hash (try! (hash-message amount recipient-buff32 source-chain-id this-chain-id_
token-principal-hash nonce messenger-id
)))
(message-hash-with-sender (try! (contract-call? messenger-ref hash-with-sender-address message-hash
another-bridge
)))
(has-messenger-receive (unwrap-panic (contract-call? messenger-ref is-message-received
message-hash-with-sender
)))
(receive-amount (try! (receive-and-swap-from-vusd pool-ref ft-ref recipient amount
receive-amount-min
)))
)
(asserts! (is-none (map-get? processed-messages message-hash-with-sender))
err-already-processed
)
(map-set processed-messages message-hash-with-sender true)
(asserts! has-messenger-receive err-no-message)
(print {
event: "TokensReceived",
token: (contract-of ft-ref),
extraGasAmount: extra-gas,
amount: receive-amount,
recipient: recipient,
nonce: nonce,
messenger: messenger-id,
message: message-hash-with-sender,
})
(if (> extra-gas u0)
(match (stx-transfer? extra-gas contract-caller recipient)
value (ok true)
err-value (ok true)
)
(ok true)
)
)
)
)
(define-public (swap
(amount uint)
(send-pool-ref <pool-trait>)
(send-ft-ref <ft-trait>)
(receive-pool-ref <pool-trait>)
(receive-ft-ref <ft-trait>)
(recipient principal)
(receive-amount-min uint)
)
(let (
(vusd-amount (try! (send-and-swap-to-vusd send-pool-ref send-ft-ref contract-caller amount)))
(receive-amount (try! (receive-and-swap-from-vusd receive-pool-ref receive-ft-ref recipient
vusd-amount receive-amount-min
)))
)
(print {
event: "Swapped",
sendToken: (contract-of send-ft-ref),
receiveToken: (contract-of receive-ft-ref),
sender: contract-caller,
recipient: recipient,
sendAmount: amount,
receiveAmount: receive-amount,
})
(assert-can-swap)
)
)
(define-private (convert-bridging-fee-in-tokens-to-native
(ft-ref <ft-trait>)
(gas-oracle-ref <gas-oracle-trait>)
(fee-token-amount uint)
)
(let (
(token-address (contract-of ft-ref))
(this-chain-id_ (unwrap! (var-get this-chain-id) err-not-initialized))
(price (try! (contract-call? gas-oracle-ref get-price this-chain-id_)))
(token-principal-hash (principal-hash token-address))
(token-info (unwrap! (map-get? token-infos token-principal-hash)
err-token-not-supported
))
(bridging-fee-conversion-scaling-factor (get bridging-fee-conversion-scaling-factor token-info))
(fee (if (is-eq fee-token-amount u0)
u0
(/ (* fee-token-amount bridging-fee-conversion-scaling-factor) price)
))
)
(try! (assert-gas-oracle gas-oracle-ref))
(print {
event: "BridgingFeeFromTokens",
gas: fee,
})
(ok fee)
)
)
(define-private (receive-and-swap-from-vusd
(pool-ref <pool-trait>)
(ft-ref <ft-trait>)
(recipient principal)
(vusd-amount uint)
(receive-amount-min uint)
)
(let (
(token-address (contract-of ft-ref))
(token-principal-hash (principal-hash token-address))
(token-info (unwrap! (map-get? token-infos token-principal-hash)
err-token-not-supported
))
(pool-address (contract-of pool-ref))
)
(asserts! (is-eq pool-address (get pool token-info)) err-wrong-token-for-pool)
(contract-call? pool-ref swap-from-vusd ft-ref recipient vusd-amount
receive-amount-min (is-eq recipient (var-get rebalancer))
)
)
)
(define-private (send-and-swap-to-vusd
(pool-ref <pool-trait>)
(ft-ref <ft-trait>)
(user principal)
(amount uint)
)
(let (
(token-address (contract-of ft-ref))
(token-principal-hash (principal-hash token-address))
(token-info (unwrap! (map-get? token-infos token-principal-hash)
err-token-not-supported
))
(pool-address (contract-of pool-ref))
)
(asserts! (is-eq pool-address (get pool token-info)) err-wrong-token-for-pool)
(contract-call? pool-ref swap-to-vusd ft-ref user amount
(is-eq user (var-get rebalancer))
)
)
)
(define-private (send-tokens
(messenger-ref <messenger-trait>)
(gas-oracle-ref <gas-oracle-trait>) ;; validated in get-transaction-cost
(amount uint)
(recipient (buff 32))
(destination-chain-id uint)
(receive-token (buff 32))
(nonce (buff 32))
(messenger-id uint)
(bridging-fee uint)
)
(let (
(this-chain-id_ (unwrap! (var-get this-chain-id) err-not-initialized))
(message (try! (hash-message amount recipient this-chain-id_ destination-chain-id
receive-token nonce messenger-id
)))
(bridge-tx-cost (try! (get-transaction-cost gas-oracle-ref destination-chain-id)))
(message-tx-cost (try! (as-contract (contract-call? messenger-ref send-message gas-oracle-ref message))))
)
(asserts! (not (is-eq destination-chain-id this-chain-id_))
err-wrong-destination-chain
)
(asserts!
(is-eq
(map-get? other-bridge-tokens {
chain-id: destination-chain-id,
token-address: receive-token,
})
(some true)
)
err-unknown-chain-or-token
)
(asserts! (is-none (map-get? sent-messages message)) err-already-sent)
(asserts! (>= bridging-fee (+ bridge-tx-cost message-tx-cost))
err-not-enough-fee
)
(map-set sent-messages message true)
(ok {
bridge-tx-cost: bridge-tx-cost,
message-tx-cost: message-tx-cost,
})
)
)
;; ============================ ADMIN ============================
(define-public (add-pool
(pool-ref <pool-trait>)
(ft-ref <ft-trait>)
)
(let (
(toke-decimals (unwrap-panic (contract-call? ft-ref get-decimals)))
(token-address (contract-of ft-ref))
(token-address-from-pool (try! (contract-call? pool-ref get-token-address)))
(pool-address (contract-of pool-ref))
(bridging-fee-conversion-scaling-factor (pow u10 (+ (- oracle-precision toke-decimals) chain-precision)))
(from-gas-oracle-scaling-factor (pow u10 (- oracle-precision toke-decimals)))
(token-principal-hash (principal-hash token-address))
)
(asserts! (is-eq token-address-from-pool token-address)
err-wrong-token-for-pool
)
(map-set token-infos token-principal-hash {
pool: pool-address,
token: token-address,
from-gas-oracle-scaling-factor: from-gas-oracle-scaling-factor,
bridging-fee-conversion-scaling-factor: bridging-fee-conversion-scaling-factor,
})
(only-owner)
)
)
(define-public (register-bridge
(chain-id_ uint)
(bridge-address (buff 32))
)
(begin
(map-set other-bridges chain-id_ bridge-address)
(only-owner)
)
)
(define-public (remove-bridge (chain-id_ uint))
(begin
(map-delete other-bridges chain-id_)
(only-owner)
)
)
(define-public (add-bridge-token
(chain-id_ uint)
(token-address (buff 32))
)
(begin
(map-set other-bridge-tokens {
chain-id: chain-id_,
token-address: token-address,
}
true
)
(only-owner)
)
)
(define-public (remove-bridge-token
(chain-id_ uint)
(token-address (buff 32))
)
(begin
(map-delete other-bridge-tokens {
chain-id: chain-id_,
token-address: token-address,
})
(only-owner)
)
)
(define-public (withdraw-gas-tokens (amount uint))
(let ((caller contract-caller))
(try! (as-contract (stx-transfer? amount tx-sender caller)))
(only-owner)
)
)
(define-public (withdraw-bridging-fee-in-tokens
(ft-ref <ft-trait>)
(amount uint)
)
(let ((caller contract-caller))
(try! (as-contract (contract-call? ft-ref transfer amount tx-sender caller none)))
(only-owner)
)
)
(define-public (start-swap)
(begin
(var-set can-swap true)
(only-owner)
)
)
(define-public (stop-swap)
(begin
(var-set can-swap false)
(only-stop-authority)
)
)
(define-public (set-stop-authority (new-authority principal))
(begin
(var-set stop-authority new-authority)
(only-owner)
)
)
(define-public (set-rebalancer (new-rebalancer principal))
(begin
(var-set rebalancer new-rebalancer)
(only-owner)
)
)
(define-public (set-messenger
(messenger-id uint)
(new-messenger principal)
)
(begin
(map-set messengers messenger-id new-messenger)
(only-owner)
)
)
(define-public (remove-messenger (messenger-id uint))
(begin
(map-delete messengers messenger-id)
(only-owner)
)
)
;; ============================ VIEW =============================
(define-read-only (get-this-chain-id)
(unwrap-panic (var-get this-chain-id))
)
(define-read-only (get-gas-oracle-address)
(unwrap-panic (var-get gas-oracle-principal))
)
(define-read-only (get-owner)
(var-get owner)
)
(define-read-only (get-stop-authority)
(var-get stop-authority)
)
(define-read-only (get-rebalancer)
(var-get rebalancer)
)
(define-read-only (is-swap-enabled)
(var-get can-swap)
)
(define-read-only (get-sent-message-status (message-hash (buff 32)))
(ok (is-eq (map-get? sent-messages message-hash) (some true)))
)
(define-read-only (get-processed-message-status (message-hash (buff 32)))
(ok (is-eq (map-get? processed-messages message-hash) (some true)))
)
(define-read-only (get-messenger (messenger-id uint))
(ok (unwrap! (map-get? messengers messenger-id) err-messenger-not-registered))
)
(define-read-only (get-other-bridge (chain-id_ uint))
(ok (unwrap! (map-get? other-bridges chain-id_) err-bridge-source-not-registered))
)
(define-read-only (is-bridge-token-supported
(chain-id_ uint)
(token-address (buff 32))
)
(ok (is-eq
(map-get? other-bridge-tokens {
chain-id: chain-id_,
token-address: token-address,
})
(some true)
))
)
(define-read-only (get-token-info (token-principal-hash (buff 32)))
(ok (unwrap! (map-get? token-infos token-principal-hash) err-token-not-supported))
)
;; ========================== GAS USAGE ==========================
(define-public (set-gas-usage
(chain-id_ uint)
(gas uint)
)
(begin
(map-set gas-usage-map chain-id_ gas)
(only-owner)
)
)
(define-read-only (get-gas-usage (chain-id_ uint))
(match (map-get? gas-usage-map chain-id_)
gas-usage (ok gas-usage)
err-gas-usage-not-set
)
)
(define-public (get-transaction-cost
(gas-oracle-ref <gas-oracle-trait>)
(chain-id_ uint)
)
(begin
(try! (assert-gas-oracle gas-oracle-ref))
(let (
(gas-usage (try! (get-gas-usage chain-id_)))
(tx-const (try! (contract-call? gas-oracle-ref get-transaction-gas-cost-in-native-token
chain-id_ gas-usage
)))
)
(ok (if (is-eq tx-const u0)
u1
tx-const
))
)
)
)
;; ========================= OWNABLE ==========================
(define-public (set-owner (new-owner principal))
(begin
(try! (only-owner))
(var-set owner new-owner)
(ok true)
)
)
(define-private (only-owner)
(if (is-eq contract-caller (var-get owner))
(ok true)
err-unauthorized
)
)
(define-private (only-stop-authority)
(if (is-eq contract-caller (var-get stop-authority))
(ok true)
err-unauthorized
)
)
;; ========================= ASSERTS ==========================
(define-private (assert-can-swap)
(if (var-get can-swap)
(ok true)
err-swap-prohibited
)
)
(define-private (assert-gas-oracle (gas-oracle-ref <gas-oracle-trait>))
(let (
(registered-gas-oracle (unwrap! (var-get gas-oracle-principal) err-not-initialized))
(gas-oracle-address (contract-of gas-oracle-ref))
)
(asserts! (is-eq registered-gas-oracle gas-oracle-address)
err-wrong-gas-oracle
)
(ok true)
)
)
(define-private (assert-messenger
(messenger-ref <messenger-trait>)
(messenger-id uint)
)
(let (
(registered-messenger (unwrap! (map-get? messengers messenger-id) err-messenger-not-registered))
(messenger-address (contract-of messenger-ref))
)
(asserts! (is-eq registered-messenger messenger-address) err-wrong-messenger)
(ok true)
)
)
(define-private (assert-not-empty-buff-32 (b (buff 32)))
(begin
(asserts! (is-eq (len b) u32) err-wrong-buffer-length)
(asserts! (not (is-eq b zero-bytes-32)) err-empty-buffer)
(ok true)
)
)
;; ========================= HELPERS ==========================
(define-read-only (hash-message
(amount uint)
(recipient (buff 32))
(source-chain-id uint)
(destination-chain-id uint)
(receive-token (buff 32))
(nonce (buff 32))
(messenger-id uint)
)
(let (
(msg1 (uint-to-buff-32 amount))
(msg2 (concat msg1 recipient))
(msg3 (concat msg2 (uint-to-buff-32 source-chain-id)))
(msg4 (concat msg3 receive-token))
(msg5 (concat msg4 nonce))
(msg (concat msg5 (uint-to-buff-1 messenger-id)))
(msg-hash (keccak256 msg))
(last-30-bytes (unwrap! (slice? msg-hash u2 u32) err-wrong-buffer-length))
(first-2-bytes (concat (uint-to-buff-1 source-chain-id)
(uint-to-buff-1 destination-chain-id)
))
)
(ok (unwrap! (as-max-len? (concat first-2-bytes last-30-bytes) u32)
err-wrong-msg-length
))
)
)
;; ========================= PRIVATE HELPERS ==========================
(define-private (principal-to-buff32 (value principal))
(concat zero-bytes-12
(get hash-bytes (unwrap-panic (principal-destruct? value)))
)
)
(define-private (uint-to-buff-32 (value uint))
(concat zero-bytes-16
(unwrap-panic (slice? (unwrap-panic (to-consensus-buff? value)) u1 u17))
)
)
(define-private (uint-to-buff-1 (value uint))
(unwrap-panic (slice? (unwrap-panic (to-consensus-buff? value)) u16 u17))
)
(define-private (principal-hash (value principal))
(let (
(dest (unwrap-panic (principal-destruct? value)))
(hash-bytes (get hash-bytes dest))
(name (get name dest))
(name-bytes (match name
v (string-to-buff v)
0x
))
)
(keccak256 (concat hash-bytes name-bytes))
)
)
(define-private (string-to-buff (str (string-ascii 40)))
(let (
(str-bytes (unwrap-panic (to-consensus-buff? str)))
(length (len str-bytes))
)
(unwrap-panic (slice? str-bytes u5 length))
)
)