;; Asset Manager Implementation
;; Traits
(use-trait ft-trait .sip-010-trait.sip-010-trait)
(use-trait connection-contract .connection-trait.connection-trait)
(use-trait limit-trait .rate-limit-trait.rate-limit-trait)
;; Constants
(define-constant err-not-admin (err u2100))
(define-constant err-not-connection-contract (err u2101))
(define-constant err-amount-invalid (err u2102))
(define-constant err-token-not-found (err u2103))
(define-constant err-withdraw-limit-exceeded (err u2104))
(define-constant err-already-initialized (err u2105))
(define-constant err-signature-verification-failed (err u2106))
(define-constant err-withdraw-token-mismatch (err u2107))
(define-constant err-withdraw-failed (err u2108))
(define-constant err-not-sent-from-hub (err u2109))
(define-constant err-not-hub-asset-manager (err u2110))
(define-constant err-not-rate-limit-contract (err u2111))
(define-constant err-invalid-chain-id (err u2112))
(define-constant err-rlp-length-invalid (err u2113))
(define-constant err-insufficient-balance (err u2114))
;; Representation Address of STX
(define-constant NATIVE_TOKEN 'ST000000000000000000002AMW42H.nativetoken)
;; ---------------------------------------------------------
;; Public Functions
;; ---------------------------------------------------------
;; @notice Initializes the asset manager contract.
;; @dev Sets connection, rate limit, hub chain ID, hub asset manager, and marks contract initialized.
;; @param connection The principal of the connection contract.
;; @param rate-limit The principal of the rate limit contract.
;; @param hub-chain-id The chain ID of the hub.
;; @param hub-asset-manager The hub asset manager address (byte buffer).
;; @return success True if initialization succeeds.
(define-public (init (connection principal) (rate-limit principal) (hub-chain-id uint) (hub-asset-manager (buff 256)))
(begin
(try! (is-admin))
(try! (is-not-initialized))
(asserts! (> hub-chain-id u0) err-invalid-chain-id)
(try! (contract-call? .asset-manager-state set-rate-limit-impl rate-limit))
(try! (contract-call? .asset-manager-state set-connection-impl connection))
(try! (contract-call? .asset-manager-state set-hub-chain-id hub-chain-id))
(try! (contract-call? .asset-manager-state set-hub-asset-manager hub-asset-manager))
(ok true)
)
)
;; @notice Sets the asset manager implementation contract - will upgrade asset manager to a new implementation.
;; @dev Only callable by the current admin
;; @param who Principal of the new asset manager implementation contract.
;; @return none
(define-public (set-asset-manager-impl (who principal))
(begin
(try! (is-admin))
(contract-call? .asset-manager-state set-asset-manager-impl who)
)
)
;; @notice Updates the connection contract address.
;; @dev Only callable by the current admin.
;; @param who Principal of the new connection contract.
(define-public (set-connection-impl (who principal))
(let
(
(current-connection-contract (contract-call? .asset-manager-state get-connection-impl))
)
(try! (is-admin))
(unwrap-panic (contract-call? .asset-manager-state set-connection-impl who))
(print {
event: "ConnectionUpdated",
old-connection: current-connection-contract,
new-connection: who,
})
(ok true)
)
)
;; @notice Updates the rate limit contract address.
;; @dev Only callable by the current admin.
;; @param who Principal of the new rate limit contract.
(define-public (set-rate-limit-impl (who principal))
(let
(
(current-rate-limit-contract (contract-call? .asset-manager-state get-rate-limit-impl))
)
(try! (is-admin))
(unwrap-panic (contract-call? .asset-manager-state set-rate-limit-impl who))
(print {
event: "RateLimitUpdated",
old-rate-limit: current-rate-limit-contract,
new-rate-limit: who,
})
(ok true)
)
)
;; @notice Initiates a token transfer to the hub chain.
;; @dev Locks the token amount in this contract, encodes transfer message, sends via connection contract.
;; @param token Optional token trait reference (or none for native token).
;; @param to-address Recipient address on destination chain (byte buffer).
;; @param amount Amount of tokens to transfer.
;; @param data Additional payload data.
;; @param connection The connection contract instance.
;; @return success True if transfer initiation succeeds.
(define-public (transfer (token (optional <ft-trait>)) (to-address (buff 256)) (amount uint) (data (buff 4096)) (connection <connection-contract>))
(begin
(asserts! (> amount u0) err-amount-invalid)
(let (
(current-connection-contract (contract-call? .asset-manager-state get-connection-impl))
(message {
token-addr: (unwrap-panic (principal-to-bytes (match token token-name (contract-of token-name) NATIVE_TOKEN))),
sender: (unwrap-panic (principal-to-bytes contract-caller)),
recipient: to-address,
amount: amount,
data: data
})
(encoded-message (unwrap-panic (encode-transfer message)))
(dst-chain-id (contract-call? .asset-manager-state get-hub-chain-id))
(dst-address (unwrap-panic (contract-call? .asset-manager-state get-hub-asset-manager)))
)
(unwrap-panic (contract-call? .asset-manager-state deposit token amount contract-caller))
(asserts! (is-eq (contract-of connection) current-connection-contract) err-not-connection-contract)
(try! (contract-call? .asset-manager-state send-message dst-chain-id dst-address encoded-message connection))
)
(ok true)
)
)
;; @notice Processes an incoming withdrawal message from the hub chain.
;; @dev Verifies signatures, source, rate limits, and token availability before withdrawal.
;; @param src-chain-id Source chain ID.
;; @param src-address Source asset manager address (byte buffer).
;; @param conn-sn Connection sequence number.
;; @param payload Encoded transfer payload.
;; @param signatures List of validator signatures.
;; @param connection The connection contract instance.
;; @param rate-limit The rate limit contract instance.
;; @param token Optional token trait reference (or none for native token).
;; @return success True if withdrawal succeeds.
(define-public (recv-message (src-chain-id uint) (src-address (buff 256)) (conn-sn uint) (payload (buff 4096)) (signatures (list 50 (buff 65))) (connection <connection-contract>) (rate-limit <limit-trait>) (token (optional <ft-trait>)))
(let (
(current-connection-contract (contract-call? .asset-manager-state get-connection-impl))
(rate-limit-contract (contract-call? .asset-manager-state get-rate-limit-impl))
)
;; Ensure the contracts connection and rate-limit are correct
(asserts! (is-eq (contract-of connection) current-connection-contract) err-not-connection-contract)
(asserts! (is-eq (contract-of rate-limit) rate-limit-contract) err-not-rate-limit-contract)
;; Ensure the message is sent from the hub chain and hub asset manager
(asserts! (is-eq src-chain-id (contract-call? .asset-manager-state get-hub-chain-id)) err-not-sent-from-hub)
(asserts! (is-eq src-address (unwrap-panic (contract-call? .asset-manager-state get-hub-asset-manager))) err-not-hub-asset-manager)
(try! (contract-call? .asset-manager-state verify-message src-chain-id src-address conn-sn payload signatures connection))
(let (
(message (unwrap-panic (decode-transfer payload)))
(token-address (get token-address message))
(recipient (get recipient message))
(amount (get amount message))
(token-principal (match token ft-token (contract-of ft-token) NATIVE_TOKEN))
(token-balance (unwrap-panic (contract-call? .asset-manager-state get-token-balance token)))
)
(asserts! (> amount u0) err-amount-invalid)
(asserts! (>= token-balance amount) err-insufficient-balance)
(try! (contract-call? rate-limit verify-withdraw token-principal amount))
(asserts! (is-eq token-address token-principal) err-withdraw-token-mismatch)
(unwrap! (contract-call? .asset-manager-state withdraw token amount recipient) err-withdraw-failed)
)
(ok true)
)
)
;; ---------------------------------------------------------
;; Read-Only Functions
;; ---------------------------------------------------------
;; @notice Reads the remaining cv needed to call recv-message from an encoded transfer payload.
;; @param payload Encoded transfer payload.
;; @return tuple containing cvs
(define-read-only (get-remaining (payload (buff 4096)))
(let (
(message (unwrap-panic (decode-transfer payload)))
(token-address (get token-address message))
(token-trait (if (is-eq token-address NATIVE_TOKEN) none (some token-address)))
)
{token-trait: token-trait}
))
;; Private Functions
;; @notice Checks if the contract is not yet initialized.
;; @return True if not initialized.
(define-read-only (is-not-initialized)
(ok (asserts! (is-eq (contract-call? .asset-manager-state get-hub-chain-id) u0) err-already-initialized))
)
;; @notice Converts a principal to its consensus buffer representation.
;; @param p The principal to convert.
;; @return buff Consensus buffer representation.
(define-private (principal-to-bytes (p principal))
(to-consensus-buff? p))
;; read only functions
;; @notice Checks if the caller is the admin.
;; @return True if caller is admin.
(define-read-only (is-admin)
(ok (asserts! (is-eq contract-caller (contract-call? .asset-manager-state get-admin)) err-not-admin))
)
;; ---------------------------------------------------------
;; Encoding / Decoding Helpers
;; ---------------------------------------------------------
(define-private (encode-transfer (message
{
token-addr: (buff 200),
sender: (buff 200),
recipient: (buff 500),
amount: uint,
data: (buff 4096)}
))
(let (
(token-address (get token-addr message))
(from (get sender message))
(to (get recipient message))
(amount (get amount message))
(data (get data message))
)
(ok (contract-call? .rlp-encode encode-arr
(list
(contract-call? .rlp-encode encode-buff token-address)
(contract-call? .rlp-encode encode-buff from)
(contract-call? .rlp-encode encode-buff to)
(contract-call? .rlp-encode encode-uint amount)
(contract-call? .rlp-encode encode-buff data)
)
))
)
)
(define-private (decode-transfer (data (buff 4096)))
(let (
(decoded (unwrap-panic (decode-transfer-raw data)))
(token-address (unwrap-panic (from-consensus-buff? principal (get token-address decoded))))
(from (get sender decoded))
(to (unwrap-panic (from-consensus-buff? principal (get recipient decoded))))
(amount (get amount decoded))
)
(ok {
token-address: token-address,
sender: from,
recipient: to,
amount: amount
})
)
)
(define-private (decode-transfer-raw (raw (buff 4096)))
(let (
(rlp-list (contract-call? .rlp-decode rlp-to-list raw))
(token-address (unwrap-panic (element-at? rlp-list u0)))
(from (unwrap-panic (element-at? rlp-list u1)))
(to (unwrap-panic (element-at? rlp-list u2)))
(amount (contract-call? .rlp-decode rlp-decode-uint rlp-list u3))
(dataa (unwrap-panic (element-at? rlp-list u4)))
)
(asserts! (is-eq (len rlp-list) u5) err-rlp-length-invalid)
(ok {
token-address: token-address,
sender: from,
recipient: to,
amount: amount,
data: dataa
})
)
)