From 2ec2247125af9ff07b9f6db50727f916c3a6989a Mon Sep 17 00:00:00 2001 From: Jason Schrader Date: Wed, 16 Apr 2025 14:46:41 -0700 Subject: [PATCH 01/12] feat: create new trait file for user agent acct --- .../aibtc-user-agent-account-traits.clar | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 contracts/aibtc-user-agent-account-traits.clar diff --git a/contracts/aibtc-user-agent-account-traits.clar b/contracts/aibtc-user-agent-account-traits.clar new file mode 100644 index 0000000..5212425 --- /dev/null +++ b/contracts/aibtc-user-agent-account-traits.clar @@ -0,0 +1,116 @@ +;; title: aibtc-smart-wallet-traits +;; version: 2.0.0 +;; summary: A collection of traits for user agent smart wallets. + +;; IMPORTS +(use-trait sip010-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait) +(use-trait dao-action-trait .aibtc-dao-traits-v3.action) +(use-trait dao-proposal-trait .aibtc-dao-traits-v3.proposal) +(use-trait dao-action-proposals-trait .aibtc-dao-traits-v3.action-proposals) +(use-trait dao-core-proposals-trait .aibtc-dao-traits-v3.core-proposals) +(use-trait dao-faktory-dex .aibtc-dao-traits-v3.faktory-dex) +(use-trait faktory-token .faktory-trait-v1.sip-010-trait) + +;; SMART WALLET TRAITS + +(define-trait aibtc-account ( + ;; deposit STX to the smart wallet + ;; @param amount amount of microSTX to deposit + ;; @returns (response bool uint) + (deposit-stx (uint) (response bool uint)) + ;; deposit FT to the smart wallet + ;; @param ft the fungible token contract + ;; @param amount amount of tokens to deposit + ;; @returns (response bool uint) + (deposit-ft ( uint) (response bool uint)) + ;; withdraw STX from the smart wallet (user only) + ;; @param amount amount of microSTX to withdraw + ;; @returns (response bool uint) + (withdraw-stx (uint) (response bool uint)) + ;; withdraw FT from the smart wallet (user only) + ;; @param ft the fungible token contract + ;; @param amount amount of tokens to withdraw + ;; @returns (response bool uint) + (withdraw-ft ( uint) (response bool uint)) + ;; approve an asset for deposit/withdrawal (user only) + ;; @param asset the asset contract principal + ;; @returns (response bool uint) + (approve-asset (principal) (response bool uint)) + ;; revoke approval for an asset (user only) + ;; @param asset the asset contract principal + ;; @returns (response bool uint) + (revoke-asset (principal) (response bool uint)) +)) + +(define-trait aibtc-proposals-v3 ( + ;; propose an action to the DAO (user or agent) + ;; @param action-proposals the action proposals contract + ;; @param action the action contract + ;; @param parameters encoded action parameters + ;; @returns (response bool uint) + (acct-propose-action ( (buff 2048) (optional (string-ascii 1024))) (response bool uint)) + ;; create a core proposal to the DAO (user or agent) + ;; @param core-proposals the core proposals contract + ;; @param proposal the proposal contract + ;; @returns (response bool uint) + (acct-create-proposal ( (optional (string-ascii 1024))) (response bool uint)) + ;; vote on an action proposal (user or agent) + ;; @param action-proposals the action proposals contract + ;; @param proposalId the proposal ID + ;; @param vote true for yes, false for no + ;; @returns (response bool uint) + (vote-on-action-proposal ( uint bool) (response bool uint)) + ;; vote on a core proposal (user or agent) + ;; @param core-proposals the core proposals contract + ;; @param proposal the proposal contract + ;; @param vote true for yes, false for no + ;; @returns (response bool uint) + (vote-on-core-proposal ( bool) (response bool uint)) + ;; conclude an action proposal (user or agent) + ;; @param action-proposals the action proposals contract + ;; @param proposalId the proposal ID + ;; @param action the action contract + ;; @returns (response bool uint) + (conclude-action-proposal ( uint ) (response bool uint)) + ;; conclude a core proposal (user or agent) + ;; @param core-proposals the core proposals contract + ;; @param proposal the proposal contract + ;; @returns (response bool uint) + (conclude-core-proposal ( ) (response bool uint)) +)) + +(define-trait dex-approval ( + ;; approve a dex for trading an asset + ;; @param faktory-dex the faktory dex contract + ;; @returns (response bool uint) + (acct-approve-dex () (response bool uint)) + ;; revoke approval for a dex + ;; @param faktory-dex the faktory dex contract + ;; @returns (response bool uint) + (acct-revoke-dex () (response bool uint)) +)) + +(define-trait faktory-buy-sell ( + ;; buy an asset from a faktory dex + ;; @param faktory-dex the faktory dex contract + ;; @param asset the asset contract principal + ;; @param amount amount of tokens to buy + ;; @returns (response bool uint) + (acct-buy-asset ( uint) (response bool uint)) + ;; sell an asset to a faktory dex + ;; @param faktory-dex the faktory dex contract + ;; @param asset the asset contract principal + ;; @param amount amount of tokens to sell + ;; @returns (response bool uint) + (acct-sell-asset ( uint) (response bool uint)) +)) + + +(define-trait bitflow-buy-sell ( + ;; buy an asset from a bitflow dex + ;; @param bitflow-dex the bitflow dex contract + ;; @param asset the asset contract principal + ;; @param amount amount of tokens to buy + ;; @returns (response bool uint) + +)) \ No newline at end of file From 70c6c52707663f011adb03c3faf8cc4e74676e65 Mon Sep 17 00:00:00 2001 From: Jason Schrader Date: Wed, 16 Apr 2025 15:48:58 -0700 Subject: [PATCH 02/12] refactor: remove old traits, add new version and rewrite contract --- Clarinet.toml | 10 +- contracts/aibtc-smart-wallet-traits-v2.clar | 103 ------------------ contracts/aibtc-smart-wallet-traits.clar | 103 ------------------ .../aibtc-user-agent-account-traits.clar | 12 +- ...let.clar => aibtc-user-agent-account.clar} | 91 +++++++++------- 5 files changed, 55 insertions(+), 264 deletions(-) delete mode 100644 contracts/aibtc-smart-wallet-traits-v2.clar delete mode 100644 contracts/aibtc-smart-wallet-traits.clar rename contracts/{aibtc-user-agent-smart-wallet.clar => aibtc-user-agent-account.clar} (75%) diff --git a/Clarinet.toml b/Clarinet.toml index 242ff00..d8b1e58 100644 --- a/Clarinet.toml +++ b/Clarinet.toml @@ -52,15 +52,15 @@ path = 'contracts/aibtcdev-airdrop-2.clar' clarity_version = 2 epoch = 3.0 -# smart wallet and traits +# agent account and traits -[contracts.aibtc-smart-wallet-traits] -path = 'contracts/aibtc-smart-wallet-traits.clar' +[contracts.aibtc-user-agent-account-traits] +path = 'contracts/aibtc-user-agent-account-traits.clar' clarity_version = 3 epoch = 3.1 -[contracts.aibtc-user-agent-smart-wallet] -path = 'contracts/aibtc-user-agent-smart-wallet.clar' +[contracts.aibtc-user-agent-account] +path = 'contracts/aibtc-user-agent-account.clar' clarity_version = 3 epoch = 3.1 diff --git a/contracts/aibtc-smart-wallet-traits-v2.clar b/contracts/aibtc-smart-wallet-traits-v2.clar deleted file mode 100644 index ca49cac..0000000 --- a/contracts/aibtc-smart-wallet-traits-v2.clar +++ /dev/null @@ -1,103 +0,0 @@ -;; title: aibtc-smart-wallet-traits -;; version: 2.0.0 -;; summary: A collection of traits for user agent smart wallets. - -;; IMPORTS -(use-trait sip010-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait) -(use-trait dao-action-trait .aibtc-dao-traits-v3.action) -(use-trait dao-proposal-trait .aibtc-dao-traits-v3.proposal) -(use-trait dao-action-proposals-trait .aibtc-dao-traits-v3.action-proposals) -(use-trait dao-core-proposals-trait .aibtc-dao-traits-v3.core-proposals) -(use-trait dao-faktory-dex .aibtc-dao-traits-v3.faktory-dex) -(use-trait faktory-token .faktory-trait-v1.sip-010-trait) - -;; SMART WALLET TRAITS - -(define-trait aibtc-smart-wallet ( - ;; deposit STX to the smart wallet - ;; @param amount amount of microSTX to deposit - ;; @returns (response bool uint) - (deposit-stx (uint) (response bool uint)) - ;; deposit FT to the smart wallet - ;; @param ft the fungible token contract - ;; @param amount amount of tokens to deposit - ;; @returns (response bool uint) - (deposit-ft ( uint) (response bool uint)) - ;; withdraw STX from the smart wallet (user only) - ;; @param amount amount of microSTX to withdraw - ;; @returns (response bool uint) - (withdraw-stx (uint) (response bool uint)) - ;; withdraw FT from the smart wallet (user only) - ;; @param ft the fungible token contract - ;; @param amount amount of tokens to withdraw - ;; @returns (response bool uint) - (withdraw-ft ( uint) (response bool uint)) - ;; approve an asset for deposit/withdrawal (user only) - ;; @param asset the asset contract principal - ;; @returns (response bool uint) - (approve-asset (principal) (response bool uint)) - ;; revoke approval for an asset (user only) - ;; @param asset the asset contract principal - ;; @returns (response bool uint) - (revoke-asset (principal) (response bool uint)) -)) - -(define-trait aibtc-proposals-v3 ( - ;; propose an action to the DAO (user or agent) - ;; @param action-proposals the action proposals contract - ;; @param action the action contract - ;; @param parameters encoded action parameters - ;; @returns (response bool uint) - (proxy-propose-action ( (buff 2048) (optional (string-ascii 1024))) (response bool uint)) - ;; create a core proposal to the DAO (user or agent) - ;; @param core-proposals the core proposals contract - ;; @param proposal the proposal contract - ;; @returns (response bool uint) - (proxy-create-proposal ( (optional (string-ascii 1024))) (response bool uint)) - ;; vote on an action proposal (user or agent) - ;; @param action-proposals the action proposals contract - ;; @param proposalId the proposal ID - ;; @param vote true for yes, false for no - ;; @returns (response bool uint) - (vote-on-action-proposal ( uint bool) (response bool uint)) - ;; vote on a core proposal (user or agent) - ;; @param core-proposals the core proposals contract - ;; @param proposal the proposal contract - ;; @param vote true for yes, false for no - ;; @returns (response bool uint) - (vote-on-core-proposal ( bool) (response bool uint)) - ;; conclude an action proposal (user or agent) - ;; @param action-proposals the action proposals contract - ;; @param proposalId the proposal ID - ;; @param action the action contract - ;; @returns (response bool uint) - (conclude-action-proposal ( uint ) (response bool uint)) - ;; conclude a core proposal (user or agent) - ;; @param core-proposals the core proposals contract - ;; @param proposal the proposal contract - ;; @returns (response bool uint) - (conclude-core-proposal ( ) (response bool uint)) -)) - -(define-trait faktory-buy-sell ( - ;; buy an asset from a faktory dex - ;; @param faktory-dex the faktory dex contract - ;; @param asset the asset contract principal - ;; @param amount amount of tokens to buy - ;; @returns (response bool uint) - (buy-asset ( uint) (response bool uint)) - ;; sell an asset to a faktory dex - ;; @param faktory-dex the faktory dex contract - ;; @param asset the asset contract principal - ;; @param amount amount of tokens to sell - ;; @returns (response bool uint) - (sell-asset ( uint) (response bool uint)) - ;; approve a dex for trading an asset - ;; @param faktory-dex the faktory dex contract - ;; @returns (response bool uint) - (approve-dex () (response bool uint)) - ;; revoke approval for a dex - ;; @param faktory-dex the faktory dex contract - ;; @returns (response bool uint) - (revoke-dex () (response bool uint)) -)) diff --git a/contracts/aibtc-smart-wallet-traits.clar b/contracts/aibtc-smart-wallet-traits.clar deleted file mode 100644 index 6db1c25..0000000 --- a/contracts/aibtc-smart-wallet-traits.clar +++ /dev/null @@ -1,103 +0,0 @@ -;; title: aibtc-smart-wallet-traits -;; version: 1.0.0 -;; summary: A collection of traits for user agent smart wallets. - -;; IMPORTS -(use-trait sip010-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait) -(use-trait dao-action-trait .aibtc-dao-traits-v3.action) -(use-trait dao-proposal-trait .aibtc-dao-traits-v3.proposal) -(use-trait dao-action-proposals-trait .aibtc-dao-traits-v3.action-proposals) -(use-trait dao-core-proposals-trait .aibtc-dao-traits-v3.core-proposals) -(use-trait dao-faktory-dex .aibtc-dao-traits-v3.faktory-dex) -(use-trait faktory-token .faktory-trait-v1.sip-010-trait) - -;; SMART WALLET TRAITS - -(define-trait aibtc-smart-wallet ( - ;; deposit STX to the smart wallet - ;; @param amount amount of microSTX to deposit - ;; @returns (response bool uint) - (deposit-stx (uint) (response bool uint)) - ;; deposit FT to the smart wallet - ;; @param ft the fungible token contract - ;; @param amount amount of tokens to deposit - ;; @returns (response bool uint) - (deposit-ft ( uint) (response bool uint)) - ;; withdraw STX from the smart wallet (user only) - ;; @param amount amount of microSTX to withdraw - ;; @returns (response bool uint) - (withdraw-stx (uint) (response bool uint)) - ;; withdraw FT from the smart wallet (user only) - ;; @param ft the fungible token contract - ;; @param amount amount of tokens to withdraw - ;; @returns (response bool uint) - (withdraw-ft ( uint) (response bool uint)) - ;; approve an asset for deposit/withdrawal (user only) - ;; @param asset the asset contract principal - ;; @returns (response bool uint) - (approve-asset (principal) (response bool uint)) - ;; revoke approval for an asset (user only) - ;; @param asset the asset contract principal - ;; @returns (response bool uint) - (revoke-asset (principal) (response bool uint)) -)) - -(define-trait aibtc-proposals-v2 ( - ;; propose an action to the DAO (user or agent) - ;; @param action-proposals the action proposals contract - ;; @param action the action contract - ;; @param parameters encoded action parameters - ;; @returns (response bool uint) - (proxy-propose-action ( (buff 2048) (optional (string-ascii 1024))) (response bool uint)) - ;; create a core proposal to the DAO (user or agent) - ;; @param core-proposals the core proposals contract - ;; @param proposal the proposal contract - ;; @returns (response bool uint) - (proxy-create-proposal ( (optional (string-ascii 1024))) (response bool uint)) - ;; vote on an action proposal (user or agent) - ;; @param action-proposals the action proposals contract - ;; @param proposalId the proposal ID - ;; @param vote true for yes, false for no - ;; @returns (response bool uint) - (vote-on-action-proposal ( uint bool) (response bool uint)) - ;; vote on a core proposal (user or agent) - ;; @param core-proposals the core proposals contract - ;; @param proposal the proposal contract - ;; @param vote true for yes, false for no - ;; @returns (response bool uint) - (vote-on-core-proposal ( bool) (response bool uint)) - ;; conclude an action proposal (user or agent) - ;; @param action-proposals the action proposals contract - ;; @param proposalId the proposal ID - ;; @param action the action contract - ;; @returns (response bool uint) - (conclude-action-proposal ( uint ) (response bool uint)) - ;; conclude a core proposal (user or agent) - ;; @param core-proposals the core proposals contract - ;; @param proposal the proposal contract - ;; @returns (response bool uint) - (conclude-core-proposal ( ) (response bool uint)) -)) - -(define-trait faktory-buy-sell ( - ;; buy an asset from a faktory dex - ;; @param faktory-dex the faktory dex contract - ;; @param asset the asset contract principal - ;; @param amount amount of tokens to buy - ;; @returns (response bool uint) - (buy-asset ( uint) (response bool uint)) - ;; sell an asset to a faktory dex - ;; @param faktory-dex the faktory dex contract - ;; @param asset the asset contract principal - ;; @param amount amount of tokens to sell - ;; @returns (response bool uint) - (sell-asset ( uint) (response bool uint)) - ;; approve a dex for trading an asset - ;; @param faktory-dex the faktory dex contract - ;; @returns (response bool uint) - (approve-dex () (response bool uint)) - ;; revoke approval for a dex - ;; @param faktory-dex the faktory dex contract - ;; @returns (response bool uint) - (revoke-dex () (response bool uint)) -)) diff --git a/contracts/aibtc-user-agent-account-traits.clar b/contracts/aibtc-user-agent-account-traits.clar index 5212425..e2513c2 100644 --- a/contracts/aibtc-user-agent-account-traits.clar +++ b/contracts/aibtc-user-agent-account-traits.clar @@ -79,7 +79,7 @@ (conclude-core-proposal ( ) (response bool uint)) )) -(define-trait dex-approval ( +(define-trait faktory-dex-approval ( ;; approve a dex for trading an asset ;; @param faktory-dex the faktory dex contract ;; @returns (response bool uint) @@ -104,13 +104,3 @@ ;; @returns (response bool uint) (acct-sell-asset ( uint) (response bool uint)) )) - - -(define-trait bitflow-buy-sell ( - ;; buy an asset from a bitflow dex - ;; @param bitflow-dex the bitflow dex contract - ;; @param asset the asset contract principal - ;; @param amount amount of tokens to buy - ;; @returns (response bool uint) - -)) \ No newline at end of file diff --git a/contracts/aibtc-user-agent-smart-wallet.clar b/contracts/aibtc-user-agent-account.clar similarity index 75% rename from contracts/aibtc-user-agent-smart-wallet.clar rename to contracts/aibtc-user-agent-account.clar index 4cf9d94..1de7ef9 100644 --- a/contracts/aibtc-user-agent-smart-wallet.clar +++ b/contracts/aibtc-user-agent-account.clar @@ -1,11 +1,12 @@ -;; title: aibtc-user-agent-smart-wallet +;; title: aibtc-user-agent-account ;; version: 1.0.0 -;; summary: A smart wallet contract between a user and an agent for managing assets and DAO interactions +;; summary: A special account contract between a user and an agent for managing assets and DAO interactions. Only the user can withdraw funds. ;; traits -(impl-trait .aibtc-smart-wallet-traits.aibtc-smart-wallet) -(impl-trait .aibtc-smart-wallet-traits.aibtc-proposals-v2) -(impl-trait .aibtc-smart-wallet-traits.faktory-buy-sell) +(impl-trait .aibtc-user-agent-account-traits.aibtc-account) +(impl-trait .aibtc-user-agent-account-traits.aibtc-proposals-v3) +(impl-trait .aibtc-user-agent-account-traits.faktory-dex-approval) +(impl-trait .aibtc-user-agent-account-traits.faktory-buy-sell) (use-trait ft-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait) (use-trait action-trait .aibtc-dao-traits-v3.action) (use-trait proposal-trait .aibtc-dao-traits-v3.proposal) @@ -18,15 +19,22 @@ (define-constant DEPLOYED_BURN_BLOCK burn-block-height) (define-constant DEPLOYED_STACKS_BLOCK stacks-block-height) (define-constant SELF (as-contract tx-sender)) -(define-constant USER 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM) ;; user (smart wallet owner) -(define-constant AGENT 'ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG) ;; agent (proposal voter) -;; Pre-approved contracts +;; owner and agent addresses +;; /g/ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM/account_owner +(define-constant ACCOUNT_OWNER 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM) ;; owner (user/creator of account, full access) +;; /g/ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG/account_agent +(define-constant ACCOUNT_AGENT 'ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG) ;; agent (can only take approved actions) + +;; pre-approved contracts +;; /g/STV9K21TBFAK4KNRJXF5DFP8N7W46G4V9RJ5XDY2.sbtc-token/sbtc_contract (define-constant SBTC_TOKEN 'STV9K21TBFAK4KNRJXF5DFP8N7W46G4V9RJ5XDY2.sbtc-token) ;; sBTC token +;; /g/.aibtc-token/dao_token_contract (define-constant DAO_TOKEN .aibtc-token) ;; DAO token +;; /g/.aibtc-token-dex/dao_token_dex_contract (define-constant DAO_TOKEN_DEX .aibtc-token-dex) ;; DAO token DEX -;; Error codes +;; error codes (define-constant ERR_UNAUTHORIZED (err u9000)) (define-constant ERR_UNKNOWN_ASSET (err u9001)) (define-constant ERR_OPERATION_FAILED (err u9002)) @@ -37,7 +45,6 @@ (define-map ApprovedDexes principal bool) ;; data vars - (define-data-var agentCanBuySell bool false) ;; public functions @@ -78,23 +85,23 @@ (define-public (withdraw-stx (amount uint)) (begin - (asserts! (is-user) ERR_UNAUTHORIZED) + (asserts! (is-owner) ERR_UNAUTHORIZED) (print { notification: "withdraw-stx", payload: { amount: amount, sender: SELF, caller: contract-caller, - recipient: USER + recipient: ACCOUNT_OWNER } }) - (as-contract (stx-transfer? amount SELF USER)) + (as-contract (stx-transfer? amount SELF ACCOUNT_OWNER)) ) ) (define-public (withdraw-ft (ft ) (amount uint)) (begin - (asserts! (is-user) ERR_UNAUTHORIZED) + (asserts! (is-owner) ERR_UNAUTHORIZED) (asserts! (is-approved-asset (contract-of ft)) ERR_UNKNOWN_ASSET) (print { notification: "withdraw-ft", @@ -103,16 +110,16 @@ assetContract: (contract-of ft), sender: SELF, caller: contract-caller, - recipient: USER + recipient: ACCOUNT_OWNER } }) - (as-contract (contract-call? ft transfer amount SELF USER none)) + (as-contract (contract-call? ft transfer amount SELF ACCOUNT_OWNER none)) ) ) (define-public (approve-asset (asset principal)) (begin - (asserts! (is-user) ERR_UNAUTHORIZED) + (asserts! (is-owner) ERR_UNAUTHORIZED) (print { notification: "approve-asset", payload: { @@ -128,7 +135,7 @@ (define-public (revoke-asset (asset principal)) (begin - (asserts! (is-user) ERR_UNAUTHORIZED) + (asserts! (is-owner) ERR_UNAUTHORIZED) (print { notification: "revoke-asset", payload: { @@ -144,11 +151,11 @@ ;; DAO Interaction Functions -(define-public (proxy-propose-action (action-proposals ) (action ) (parameters (buff 2048)) (memo (optional (string-ascii 1024)))) +(define-public (acct-propose-action (action-proposals ) (action ) (parameters (buff 2048)) (memo (optional (string-ascii 1024)))) (begin (asserts! (is-authorized) ERR_UNAUTHORIZED) (print { - notification: "proxy-propose-action", + notification: "acct-propose-action", payload: { proposalContract: (contract-of action-proposals), action: (contract-of action), @@ -161,11 +168,11 @@ ) ) -(define-public (proxy-create-proposal (core-proposals ) (proposal ) (memo (optional (string-ascii 1024)))) +(define-public (acct-create-proposal (core-proposals ) (proposal ) (memo (optional (string-ascii 1024)))) (begin (asserts! (is-authorized) ERR_UNAUTHORIZED) (print { - notification: "proxy-create-proposal", + notification: "acct-create-proposal", payload: { proposalContract: (contract-of core-proposals), proposal: (contract-of proposal), @@ -246,12 +253,12 @@ ;; Faktory DEX Trading Functions -(define-public (buy-asset (faktory-dex ) (asset ) (amount uint)) +(define-public (acct-buy-asset (faktory-dex ) (asset ) (amount uint)) (begin (asserts! (buy-sell-allowed) ERR_BUY_SELL_NOT_ALLOWED) (asserts! (is-approved-dex (contract-of faktory-dex)) ERR_UNKNOWN_ASSET) (print { - notification: "buy-asset", + notification: "acct-buy-asset", payload: { dexContract: (contract-of faktory-dex), asset: (contract-of asset), @@ -264,12 +271,12 @@ ) ) -(define-public (sell-asset (faktory-dex ) (asset ) (amount uint)) +(define-public (acct-sell-asset (faktory-dex ) (asset ) (amount uint)) (begin (asserts! (buy-sell-allowed) ERR_BUY_SELL_NOT_ALLOWED) (asserts! (is-approved-dex (contract-of faktory-dex)) ERR_UNKNOWN_ASSET) (print { - notification: "sell-asset", + notification: "acct-sell-asset", payload: { dexContract: (contract-of faktory-dex), asset: (contract-of asset), @@ -282,11 +289,11 @@ ) ) -(define-public (approve-dex (faktory-dex )) +(define-public (acct-approve-dex (faktory-dex )) (begin - (asserts! (is-user) ERR_UNAUTHORIZED) + (asserts! (is-owner) ERR_UNAUTHORIZED) (print { - notification: "approve-dex", + notification: "acct-approve-dex", payload: { dexContract: (contract-of faktory-dex), approved: true, @@ -298,11 +305,11 @@ ) ) -(define-public (revoke-dex (faktory-dex )) +(define-public (acct-revoke-dex (faktory-dex )) (begin - (asserts! (is-user) ERR_UNAUTHORIZED) + (asserts! (is-owner) ERR_UNAUTHORIZED) (print { - notification: "revoke-dex", + notification: "acct-revoke-dex", payload: { dexContract: (contract-of faktory-dex), approved: false, @@ -316,7 +323,7 @@ (define-public (set-agent-can-buy-sell (canBuySell bool)) (begin - (asserts! (is-user) ERR_UNAUTHORIZED) + (asserts! (is-owner) ERR_UNAUTHORIZED) (print { notification: "set-agent-can-buy-sell", payload: { @@ -345,9 +352,9 @@ (define-read-only (get-configuration) { - agent: AGENT, - user: USER, - smartWallet: SELF, + account: SELF, + agent: ACCOUNT_AGENT, + owner: ACCOUNT_OWNER, daoToken: DAO_TOKEN, daoTokenDex: DAO_TOKEN_DEX, sbtcToken: SBTC_TOKEN, @@ -357,19 +364,19 @@ ;; private functions (define-private (is-authorized) - (or (is-eq contract-caller USER) (is-eq contract-caller AGENT)) + (or (is-eq contract-caller ACCOUNT_OWNER) (is-eq contract-caller ACCOUNT_AGENT)) ) -(define-private (is-user) - (is-eq contract-caller USER) +(define-private (is-owner) + (is-eq contract-caller ACCOUNT_OWNER) ) (define-private (is-agent) - (is-eq contract-caller AGENT) + (is-eq contract-caller ACCOUNT_AGENT) ) (define-private (buy-sell-allowed) - (or (is-user) (and (is-agent) (var-get agentCanBuySell))) + (or (is-owner) (and (is-agent) (var-get agentCanBuySell))) ) ;; initialize approved contracts @@ -379,6 +386,6 @@ ;; print creation event (print { - notification: "smart-wallet-created", + notification: "user-agent-account-created", payload: (get-configuration) }) From e43303d52be4df28c42994d4af12c396333e1a6c Mon Sep 17 00:00:00 2001 From: Jason Schrader Date: Wed, 16 Apr 2025 15:52:03 -0700 Subject: [PATCH 03/12] fix: rename tests to match new contract name --- .../aibtc-user-agent-account-traits.clar | 20 +++++++++---------- ...st.ts => aibtc-user-agent-account.test.ts} | 0 2 files changed, 10 insertions(+), 10 deletions(-) rename tests/{aibtc-user-agent-smart-wallet.test.ts => aibtc-user-agent-account.test.ts} (100%) diff --git a/contracts/aibtc-user-agent-account-traits.clar b/contracts/aibtc-user-agent-account-traits.clar index e2513c2..1e948ec 100644 --- a/contracts/aibtc-user-agent-account-traits.clar +++ b/contracts/aibtc-user-agent-account-traits.clar @@ -1,6 +1,6 @@ -;; title: aibtc-smart-wallet-traits -;; version: 2.0.0 -;; summary: A collection of traits for user agent smart wallets. +;; title: aibtc-user-agent-account-traits +;; version: 1.0.0 +;; summary: A collection of traits for user agent accounts. ;; IMPORTS (use-trait sip010-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait) @@ -11,32 +11,32 @@ (use-trait dao-faktory-dex .aibtc-dao-traits-v3.faktory-dex) (use-trait faktory-token .faktory-trait-v1.sip-010-trait) -;; SMART WALLET TRAITS +;; ACCOUNT TRAITS (define-trait aibtc-account ( - ;; deposit STX to the smart wallet + ;; deposit STX to the agent account ;; @param amount amount of microSTX to deposit ;; @returns (response bool uint) (deposit-stx (uint) (response bool uint)) - ;; deposit FT to the smart wallet + ;; deposit FT to the agent account ;; @param ft the fungible token contract ;; @param amount amount of tokens to deposit ;; @returns (response bool uint) (deposit-ft ( uint) (response bool uint)) - ;; withdraw STX from the smart wallet (user only) + ;; withdraw STX from the agent account (owner only) ;; @param amount amount of microSTX to withdraw ;; @returns (response bool uint) (withdraw-stx (uint) (response bool uint)) - ;; withdraw FT from the smart wallet (user only) + ;; withdraw FT from the agent account (owner only) ;; @param ft the fungible token contract ;; @param amount amount of tokens to withdraw ;; @returns (response bool uint) (withdraw-ft ( uint) (response bool uint)) - ;; approve an asset for deposit/withdrawal (user only) + ;; approve an asset for deposit/withdrawal (owner only) ;; @param asset the asset contract principal ;; @returns (response bool uint) (approve-asset (principal) (response bool uint)) - ;; revoke approval for an asset (user only) + ;; revoke approval for an asset (owner only) ;; @param asset the asset contract principal ;; @returns (response bool uint) (revoke-asset (principal) (response bool uint)) diff --git a/tests/aibtc-user-agent-smart-wallet.test.ts b/tests/aibtc-user-agent-account.test.ts similarity index 100% rename from tests/aibtc-user-agent-smart-wallet.test.ts rename to tests/aibtc-user-agent-account.test.ts From 24ec3ade34bc7b819a435ea3120cc87de7b53292 Mon Sep 17 00:00:00 2001 From: "Jason Schrader (aider)" Date: Wed, 16 Apr 2025 15:56:18 -0700 Subject: [PATCH 04/12] refactor: Rename smart wallet to account in tests and error codes --- tests/aibtc-user-agent-account.test.ts | 186 ++++++++++++------------- tests/error-codes.ts | 2 +- 2 files changed, 94 insertions(+), 94 deletions(-) diff --git a/tests/aibtc-user-agent-account.test.ts b/tests/aibtc-user-agent-account.test.ts index c7a1b0d..ee9b268 100644 --- a/tests/aibtc-user-agent-account.test.ts +++ b/tests/aibtc-user-agent-account.test.ts @@ -1,6 +1,6 @@ import { Cl, cvToValue } from "@stacks/transactions"; import { describe, expect, it } from "vitest"; -import { UserAgentSmartWalletErrCode } from "./error-codes"; +import { UserAgentAccountErrCode } from "./error-codes"; import { constructDao, convertSIP019PrintEvent, @@ -20,7 +20,7 @@ const address2 = accounts.get("wallet_2")!; const address3 = accounts.get("wallet_3")!; // Contract references -const contractName = "aibtc-user-agent-smart-wallet"; +const contractName = "aibtc-user-agent-account"; const contractAddress = `${deployer}.${contractName}`; const daoTokenAddress = `${deployer}.aibtc-token`; const tokenDexContractAddress = `${deployer}.aibtc-token-dex`; @@ -36,9 +36,9 @@ const actionProposalVotingConfig = VOTING_CONFIG["aibtc-action-proposals-v2"]; const coreProposalVotingConfig = VOTING_CONFIG["aibtc-core-proposals-v2"]; // Error codes -const ErrCode = UserAgentSmartWalletErrCode; +const ErrCode = UserAgentAccountErrCode; -function setupSmartWallet(sender: string, satsAmount: number = 1000000) { +function setupAccount(sender: string, satsAmount: number = 1000000) { // construct the dao so we can call extensions const constructReceipt = constructDao( sender, @@ -90,7 +90,7 @@ describe(`public functions: ${contractName}`, () => { //////////////////////////////////////// // deposit-stx() tests //////////////////////////////////////// - it("deposit-stx() succeeds and deposits STX to the smart wallet", () => { + it("deposit-stx() succeeds and deposits STX to the account", () => { // arrange const amount = 1000000; // 1 STX const initialBalanceResponse = simnet.callReadOnlyFn( @@ -169,7 +169,7 @@ describe(`public functions: ${contractName}`, () => { // assert expect(receipt.result).toBeErr(Cl.uint(ErrCode.ERR_UNKNOWN_ASSET)); }); - it("deposit-ft() succeeds and transfers sBTC to the smart wallet", () => { + it("deposit-ft() succeeds and transfers sBTC to the account", () => { // arrange const sbtcAmount = 100000000; // get sBTC from the faucet @@ -190,7 +190,7 @@ describe(`public functions: ${contractName}`, () => { // assert expect(receipt.result).toBeOk(Cl.bool(true)); }); - it("deposit-ft() succeeds and transfers DAO tokens to the smart wallet", () => { + it("deposit-ft() succeeds and transfers DAO tokens to the account", () => { // arrange const amount = 1000000; // get sBTC from the faucet @@ -584,13 +584,13 @@ describe(`public functions: ${contractName}`, () => { //////////////////////////////////////// const memoContext = "Can pass up to 1024 characters for additional context."; - it("proxy-propose-action() fails if caller is not authorized (user or agent)", () => { + it("acct-propose-action() fails if caller is not authorized (user or agent)", () => { // arrange const message = Cl.stringAscii("hello world"); // act const receipt = simnet.callPublicFn( contractAddress, - "proxy-propose-action", + "acct-propose-action", [ Cl.principal(actionProposalsV2ContractAddress), Cl.principal(sendMessageActionContractAddress), @@ -605,8 +605,8 @@ describe(`public functions: ${contractName}`, () => { it("proxy-propose-action() succeeds and creates a new action proposal", () => { // arrange const message = Cl.stringAscii("hello world"); - // construct dao / setup smart wallet with dao tokens - setupSmartWallet(deployer); + // construct dao / setup account with dao tokens + setupAccount(deployer); // act const receipt = simnet.callPublicFn( contractAddress, @@ -622,11 +622,11 @@ describe(`public functions: ${contractName}`, () => { // assert expect(receipt.result).toBeOk(Cl.bool(true)); }); - it("proxy-propose-action() emits the correct notification event", () => { + it("acct-propose-action() emits the correct notification event", () => { // arrange const message = Cl.stringAscii("hello world"); const expectedEvent = { - notification: "proxy-propose-action", + notification: "acct-propose-action", payload: { proposalContract: actionProposalsV2ContractAddress, action: sendMessageActionContractAddress, @@ -635,12 +635,12 @@ describe(`public functions: ${contractName}`, () => { caller: deployer, }, }; - // construct dao / setup smart wallet with dao tokens - setupSmartWallet(deployer); + // construct dao / setup account with dao tokens + setupAccount(deployer); // act const receipt = simnet.callPublicFn( contractAddress, - "proxy-propose-action", + "acct-propose-action", [ Cl.principal(actionProposalsV2ContractAddress), Cl.principal(sendMessageActionContractAddress), @@ -659,11 +659,11 @@ describe(`public functions: ${contractName}`, () => { //////////////////////////////////////// // proxy-create-proposal() tests //////////////////////////////////////// - it("proxy-create-proposal() fails if caller is not authorized (user or agent)", () => { + it("acct-create-proposal() fails if caller is not authorized (user or agent)", () => { // act const receipt = simnet.callPublicFn( contractAddress, - "proxy-create-proposal", + "acct-create-proposal", [ Cl.principal(coreProposalsV2ContractAddress), Cl.principal(baseEnableExtensionContractAddress), @@ -674,16 +674,16 @@ describe(`public functions: ${contractName}`, () => { // assert expect(receipt.result).toBeErr(Cl.uint(ErrCode.ERR_UNAUTHORIZED)); }); - it("proxy-create-proposal() succeeds and creates a new core proposal", () => { + it("acct-create-proposal() succeeds and creates a new core proposal", () => { // arrange - // construct dao / setup smart wallet with dao tokens - setupSmartWallet(deployer); + // construct dao / setup account with dao tokens + setupAccount(deployer); // progress the chain past the first voting period simnet.mineEmptyBlocks(coreProposalVotingConfig.votingPeriod); // act const receipt = simnet.callPublicFn( contractAddress, - "proxy-create-proposal", + "acct-create-proposal", [ Cl.principal(coreProposalsV2ContractAddress), Cl.principal(baseEnableExtensionContractAddress), @@ -694,15 +694,15 @@ describe(`public functions: ${contractName}`, () => { // assert expect(receipt.result).toBeOk(Cl.bool(true)); }); - it("proxy-create-proposal() emits the correct notification event", () => { + it("acct-create-proposal() emits the correct notification event", () => { // arrange - // construct dao / setup smart wallet with dao tokens - setupSmartWallet(deployer); + // construct dao / setup account with dao tokens + setupAccount(deployer); // progress the chain past the first voting period simnet.mineEmptyBlocks(coreProposalVotingConfig.votingPeriod); // format expected event like print event const expectedEvent = { - notification: "proxy-create-proposal", + notification: "acct-create-proposal", payload: { proposalContract: coreProposalsV2ContractAddress, proposal: baseEnableExtensionContractAddress, @@ -713,7 +713,7 @@ describe(`public functions: ${contractName}`, () => { // act const receipt = simnet.callPublicFn( contractAddress, - "proxy-create-proposal", + "acct-create-proposal", [ Cl.principal(coreProposalsV2ContractAddress), Cl.principal(baseEnableExtensionContractAddress), @@ -759,7 +759,7 @@ describe(`public functions: ${contractName}`, () => { // create a new action proposal const proposeReceipt = simnet.callPublicFn( contractAddress, - "proxy-propose-action", + "acct-propose-action", [ Cl.principal(actionProposalsV2ContractAddress), Cl.principal(sendMessageActionContractAddress), @@ -866,7 +866,7 @@ describe(`public functions: ${contractName}`, () => { // create a new core proposal const createReceipt = simnet.callPublicFn( contractAddress, - "proxy-create-proposal", + "acct-create-proposal", [ Cl.principal(coreProposalsV2ContractAddress), Cl.principal(baseEnableExtensionContractAddress), @@ -1002,7 +1002,7 @@ describe(`public functions: ${contractName}`, () => { [Cl.uint(proposalId), Cl.bool(true)], address1 ), - // cast vote through our user/agent smart wallet + // cast vote through our user/agent account simnet.callPublicFn( contractAddress, "vote-on-action-proposal", @@ -1085,7 +1085,7 @@ describe(`public functions: ${contractName}`, () => { [Cl.uint(proposalId), Cl.bool(true)], address1 ), - // cast vote through our user/agent smart wallet + // cast vote through our user/agent account simnet.callPublicFn( contractAddress, "vote-on-action-proposal", @@ -1210,28 +1210,28 @@ describe(`public functions: ${contractName}`, () => { expect(receipt.result).toBeOk(Cl.bool(true)); }); //////////////////////////////////////// - // approve-dex() tests + // acct-approve-dex() tests //////////////////////////////////////// - it("approve-dex() fails if caller is not the user", () => { + it("acct-approve-dex() fails if caller is not the user", () => { // arrange const dex = `${deployer}.test-dex-1`; // act const receipt = simnet.callPublicFn( contractAddress, - "approve-dex", + "acct-approve-dex", [Cl.principal(dex)], address3 ); // assert expect(receipt.result).toBeErr(Cl.uint(ErrCode.ERR_UNAUTHORIZED)); }); - it("approve-dex() succeeds and sets new approved dex", () => { + it("acct-approve-dex() succeeds and sets new approved dex", () => { // arrange const dex = `${deployer}.test-dex-1`; // act const receipt = simnet.callPublicFn( contractAddress, - "approve-dex", + "acct-approve-dex", [Cl.principal(dex)], deployer ); @@ -1246,11 +1246,11 @@ describe(`public functions: ${contractName}`, () => { ); expect(isApproved.result).toStrictEqual(Cl.bool(true)); }); - it("approve-dex() emits the correct notification event", () => { + it("acct-approve-dex() emits the correct notification event", () => { // arrange const dex = `${deployer}.test-dex-2`; const expectedEvent = { - notification: "approve-dex", + notification: "acct-approve-dex", payload: { dexContract: dex, approved: true, @@ -1261,7 +1261,7 @@ describe(`public functions: ${contractName}`, () => { // act const receipt = simnet.callPublicFn( contractAddress, - "approve-dex", + "acct-approve-dex", [Cl.principal(dex)], deployer ); @@ -1273,28 +1273,28 @@ describe(`public functions: ${contractName}`, () => { expect(printEvent).toStrictEqual(expectedEvent); }); //////////////////////////////////////// - // revoke-dex() tests + // acct-revoke-dex() tests //////////////////////////////////////// - it("revoke-dex() fails if caller is not the user", () => { + it("acct-revoke-dex() fails if caller is not the user", () => { // arrange const dex = `${deployer}.test-dex-1`; // act const receipt = simnet.callPublicFn( contractAddress, - "revoke-dex", + "acct-revoke-dex", [Cl.principal(dex)], address3 ); // assert expect(receipt.result).toBeErr(Cl.uint(ErrCode.ERR_UNAUTHORIZED)); }); - it("revoke-dex() succeeds and removes approved dex", () => { + it("acct-revoke-dex() succeeds and removes approved dex", () => { // arrange const dex = `${deployer}.test-dex-1`; // approve the dex first const approveReceipt = simnet.callPublicFn( contractAddress, - "approve-dex", + "acct-approve-dex", [Cl.principal(dex)], deployer ); @@ -1302,7 +1302,7 @@ describe(`public functions: ${contractName}`, () => { // act const receipt = simnet.callPublicFn( contractAddress, - "revoke-dex", + "acct-revoke-dex", [Cl.principal(dex)], deployer ); @@ -1317,11 +1317,11 @@ describe(`public functions: ${contractName}`, () => { ); expect(isApproved.result).toStrictEqual(Cl.bool(false)); }); - it("revoke-dex() emits the correct notification event", () => { + it("acct-revoke-dex() emits the correct notification event", () => { // arrange const dex = `${deployer}.test-dex-1`; const expectedEvent = { - notification: "revoke-dex", + notification: "acct-revoke-dex", payload: { dexContract: dex, approved: false, @@ -1332,7 +1332,7 @@ describe(`public functions: ${contractName}`, () => { // approve the dex first const approveReceipt = simnet.callPublicFn( contractAddress, - "approve-dex", + "acct-approve-dex", [Cl.principal(dex)], deployer ); @@ -1340,7 +1340,7 @@ describe(`public functions: ${contractName}`, () => { // act const receipt = simnet.callPublicFn( contractAddress, - "revoke-dex", + "acct-revoke-dex", [Cl.principal(dex)], deployer ); @@ -1406,9 +1406,9 @@ describe(`public functions: ${contractName}`, () => { expect(printEvent).toStrictEqual(expectedEvent); }); //////////////////////////////////////// - // buy-asset() tests + // acct-buy-asset() tests //////////////////////////////////////// - it("buy-asset() fails if caller is not authorized", () => { + it("acct-buy-asset() fails if caller is not authorized", () => { // arrange const amount = 10000000000; const dex = tokenDexContractAddress; @@ -1416,14 +1416,14 @@ describe(`public functions: ${contractName}`, () => { // act const receipt = simnet.callPublicFn( contractAddress, - "buy-asset", + "acct-buy-asset", [Cl.principal(dex), Cl.principal(asset), Cl.uint(amount)], address3 ); // assert expect(receipt.result).toBeErr(Cl.uint(ErrCode.ERR_BUY_SELL_NOT_ALLOWED)); }); - it("buy-asset() fails if agent can't buy/sell", () => { + it("acct-buy-asset() fails if agent can't buy/sell", () => { // arrange const amount = 10000000000; const dex = tokenDexContractAddress; @@ -1431,14 +1431,14 @@ describe(`public functions: ${contractName}`, () => { // act const receipt = simnet.callPublicFn( contractAddress, - "buy-asset", + "acct-buy-asset", [Cl.principal(dex), Cl.principal(asset), Cl.uint(amount)], address2 // agent address ); // assert expect(receipt.result).toBeErr(Cl.uint(ErrCode.ERR_BUY_SELL_NOT_ALLOWED)); }); - it("buy-asset() fails if dex is not approved", () => { + it("acct-buy-asset() fails if dex is not approved", () => { // arrange const amount = 10000000000; const dex = `${deployer}.test-dex-1`; @@ -1446,49 +1446,49 @@ describe(`public functions: ${contractName}`, () => { // act const receipt = simnet.callPublicFn( contractAddress, - "buy-asset", + "acct-buy-asset", [Cl.principal(dex), Cl.principal(asset), Cl.uint(amount)], deployer ); // assert expect(receipt.result).toBeErr(Cl.uint(ErrCode.ERR_UNKNOWN_ASSET)); }); - it("buy-asset() succeeds when called by user", () => { + it("acct-buy-asset() succeeds when called by user", () => { // arrange const amount = 10000; const dex = tokenDexContractAddress; const asset = daoTokenAddress; - // construct dao / setup smart wallet with dao tokens - setupSmartWallet(deployer); + // construct dao / setup account with dao tokens + setupAccount(deployer); // get our balances from the assets map const balancesMap = simnet.getAssetsMap(); dbgLog(balancesMap); const aibtcKey = ".aibtc-token.SYMBOL"; const sbtcKey = ".sbtc-token.sbtc-token"; const stxKey = "STX"; - const smartWalletBalances = { + const accountBalances = { sbtc: balancesMap.get(sbtcKey)?.get(contractAddress) ?? 0n, aibtc: balancesMap.get(aibtcKey)?.get(contractAddress) ?? 0n, stx: balancesMap.get(stxKey)?.get(contractAddress) ?? 0n, }; - dbgLog(`smartWalletBalances: ${JSON.stringify(smartWalletBalances)}`); + dbgLog(`accountBalances: ${JSON.stringify(accountBalances)}`); // act const receipt = simnet.callPublicFn( contractAddress, - "buy-asset", + "acct-buy-asset", [Cl.principal(dex), Cl.principal(asset), Cl.uint(amount)], deployer ); // assert expect(receipt.result).toBeOk(Cl.bool(true)); }); - it("buy-asset() succeeds when called by agent with permission", () => { + it("acct-buy-asset() succeeds when called by agent with permission", () => { // arrange const amount = 10000; const dex = tokenDexContractAddress; const asset = daoTokenAddress; - // construct dao / setup smart wallet with dao tokens - setupSmartWallet(deployer); + // construct dao / setup account with dao tokens + setupAccount(deployer); // enable agent to buy/sell const permissionReceipt = simnet.callPublicFn( contractAddress, @@ -1500,20 +1500,20 @@ describe(`public functions: ${contractName}`, () => { // act const receipt = simnet.callPublicFn( contractAddress, - "buy-asset", + "acct-buy-asset", [Cl.principal(dex), Cl.principal(asset), Cl.uint(amount)], address2 // agent address ); // assert expect(receipt.result).toBeOk(Cl.bool(true)); }); - it("buy-asset() emits the correct notification event", () => { + it("acct-buy-asset() emits the correct notification event", () => { // arrange const amount = 10000; const dex = tokenDexContractAddress; const asset = daoTokenAddress; const expectedEvent = { - notification: "buy-asset", + notification: "acct-buy-asset", payload: { dexContract: dex, asset: asset, @@ -1522,12 +1522,12 @@ describe(`public functions: ${contractName}`, () => { caller: deployer, }, }; - // construct dao / setup smart wallet with dao tokens - setupSmartWallet(deployer); + // construct dao / setup account with dao tokens + setupAccount(deployer); // act const receipt = simnet.callPublicFn( contractAddress, - "buy-asset", + "acct-buy-asset", [Cl.principal(dex), Cl.principal(asset), Cl.uint(amount)], deployer ); @@ -1539,9 +1539,9 @@ describe(`public functions: ${contractName}`, () => { expect(printEvent).toStrictEqual(expectedEvent); }); //////////////////////////////////////// - // sell-asset() tests + // acct-sell-asset() tests //////////////////////////////////////// - it("sell-asset() fails if caller is not authorized", () => { + it("acct-sell-asset() fails if caller is not authorized", () => { // arrange const amount = 10000000000; const dex = tokenDexContractAddress; @@ -1549,14 +1549,14 @@ describe(`public functions: ${contractName}`, () => { // act const receipt = simnet.callPublicFn( contractAddress, - "sell-asset", + "acct-sell-asset", [Cl.principal(dex), Cl.principal(asset), Cl.uint(amount)], address3 ); // assert expect(receipt.result).toBeErr(Cl.uint(ErrCode.ERR_BUY_SELL_NOT_ALLOWED)); }); - it("sell-asset() fails if agent can't buy/sell", () => { + it("acct-sell-asset() fails if agent can't buy/sell", () => { // arrange const amount = 10000000000; const dex = tokenDexContractAddress; @@ -1572,14 +1572,14 @@ describe(`public functions: ${contractName}`, () => { // act const receipt = simnet.callPublicFn( contractAddress, - "sell-asset", + "acct-sell-asset", [Cl.principal(dex), Cl.principal(asset), Cl.uint(amount)], address2 // agent address ); // assert expect(receipt.result).toBeErr(Cl.uint(ErrCode.ERR_BUY_SELL_NOT_ALLOWED)); }); - it("sell-asset() fails if dex is not approved", () => { + it("acct-sell-asset() fails if dex is not approved", () => { // arrange const amount = 10000000000; const dex = `${deployer}.test-dex-1`; @@ -1587,14 +1587,14 @@ describe(`public functions: ${contractName}`, () => { // act const receipt = simnet.callPublicFn( contractAddress, - "sell-asset", + "acct-sell-asset", [Cl.principal(dex), Cl.principal(asset), Cl.uint(amount)], deployer ); // assert expect(receipt.result).toBeErr(Cl.uint(ErrCode.ERR_UNKNOWN_ASSET)); }); - it("sell-asset() succeeds when called by user", () => { + it("acct-sell-asset() succeeds when called by user", () => { // arrange const amount = 1000000000000n; // sell 10000 dao tokens const dex = tokenDexContractAddress; @@ -1604,8 +1604,8 @@ describe(`public functions: ${contractName}`, () => { titleBefore: "asset map before setup", }); - // construct dao / setup smart wallet with dao tokens - setupSmartWallet(deployer); + // construct dao / setup account with dao tokens + setupAccount(deployer); dbgLog(simnet.getAssetsMap(), { titleBefore: "asset map after setup", @@ -1625,26 +1625,26 @@ describe(`public functions: ${contractName}`, () => { // act const receipt = simnet.callPublicFn( contractAddress, - "sell-asset", + "acct-sell-asset", [Cl.principal(dex), Cl.principal(asset), Cl.uint(amount)], deployer ); dbgLog(JSON.stringify(receipt, null, 2), { - titleBefore: "sell-asset receipt", + titleBefore: "acct-sell-asset receipt", }); // assert expect(receipt.result).toBeOk(Cl.bool(true)); }); - it("sell-asset() succeeds when called by agent with permission", () => { + it("acct-sell-asset() succeeds when called by agent with permission", () => { // arrange const amount = 1000000000000n; // sell 10000 dao tokens const dex = tokenDexContractAddress; const asset = daoTokenAddress; - // construct dao / setup smart wallet with dao tokens - setupSmartWallet(deployer); + // construct dao / setup account with dao tokens + setupAccount(deployer); // enable agent to buy/sell const permissionReceipt = simnet.callPublicFn( @@ -1658,7 +1658,7 @@ describe(`public functions: ${contractName}`, () => { // act const receipt = simnet.callPublicFn( contractAddress, - "sell-asset", + "acct-sell-asset", [Cl.principal(dex), Cl.principal(asset), Cl.uint(amount)], address2 // agent address ); @@ -1666,16 +1666,16 @@ describe(`public functions: ${contractName}`, () => { // assert expect(receipt.result).toBeOk(Cl.bool(true)); }); - it("sell-asset() emits the correct notification event", () => { + it("acct-sell-asset() emits the correct notification event", () => { // arrange const amount = 1000000000000n; // sell 10000 dao tokens const dex = tokenDexContractAddress; const asset = daoTokenAddress; - // construct dao / setup smart wallet with dao tokens - setupSmartWallet(deployer); + // construct dao / setup account with dao tokens + setupAccount(deployer); // build expected print event const expectedEvent = { - notification: "sell-asset", + notification: "acct-sell-asset", payload: { dexContract: dex, asset: asset, @@ -1688,7 +1688,7 @@ describe(`public functions: ${contractName}`, () => { // act const receipt = simnet.callPublicFn( contractAddress, - "sell-asset", + "acct-sell-asset", [Cl.principal(dex), Cl.principal(asset), Cl.uint(amount)], deployer ); @@ -1847,7 +1847,7 @@ describe(`read-only functions: ${contractName}`, () => { payload: { agent: address2, user: deployer, - smartWallet: contractAddress, + account: contractAddress, daoToken: daoTokenAddress, daoTokenDex: tokenDexContractAddress, sbtcToken: SBTC_CONTRACT, diff --git a/tests/error-codes.ts b/tests/error-codes.ts index 286b1b0..6eaaf2c 100644 --- a/tests/error-codes.ts +++ b/tests/error-codes.ts @@ -142,7 +142,7 @@ export enum TokenFaktoryDexErrCode { ERR_TOKEN_NOT_AUTH = 401, } -export enum UserAgentSmartWalletErrCode { +export enum UserAgentAccountErrCode { ERR_UNAUTHORIZED = 9000, ERR_UNKNOWN_ASSET, ERR_OPERATION_FAILED, From 3bc0bbbffcca226a62824c861b76cae6ee0ca634 Mon Sep 17 00:00:00 2001 From: "Jason Schrader (aider)" Date: Wed, 16 Apr 2025 15:56:28 -0700 Subject: [PATCH 05/12] fix: Correct function name in test case for action proposal --- tests/aibtc-user-agent-account.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/aibtc-user-agent-account.test.ts b/tests/aibtc-user-agent-account.test.ts index ee9b268..53806a1 100644 --- a/tests/aibtc-user-agent-account.test.ts +++ b/tests/aibtc-user-agent-account.test.ts @@ -602,7 +602,7 @@ describe(`public functions: ${contractName}`, () => { // assert expect(receipt.result).toBeErr(Cl.uint(ErrCode.ERR_UNAUTHORIZED)); }); - it("proxy-propose-action() succeeds and creates a new action proposal", () => { + it("acct-propose-action() succeeds and creates a new action proposal", () => { // arrange const message = Cl.stringAscii("hello world"); // construct dao / setup account with dao tokens @@ -610,7 +610,7 @@ describe(`public functions: ${contractName}`, () => { // act const receipt = simnet.callPublicFn( contractAddress, - "proxy-propose-action", + "acct-propose-action", [ Cl.principal(actionProposalsV2ContractAddress), Cl.principal(sendMessageActionContractAddress), From 7964de569f8bd058b5750c5c90baeeae3362ce8d Mon Sep 17 00:00:00 2001 From: "Jason Schrader (aider)" Date: Wed, 16 Apr 2025 15:58:22 -0700 Subject: [PATCH 06/12] refactor: Replace smart wallet with account in tests. --- tests/aibtc-user-agent-account.test.ts | 36 +++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/tests/aibtc-user-agent-account.test.ts b/tests/aibtc-user-agent-account.test.ts index 53806a1..e8566a3 100644 --- a/tests/aibtc-user-agent-account.test.ts +++ b/tests/aibtc-user-agent-account.test.ts @@ -754,8 +754,8 @@ describe(`public functions: ${contractName}`, () => { const message = Cl.stringAscii("hello world"); const proposalId = 1; const vote = true; - // construct dao / setup smart wallet with dao tokens - setupSmartWallet(deployer); + // construct dao / setup account with dao tokens + setupAccount(deployer); // create a new action proposal const proposeReceipt = simnet.callPublicFn( contractAddress, @@ -790,12 +790,12 @@ describe(`public functions: ${contractName}`, () => { const message = Cl.stringAscii("hello world"); const proposalId = 1; const vote = true; - // construct dao / setup smart wallet with dao tokens - setupSmartWallet(deployer); + // construct dao / setup account with dao tokens + setupAccount(deployer); // create a new action proposal const proposeReceipt = simnet.callPublicFn( contractAddress, - "proxy-propose-action", + "acct-propose-action", [ Cl.principal(actionProposalsV2ContractAddress), Cl.principal(sendMessageActionContractAddress), @@ -894,14 +894,14 @@ describe(`public functions: ${contractName}`, () => { it("vote-on-core-proposal() emits the correct notification event", () => { // arrange const vote = true; - // construct dao / setup smart wallet with dao tokens - setupSmartWallet(deployer); + // construct dao / setup account with dao tokens + setupAccount(deployer); // progress the chain past the first voting period simnet.mineEmptyBlocks(coreProposalVotingConfig.votingPeriod); // create a new core proposal const createReceipt = simnet.callPublicFn( contractAddress, - "proxy-create-proposal", + "acct-create-proposal", [ Cl.principal(coreProposalsV2ContractAddress), Cl.principal(baseEnableExtensionContractAddress), @@ -965,8 +965,8 @@ describe(`public functions: ${contractName}`, () => { // arrange const message = Cl.stringAscii("hello world"); const proposalId = 1; - // construct dao / setup smart wallet with dao tokens - setupSmartWallet(deployer); + // construct dao / setup account with dao tokens + setupAccount(deployer); fundVoters(daoTokenAddress, tokenDexContractAddress, [ deployer, address1, @@ -1048,8 +1048,8 @@ describe(`public functions: ${contractName}`, () => { sender: deployer, }, }; - // construct dao / setup smart wallet with dao tokens - setupSmartWallet(deployer); + // construct dao / setup account with dao tokens + setupAccount(deployer); fundVoters(daoTokenAddress, tokenDexContractAddress, [ deployer, address1, @@ -1058,7 +1058,7 @@ describe(`public functions: ${contractName}`, () => { // create a new action proposal const proposeReceipt = simnet.callPublicFn( contractAddress, - "proxy-propose-action", + "acct-propose-action", [ Cl.principal(actionProposalsV2ContractAddress), Cl.principal(sendMessageActionContractAddress), @@ -1140,8 +1140,8 @@ describe(`public functions: ${contractName}`, () => { }); it("conclude-core-proposal() succeeds and concludes a core proposal", () => { // arrange - // construct dao / setup smart wallet with dao tokens - setupSmartWallet(deployer); + // construct dao / setup account with dao tokens + setupAccount(deployer); fundVoters(daoTokenAddress, tokenDexContractAddress, [ deployer, address1, @@ -1152,7 +1152,7 @@ describe(`public functions: ${contractName}`, () => { // create a new core proposal const createReceipt = simnet.callPublicFn( contractAddress, - "proxy-create-proposal", + "acct-create-proposal", [ Cl.principal(coreProposalsV2ContractAddress), Cl.principal(baseEnableExtensionContractAddress), @@ -1178,7 +1178,7 @@ describe(`public functions: ${contractName}`, () => { [Cl.principal(baseEnableExtensionContractAddress), Cl.bool(true)], address1 ), - // cast vote through our user/agent smart wallet + // cast vote through our user/agent account simnet.callPublicFn( contractAddress, "vote-on-core-proposal", @@ -1846,7 +1846,7 @@ describe(`read-only functions: ${contractName}`, () => { notification: "get-configuration", payload: { agent: address2, - user: deployer, + owner: deployer, account: contractAddress, daoToken: daoTokenAddress, daoTokenDex: tokenDexContractAddress, From 9c573419298153ae1aeda874bcde8853d905ccf8 Mon Sep 17 00:00:00 2001 From: "Jason Schrader (aider)" Date: Wed, 16 Apr 2025 15:58:43 -0700 Subject: [PATCH 07/12] refactor: Rename setupSmartWallet to setupAccount and approve/revoke-dex --- tests/aibtc-user-agent-account.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/aibtc-user-agent-account.test.ts b/tests/aibtc-user-agent-account.test.ts index e8566a3..569810e 100644 --- a/tests/aibtc-user-agent-account.test.ts +++ b/tests/aibtc-user-agent-account.test.ts @@ -859,8 +859,8 @@ describe(`public functions: ${contractName}`, () => { it("vote-on-core-proposal() succeeds and votes on a core proposal", () => { // arrange const vote = true; - // construct dao / setup smart wallet with dao tokens - setupSmartWallet(deployer); + // construct dao / setup account with dao tokens + setupAccount(deployer); // progress the chain past the first voting period simnet.mineEmptyBlocks(coreProposalVotingConfig.votingPeriod); // create a new core proposal @@ -1721,7 +1721,7 @@ describe(`read-only functions: ${contractName}`, () => { // approve the dex const approveReceipt = simnet.callPublicFn( contractAddress, - "approve-dex", + "acct-approve-dex", [Cl.principal(dex)], deployer ); @@ -1738,7 +1738,7 @@ describe(`read-only functions: ${contractName}`, () => { // revoke the dex const revokeReceipt = simnet.callPublicFn( contractAddress, - "revoke-dex", + "acct-revoke-dex", [Cl.principal(dex)], deployer ); From 5a75358edfef1715663b74e8b9957465df9d7695 Mon Sep 17 00:00:00 2001 From: "Jason Schrader (aider)" Date: Wed, 16 Apr 2025 16:01:31 -0700 Subject: [PATCH 08/12] fix: Align test function calls and event payloads with contract changes --- tests/aibtc-user-agent-account.test.ts | 34 ++++++++------------------ 1 file changed, 10 insertions(+), 24 deletions(-) diff --git a/tests/aibtc-user-agent-account.test.ts b/tests/aibtc-user-agent-account.test.ts index 569810e..d6e7c8a 100644 --- a/tests/aibtc-user-agent-account.test.ts +++ b/tests/aibtc-user-agent-account.test.ts @@ -975,7 +975,7 @@ describe(`public functions: ${contractName}`, () => { // create a new action proposal const proposeReceipt = simnet.callPublicFn( contractAddress, - "proxy-propose-action", + "acct-propose-action", [ Cl.principal(actionProposalsV2ContractAddress), Cl.principal(sendMessageActionContractAddress), @@ -1041,11 +1041,11 @@ describe(`public functions: ${contractName}`, () => { const expectedEvent = { notification: "conclude-action-proposal", payload: { - action: sendMessageActionContractAddress, - caller: deployer, proposalContract: actionProposalsV2ContractAddress, proposalId: proposalId.toString(), + action: sendMessageActionContractAddress, sender: deployer, + caller: deployer, }, }; // construct dao / setup account with dao tokens @@ -1841,17 +1841,13 @@ describe(`read-only functions: ${contractName}`, () => { //////////////////////////////////////// it("get-configuration() returns the correct configuration", () => { // arrange - // format expected config like print event const expectedConfig = { - notification: "get-configuration", - payload: { - agent: address2, - owner: deployer, - account: contractAddress, - daoToken: daoTokenAddress, - daoTokenDex: tokenDexContractAddress, - sbtcToken: SBTC_CONTRACT, - }, + account: contractAddress, + agent: address2, + owner: deployer, + daoToken: daoTokenAddress, + daoTokenDex: tokenDexContractAddress, + sbtcToken: SBTC_CONTRACT, }; // act const config = simnet.callReadOnlyFn( @@ -1861,16 +1857,6 @@ describe(`read-only functions: ${contractName}`, () => { deployer ); // assert - const event: ClarityEvent = { - event: "print_event", - data: { - value: Cl.tuple({ - notification: Cl.stringAscii("get-configuration"), - payload: config.result, - }), - }, - }; - const printEvent = convertSIP019PrintEvent(event); - expect(printEvent).toStrictEqual(expectedConfig); + expect(cvToValue(config.result)).toEqual(expectedConfig); }); }); From c163faf71c704599f53601e019cec6b11d186be4 Mon Sep 17 00:00:00 2001 From: "Jason Schrader (aider)" Date: Wed, 16 Apr 2025 16:02:07 -0700 Subject: [PATCH 09/12] feat: Add notification event test for conclude-core-proposal() --- tests/aibtc-user-agent-account.test.ts | 86 ++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/tests/aibtc-user-agent-account.test.ts b/tests/aibtc-user-agent-account.test.ts index d6e7c8a..551a856 100644 --- a/tests/aibtc-user-agent-account.test.ts +++ b/tests/aibtc-user-agent-account.test.ts @@ -1209,6 +1209,92 @@ describe(`public functions: ${contractName}`, () => { // assert expect(receipt.result).toBeOk(Cl.bool(true)); }); + + it("conclude-core-proposal() emits the correct notification event", () => { + // arrange + const vote = true; + const expectedEvent = { + notification: "conclude-core-proposal", + payload: { + proposalContract: coreProposalsV2ContractAddress, + proposal: baseEnableExtensionContractAddress, + sender: deployer, + caller: deployer, + }, + }; + // construct dao / setup account with dao tokens + setupAccount(deployer); + fundVoters(daoTokenAddress, tokenDexContractAddress, [ + deployer, + address1, + address2, + ]); + // progress the chain past the first voting period + simnet.mineEmptyBlocks(coreProposalVotingConfig.votingPeriod); + // create a new core proposal + const createReceipt = simnet.callPublicFn( + contractAddress, + "acct-create-proposal", + [ + Cl.principal(coreProposalsV2ContractAddress), + Cl.principal(baseEnableExtensionContractAddress), + Cl.some(Cl.stringAscii(memoContext)), + ], + deployer + ); + expect(createReceipt.result).toBeOk(Cl.bool(true)); + // progress the chain past the voting delay + simnet.mineEmptyBlocks(coreProposalVotingConfig.votingDelay); + // vote on the proposal + const voteReceipts = [ + // cast two regular votes to pass proposal + simnet.callPublicFn( + coreProposalsV2ContractAddress, + "vote-on-proposal", + [Cl.principal(baseEnableExtensionContractAddress), Cl.bool(true)], + address2 + ), + simnet.callPublicFn( + coreProposalsV2ContractAddress, + "vote-on-proposal", + [Cl.principal(baseEnableExtensionContractAddress), Cl.bool(true)], + address1 + ), + // cast vote through our user/agent account + simnet.callPublicFn( + contractAddress, + "vote-on-core-proposal", + [ + Cl.principal(coreProposalsV2ContractAddress), + Cl.principal(baseEnableExtensionContractAddress), + Cl.bool(true), + ], + deployer + ), + ]; + for (const voteReceipt of voteReceipts) { + expect(voteReceipt.result).toBeOk(Cl.bool(true)); + } + // progress the chain past the voting period and execution delay + simnet.mineEmptyBlocks(coreProposalVotingConfig.votingPeriod); + simnet.mineEmptyBlocks(coreProposalVotingConfig.votingDelay); + // act + const receipt = simnet.callPublicFn( + contractAddress, + "conclude-core-proposal", + [ + Cl.principal(coreProposalsV2ContractAddress), + Cl.principal(baseEnableExtensionContractAddress), + ], + deployer + ); + // assert + expect(receipt.result).toBeOk(Cl.bool(true)); + const event = receipt.events[0]; + expect(event).toBeDefined(); + const printEvent = convertSIP019PrintEvent(receipt.events[0]); + expect(printEvent).toStrictEqual(expectedEvent); + }); //////////////////////////////////////// // acct-approve-dex() tests //////////////////////////////////////// From d32a77e1932a0af5ab66b36006d69f16db507fd5 Mon Sep 17 00:00:00 2001 From: "Jason Schrader (aider)" Date: Wed, 16 Apr 2025 16:02:50 -0700 Subject: [PATCH 10/12] fix: Correctly handle Clarity value conversion in get-configuration test --- tests/aibtc-user-agent-account.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/aibtc-user-agent-account.test.ts b/tests/aibtc-user-agent-account.test.ts index 551a856..8ecb547 100644 --- a/tests/aibtc-user-agent-account.test.ts +++ b/tests/aibtc-user-agent-account.test.ts @@ -1936,13 +1936,17 @@ describe(`read-only functions: ${contractName}`, () => { sbtcToken: SBTC_CONTRACT, }; // act - const config = simnet.callReadOnlyFn( + const configCV = simnet.callReadOnlyFn( contractAddress, "get-configuration", [], deployer ); + + // Convert the Clarity value to a JavaScript object + const config = cvToValue(configCV.result); + // assert - expect(cvToValue(config.result)).toEqual(expectedConfig); + expect(config).toEqual(expectedConfig); }); }); From 7e0477bdd45c4ee7b60a3f4c6f2d12ebf7963767 Mon Sep 17 00:00:00 2001 From: Jason Schrader Date: Wed, 16 Apr 2025 16:11:55 -0700 Subject: [PATCH 11/12] fix: update remaining tests, config left to go --- tests/aibtc-user-agent-account.test.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/aibtc-user-agent-account.test.ts b/tests/aibtc-user-agent-account.test.ts index 8ecb547..f859af6 100644 --- a/tests/aibtc-user-agent-account.test.ts +++ b/tests/aibtc-user-agent-account.test.ts @@ -1,4 +1,4 @@ -import { Cl, cvToValue } from "@stacks/transactions"; +import { Cl, ClarityValue, cvToValue, TupleCV } from "@stacks/transactions"; import { describe, expect, it } from "vitest"; import { UserAgentAccountErrCode } from "./error-codes"; import { @@ -1209,7 +1209,7 @@ describe(`public functions: ${contractName}`, () => { // assert expect(receipt.result).toBeOk(Cl.bool(true)); }); - + it("conclude-core-proposal() emits the correct notification event", () => { // arrange const vote = true; @@ -1942,11 +1942,17 @@ describe(`read-only functions: ${contractName}`, () => { [], deployer ); - + // Convert the Clarity value to a JavaScript object - const config = cvToValue(configCV.result); - + const config = cvToValue(configCV.result) as TupleCV; + // Convert the TupleCV to a plain object + console.log(`config: ${JSON.stringify(config)}`); + const configData = Object.fromEntries( + Object.entries(config).map(([key, value]: [string, ClarityValue]) => { + return [key, cvToValue(value, true)]; + }) + ); // assert - expect(config).toEqual(expectedConfig); + expect(configData).toEqual(expectedConfig); }); }); From 91f0cd131c3557998bde183f921df6f6b466eef3 Mon Sep 17 00:00:00 2001 From: Jason Schrader Date: Thu, 17 Apr 2025 04:43:49 -0700 Subject: [PATCH 12/12] fix: properly decode clarity tuple Uses SIP-019 pattern in a more generalized sense --- tests/aibtc-user-agent-account.test.ts | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/tests/aibtc-user-agent-account.test.ts b/tests/aibtc-user-agent-account.test.ts index f859af6..00bd751 100644 --- a/tests/aibtc-user-agent-account.test.ts +++ b/tests/aibtc-user-agent-account.test.ts @@ -1,4 +1,11 @@ -import { Cl, ClarityValue, cvToValue, TupleCV } from "@stacks/transactions"; +import { + Cl, + ClarityType, + ClarityValue, + cvToValue, + isClarityType, + TupleCV, +} from "@stacks/transactions"; import { describe, expect, it } from "vitest"; import { UserAgentAccountErrCode } from "./error-codes"; import { @@ -1944,13 +1951,17 @@ describe(`read-only functions: ${contractName}`, () => { ); // Convert the Clarity value to a JavaScript object - const config = cvToValue(configCV.result) as TupleCV; + const config = configCV.result; + if (config.type !== ClarityType.Tuple) { + throw new Error("returned object is not a tuple"); + } // Convert the TupleCV to a plain object - console.log(`config: ${JSON.stringify(config)}`); + //console.log(`config: ${JSON.stringify(config)}`); + const configTuple = config.data; const configData = Object.fromEntries( - Object.entries(config).map(([key, value]: [string, ClarityValue]) => { - return [key, cvToValue(value, true)]; - }) + Object.entries(configTuple).map( + ([key, value]: [string, ClarityValue]) => [key, cvToValue(value, true)] + ) ); // assert expect(configData).toEqual(expectedConfig);