From f716cc8e27248933e261882a9ce3bd3abe7a9aeb Mon Sep 17 00:00:00 2001 From: simke9445 Date: Wed, 22 Nov 2023 17:00:57 +0100 Subject: [PATCH] funding account support in job-account-tracker - user can hold multiple funding accounts - jobs can have a single funding account attached - recurring jobs ad hoc create funding accounts - user can optionally provide one --- contracts/warp-controller/src/execute/job.rs | 50 +++++----- .../warp-controller/src/reply/account.rs | 6 +- contracts/warp-controller/src/reply/job.rs | 4 +- contracts/warp-controller/src/util/msg.rs | 44 ++++++++- .../warp-job-account-tracker/src/contract.rs | 17 ++++ .../warp-job-account-tracker/src/error.rs | 9 +- .../src/execute/account.rs | 99 ++++++++++++++++++- .../src/query/account.rs | 54 +++++++++- .../warp-job-account-tracker/src/state.rs | 9 +- packages/job-account-tracker/src/lib.rs | 55 +++++++++++ 10 files changed, 309 insertions(+), 38 deletions(-) diff --git a/contracts/warp-controller/src/execute/job.rs b/contracts/warp-controller/src/execute/job.rs index 47e902bb..80110d4c 100644 --- a/contracts/warp-controller/src/execute/job.rs +++ b/contracts/warp-controller/src/execute/job.rs @@ -1,5 +1,7 @@ use crate::state::{JobQueue, STATE}; -use crate::util::msg::build_account_execute_warp_msgs; +use crate::util::msg::{ + build_account_execute_warp_msgs, build_free_funding_account_msg, build_take_funding_account_msg, +}; use crate::ContractError; use controller::account::{AssetInfo, WarpMsgs}; use controller::job::{ @@ -24,7 +26,7 @@ use crate::{ }; use controller::{account::CwFund, Config}; -use job_account_tracker::{Account, AccountResponse, AccountsResponse}; +use job_account_tracker::{AccountResponse, FundingAccountResponse}; use resolver::QueryHydrateMsgsMsg; use super::fee::{compute_burn_fee, compute_creation_fee, compute_maintenance_fee}; @@ -134,38 +136,40 @@ pub fn create_job( }, )?; - let free_accounts: AccountsResponse = deps.querier.query_wasm_smart( + let job_account_resp: AccountResponse = deps.querier.query_wasm_smart( job_account_tracker_address_ref, - &job_account_tracker::QueryMsg::QueryFreeAccounts( - job_account_tracker::QueryFreeAccountsMsg { + &job_account_tracker::QueryMsg::QueryFirstFreeAccount( + job_account_tracker::QueryFirstFreeAccountMsg { account_owner_addr: job_owner.to_string(), - start_after: None, - limit: Some(2), }, ), )?; - let job_account = free_accounts.accounts.get(0); - - let funding_account: Option; + let funding_account_resp: FundingAccountResponse; if let Some(funding_account_addr) = data.funding_account { // fetch funding account and check if it exists, throw otherwise - let funding_account_response: AccountResponse = deps.querier.query_wasm_smart( + funding_account_resp = deps.querier.query_wasm_smart( job_account_tracker_address_ref, - &job_account_tracker::QueryMsg::QueryFreeAccount( - job_account_tracker::QueryFreeAccountMsg { + &job_account_tracker::QueryMsg::QueryFundingAccount( + job_account_tracker::QueryFundingAccountMsg { account_addr: funding_account_addr.to_string(), + account_owner_addr: info.sender.to_string(), }, ), )?; - - funding_account = Some(funding_account_response.account.unwrap()); } else { - funding_account = free_accounts.accounts.get(1).cloned(); + funding_account_resp = deps.querier.query_wasm_smart( + job_account_tracker_address_ref, + &job_account_tracker::QueryMsg::QueryFirstFreeFundingAccount( + job_account_tracker::QueryFirstFreeFundingAccountMsg { + account_owner_addr: job_owner.to_string(), + }, + ), + )?; } - match job_account { + match job_account_resp.account { None => { // Create account then create job in reply submsgs.push(SubMsg { @@ -266,7 +270,7 @@ pub fn create_job( } if data.recurring { - match funding_account { + match funding_account_resp.funding_account { None => { // Create funding account then create job in reply submsgs.push(SubMsg { @@ -290,8 +294,8 @@ pub fn create_job( attrs.push(Attribute::new("action", "create_funding_account_and_job")); } Some(available_account) => { - let available_account_addr = &available_account.addr; - // Update job.account from placeholder value to job account + let available_account_addr = &available_account.account_addr; + // Update funding_account from placeholder value to funding account job.funding_account = Some(available_account_addr.clone()); JobQueue::sync(&mut deps, env, job.clone())?; @@ -305,7 +309,7 @@ pub fn create_job( )); // Take account - msgs.push(build_taken_account_msg( + msgs.push(build_take_funding_account_msg( config.job_account_tracker_address.to_string(), job_owner.to_string(), available_account_addr.to_string(), @@ -401,7 +405,7 @@ pub fn delete_job( )); if let Some(funding_account) = job.funding_account { - msgs.push(build_free_account_msg( + msgs.push(build_free_funding_account_msg( config.job_account_tracker_address.to_string(), job.owner.to_string(), funding_account.to_string(), @@ -555,7 +559,7 @@ pub fn execute_job( )); if let Some(funding_account) = job.funding_account { - msgs.push(build_free_account_msg( + msgs.push(build_free_funding_account_msg( config.job_account_tracker_address.to_string(), job.owner.to_string(), funding_account.to_string(), diff --git a/contracts/warp-controller/src/reply/account.rs b/contracts/warp-controller/src/reply/account.rs index 8904dc75..3400efde 100644 --- a/contracts/warp-controller/src/reply/account.rs +++ b/contracts/warp-controller/src/reply/account.rs @@ -9,7 +9,7 @@ use crate::{ state::JobQueue, util::msg::{ build_account_execute_warp_msgs, build_taken_account_msg, build_transfer_cw20_msg, - build_transfer_cw721_msg, build_transfer_native_funds_msg, + build_transfer_cw721_msg, build_transfer_native_funds_msg, build_take_funding_account_msg, }, ContractError, }; @@ -226,8 +226,8 @@ pub fn create_funding_account_and_job( )) } - // Take job account - msgs.push(build_taken_account_msg( + // Take funding account + msgs.push(build_take_funding_account_msg( config.job_account_tracker_address.to_string(), job.owner.to_string(), funding_account_addr.to_string(), diff --git a/contracts/warp-controller/src/reply/job.rs b/contracts/warp-controller/src/reply/job.rs index 4deaa93e..3da05a90 100644 --- a/contracts/warp-controller/src/reply/job.rs +++ b/contracts/warp-controller/src/reply/job.rs @@ -11,7 +11,7 @@ use crate::{ legacy_account::is_legacy_account, msg::{ build_account_execute_generic_msgs, build_account_withdraw_assets_msg, - build_taken_account_msg, build_transfer_native_funds_msg, + build_taken_account_msg, build_transfer_native_funds_msg, build_take_funding_account_msg, }, }, ContractError, @@ -233,7 +233,7 @@ pub fn execute_job( )); // take funding account with new job - msgs.push(build_taken_account_msg( + msgs.push(build_take_funding_account_msg( config.job_account_tracker_address.to_string(), finished_job.owner.to_string(), funding_account_addr.to_string(), diff --git a/contracts/warp-controller/src/util/msg.rs b/contracts/warp-controller/src/util/msg.rs index 214d7596..5fbe44f2 100644 --- a/contracts/warp-controller/src/util/msg.rs +++ b/contracts/warp-controller/src/util/msg.rs @@ -5,7 +5,9 @@ use controller::account::{ WithdrawAssetsMsg, }; use job_account::GenericMsg; -use job_account_tracker::{FreeAccountMsg, TakeAccountMsg}; +use job_account_tracker::{ + FreeAccountMsg, FreeFundingAccountMsg, TakeAccountMsg, TakeFundingAccountMsg, +}; #[allow(clippy::too_many_arguments)] pub fn build_instantiate_warp_account_msg( @@ -73,6 +75,46 @@ pub fn build_taken_account_msg( }) } +pub fn build_free_funding_account_msg( + job_account_tracker_addr: String, + account_owner_addr: String, + account_addr: String, + job_id: Uint64, +) -> CosmosMsg { + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: job_account_tracker_addr, + msg: to_binary(&job_account_tracker::ExecuteMsg::FreeFundingAccount( + FreeFundingAccountMsg { + account_owner_addr, + account_addr, + job_id, + }, + )) + .unwrap(), + funds: vec![], + }) +} + +pub fn build_take_funding_account_msg( + job_account_tracker_addr: String, + account_owner_addr: String, + account_addr: String, + job_id: Uint64, +) -> CosmosMsg { + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: job_account_tracker_addr, + msg: to_binary(&job_account_tracker::ExecuteMsg::TakeFundingAccount( + TakeFundingAccountMsg { + account_owner_addr, + account_addr, + job_id, + }, + )) + .unwrap(), + funds: vec![], + }) +} + pub fn build_transfer_cw20_msg( cw20_token_contract_addr: String, owner_addr: String, diff --git a/contracts/warp-job-account-tracker/src/contract.rs b/contracts/warp-job-account-tracker/src/contract.rs index d77bb23f..fb75da1b 100644 --- a/contracts/warp-job-account-tracker/src/contract.rs +++ b/contracts/warp-job-account-tracker/src/contract.rs @@ -52,6 +52,14 @@ pub fn execute( nonpayable(&info).unwrap(); execute::account::free_account(deps, data) } + ExecuteMsg::TakeFundingAccount(data) => { + nonpayable(&info).unwrap(); + execute::account::take_funding_account(deps, data) + } + ExecuteMsg::FreeFundingAccount(data) => { + nonpayable(&info).unwrap(); + execute::account::free_funding_account(deps, data) + } } } @@ -71,6 +79,15 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::QueryFreeAccount(data) => { to_binary(&query::account::query_free_account(deps, data)?) } + QueryMsg::QueryFundingAccounts(data) => { + to_binary(&query::account::query_funding_accounts(deps, data)?) + } + QueryMsg::QueryFundingAccount(data) => { + to_binary(&query::account::query_funding_account(deps, data)?) + } + QueryMsg::QueryFirstFreeFundingAccount(data) => { + to_binary(&query::account::query_first_free_funding_account(deps, data)?) + } } } diff --git a/contracts/warp-job-account-tracker/src/error.rs b/contracts/warp-job-account-tracker/src/error.rs index 592558a5..65fa0305 100644 --- a/contracts/warp-job-account-tracker/src/error.rs +++ b/contracts/warp-job-account-tracker/src/error.rs @@ -38,14 +38,17 @@ pub enum ContractError { #[error("Error resolving JSON path")] ResolveError {}, - #[error("Sub account already taken")] + #[error("Account already taken")] AccountAlreadyTakenError {}, - #[error("Sub account already free")] + #[error("Account already free")] AccountAlreadyFreeError {}, - #[error("Sub account should be taken but it is free")] + #[error("Account should be taken but it is free")] AccountNotTakenError {}, + + #[error("Account not found")] + AccountNotFound {}, } impl From for ContractError { diff --git a/contracts/warp-job-account-tracker/src/execute/account.rs b/contracts/warp-job-account-tracker/src/execute/account.rs index 501bd114..a4051e3e 100644 --- a/contracts/warp-job-account-tracker/src/execute/account.rs +++ b/contracts/warp-job-account-tracker/src/execute/account.rs @@ -1,7 +1,9 @@ -use crate::state::{FREE_ACCOUNTS, TAKEN_ACCOUNTS}; +use crate::state::{FREE_ACCOUNTS, FUNDING_ACCOUNTS, TAKEN_ACCOUNTS, TAKEN_FUNDING_ACCOUNT_BY_JOB}; use crate::ContractError; use cosmwasm_std::{DepsMut, Response}; -use job_account_tracker::{FreeAccountMsg, TakeAccountMsg}; +use job_account_tracker::{ + FreeAccountMsg, FreeFundingAccountMsg, FundingAccount, TakeAccountMsg, TakeFundingAccountMsg, +}; pub fn taken_account(deps: DepsMut, data: TakeAccountMsg) -> Result { let account_owner_ref = &deps.api.addr_validate(data.account_owner_addr.as_str())?; @@ -38,3 +40,96 @@ pub fn free_account(deps: DepsMut, data: FreeAccountMsg) -> Result Result { + let account_owner_addr_ref = deps.api.addr_validate(&data.account_owner_addr)?; + let account_addr_ref = &deps.api.addr_validate(data.account_addr.as_str())?; + + TAKEN_FUNDING_ACCOUNT_BY_JOB.update(deps.storage, data.job_id.u64(), |s| match s { + // value is a dummy data because there is no built in support for set in cosmwasm + None => Ok(account_addr_ref.clone()), + Some(_) => Err(ContractError::AccountAlreadyTakenError {}), + })?; + + FUNDING_ACCOUNTS.update( + deps.storage, + &account_owner_addr_ref, + |accounts_opt| -> Result, ContractError> { + match accounts_opt { + None => { + // No funding accounts exist for this user, create a new vec + Ok(vec![FundingAccount { + account_addr: account_addr_ref.clone(), + taken_by_job_ids: vec![data.job_id], + }]) + } + Some(mut accounts) => { + // Check if a funding account with the specified address already exists + if let Some(funding_account) = accounts + .iter_mut() + .find(|acc| acc.account_addr == account_addr_ref.clone()) + { + // Funding account exists, update its job_ids + funding_account.taken_by_job_ids.push(data.job_id); + } else { + // Funding account does not exist, add a new one + accounts.push(FundingAccount { + account_addr: account_addr_ref.clone(), + taken_by_job_ids: vec![data.job_id], + }); + } + Ok(accounts) + } + } + }, + )?; + + Ok(Response::new() + .add_attribute("action", "take_funding_account") + .add_attribute("account_addr", data.account_addr) + .add_attribute("job_id", data.job_id.to_string())) +} + +pub fn free_funding_account( + deps: DepsMut, + data: FreeFundingAccountMsg, +) -> Result { + let account_owner_addr_ref = deps.api.addr_validate(&data.account_owner_addr)?; + let account_addr_ref = deps.api.addr_validate(&data.account_addr)?; + + TAKEN_FUNDING_ACCOUNT_BY_JOB.remove(deps.storage, data.job_id.u64()); + + FUNDING_ACCOUNTS.update( + deps.storage, + &account_owner_addr_ref, + |accounts_opt| -> Result, ContractError> { + match accounts_opt { + Some(mut accounts) => { + // Find the funding account with the specified address + if let Some(funding_account) = accounts + .iter_mut() + .find(|acc| acc.account_addr == account_addr_ref) + { + // Remove the specified job ID + funding_account + .taken_by_job_ids + .retain(|&id| id != data.job_id); + + Ok(accounts) + } else { + Err(ContractError::AccountNotFound {}) + } + } + None => Err(ContractError::AccountNotFound {}), + } + }, + )?; + + Ok(Response::new() + .add_attribute("action", "free_funding_account") + .add_attribute("account_addr", data.account_addr) + .add_attribute("job_id", data.job_id.to_string())) +} diff --git a/contracts/warp-job-account-tracker/src/query/account.rs b/contracts/warp-job-account-tracker/src/query/account.rs index c7d49444..b6ab2250 100644 --- a/contracts/warp-job-account-tracker/src/query/account.rs +++ b/contracts/warp-job-account-tracker/src/query/account.rs @@ -1,11 +1,13 @@ use cosmwasm_std::{Deps, Order, StdResult}; use cw_storage_plus::{Bound, PrefixBound}; -use crate::state::{CONFIG, FREE_ACCOUNTS, TAKEN_ACCOUNTS}; +use crate::state::{CONFIG, FREE_ACCOUNTS, FUNDING_ACCOUNTS, TAKEN_ACCOUNTS}; use job_account_tracker::{ - Account, AccountResponse, AccountsResponse, ConfigResponse, QueryFirstFreeAccountMsg, - QueryFreeAccountMsg, QueryFreeAccountsMsg, QueryTakenAccountsMsg, + Account, AccountResponse, AccountsResponse, ConfigResponse, FundingAccountResponse, + FundingAccountsResponse, QueryFirstFreeAccountMsg, QueryFirstFreeFundingAccountMsg, + QueryFreeAccountMsg, QueryFreeAccountsMsg, QueryFundingAccountMsg, QueryFundingAccountsMsg, + QueryTakenAccountsMsg, }; const QUERY_LIMIT: u32 = 50; @@ -140,3 +142,49 @@ pub fn query_free_account(deps: Deps, data: QueryFreeAccountMsg) -> StdResult StdResult { + let account_addr_ref = deps.api.addr_validate(data.account_addr.as_str())?; + let account_owner_addr_ref = deps.api.addr_validate(data.account_owner_addr.as_str())?; + + let funding_accounts = FUNDING_ACCOUNTS.load(deps.storage, &account_owner_addr_ref)?; + + Ok(FundingAccountResponse { + funding_account: funding_accounts + .iter() + .find(|fa| fa.account_addr == account_addr_ref.clone()) + .cloned(), + }) +} + +pub fn query_funding_accounts( + deps: Deps, + data: QueryFundingAccountsMsg, +) -> StdResult { + let account_owner_addr_ref = deps.api.addr_validate(data.account_owner_addr.as_str())?; + + let funding_accounts = FUNDING_ACCOUNTS.load(deps.storage, &account_owner_addr_ref)?; + + Ok(FundingAccountsResponse { funding_accounts }) +} + +pub fn query_first_free_funding_account( + deps: Deps, + data: QueryFirstFreeFundingAccountMsg, +) -> StdResult { + let account_owner_addr_ref = deps.api.addr_validate(data.account_owner_addr.as_str())?; + + let funding_accounts = FUNDING_ACCOUNTS.load(deps.storage, &account_owner_addr_ref)?; + + let funding_account = funding_accounts + .iter() + .find(|fa| fa.taken_by_job_ids.is_empty()) + .cloned(); + + Ok(FundingAccountResponse { funding_account }) +} diff --git a/contracts/warp-job-account-tracker/src/state.rs b/contracts/warp-job-account-tracker/src/state.rs index 5dca505e..8a34b7ee 100644 --- a/contracts/warp-job-account-tracker/src/state.rs +++ b/contracts/warp-job-account-tracker/src/state.rs @@ -1,6 +1,6 @@ use cosmwasm_std::{Addr, Uint64}; use cw_storage_plus::{Item, Map}; -use job_account_tracker::Config; +use job_account_tracker::{Config, FundingAccount}; pub const CONFIG: Item = Item::new("config"); @@ -9,3 +9,10 @@ pub const TAKEN_ACCOUNTS: Map<(&Addr, &Addr), Uint64> = Map::new("taken_accounts // Key is the (account owner address, account address), value is id of the last job which reserved it pub const FREE_ACCOUNTS: Map<(&Addr, &Addr), Uint64> = Map::new("free_accounts"); + +// owner address -> funding_account[] +// - user can have multiple funding accounts +// - a job can be assigned to only one funding account +// - funding account can fund multiple jobs +pub const FUNDING_ACCOUNTS: Map<&Addr, Vec> = Map::new("funding_accounts"); +pub const TAKEN_FUNDING_ACCOUNT_BY_JOB: Map = Map::new("funding_account_by_job"); \ No newline at end of file diff --git a/packages/job-account-tracker/src/lib.rs b/packages/job-account-tracker/src/lib.rs index b0fe77d7..b7b4bb7e 100644 --- a/packages/job-account-tracker/src/lib.rs +++ b/packages/job-account-tracker/src/lib.rs @@ -19,6 +19,8 @@ pub struct InstantiateMsg { pub enum ExecuteMsg { TakeAccount(TakeAccountMsg), FreeAccount(FreeAccountMsg), + TakeFundingAccount(TakeFundingAccountMsg), + FreeFundingAccount(FreeFundingAccountMsg), } #[cw_serde] @@ -35,6 +37,21 @@ pub struct FreeAccountMsg { pub last_job_id: Uint64, } +#[cw_serde] +pub struct TakeFundingAccountMsg { + pub account_owner_addr: String, + pub account_addr: String, + pub job_id: Uint64, +} + +#[cw_serde] +pub struct FreeFundingAccountMsg { + pub account_owner_addr: String, + pub account_addr: String, + pub job_id: Uint64, +} + + #[derive(QueryResponses)] #[cw_serde] pub enum QueryMsg { @@ -48,6 +65,12 @@ pub enum QueryMsg { QueryFirstFreeAccount(QueryFirstFreeAccountMsg), #[returns(AccountResponse)] QueryFreeAccount(QueryFreeAccountMsg), + #[returns(FundingAccountResponse)] + QueryFirstFreeFundingAccount(QueryFirstFreeFundingAccountMsg), + #[returns(FundingAccountsResponse)] + QueryFundingAccounts(QueryFundingAccountsMsg), + #[returns(FundingAccountResponse)] + QueryFundingAccount(QueryFundingAccountMsg), } #[cw_serde] @@ -78,6 +101,12 @@ pub struct Account { pub taken_by_job_id: Option, } +#[cw_serde] +pub struct FundingAccount { + pub account_addr: Addr, + pub taken_by_job_ids: Vec, // List of job IDs using this account +} + #[cw_serde] pub struct AccountsResponse { pub accounts: Vec, @@ -94,10 +123,36 @@ pub struct QueryFreeAccountMsg { pub account_addr: String, } +#[cw_serde] +pub struct QueryFirstFreeFundingAccountMsg { + pub account_owner_addr: String, +} + +#[cw_serde] +pub struct QueryFundingAccountMsg { + pub account_owner_addr: String, + pub account_addr: String, +} + +#[cw_serde] +pub struct QueryFundingAccountsMsg { + pub account_owner_addr: String, +} + +#[cw_serde] +pub struct FundingAccountsResponse { + pub funding_accounts: Vec, +} + #[cw_serde] pub struct AccountResponse { pub account: Option, } +#[cw_serde] +pub struct FundingAccountResponse { + pub funding_account: Option, +} + #[cw_serde] pub struct MigrateMsg {}