diff --git a/.github/workflows/Basic.yml b/.github/workflows/Basic.yml index 391640d7..1b44d607 100644 --- a/.github/workflows/Basic.yml +++ b/.github/workflows/Basic.yml @@ -53,7 +53,7 @@ jobs: run: cargo clippy -- -D warnings - name: Generate Schema - run: cargo schema --locked + run: cargo schema --locked --package proxy-contract - name: Schema Changes # fails if any changes not committed diff --git a/.github/workflows/Deploy-WASM-Storage.yml b/.github/workflows/Deploy-WASM-Storage.yml index cd6d3c9b..807dff25 100644 --- a/.github/workflows/Deploy-WASM-Storage.yml +++ b/.github/workflows/Deploy-WASM-Storage.yml @@ -7,7 +7,7 @@ on: name: Deploy WASM storage to testnet jobs: - check_pass: + check_pass: name: Check password runs-on: ubuntu-latest outputs: diff --git a/.github/workflows/Deploy.yml b/.github/workflows/Deploy.yml index 08905905..af706d0f 100644 --- a/.github/workflows/Deploy.yml +++ b/.github/workflows/Deploy.yml @@ -9,13 +9,14 @@ on: type: choice options: - proxy_contract - - seda_chain_contracts + - data_requests + - staking password: required: true proxy: required: false -name: Deploy Seda Contract +name: Deploy SEDA Contract jobs: check_pass: @@ -136,7 +137,7 @@ jobs: devAccount=${{ env.DEV_ACCOUNT }} if [[ "${{ github.event.inputs.contract }}" != "proxy_contract" ]]; then proxy=${{ github.event.inputs.proxy }} - OUTPUT=$(./seda-chaind tx wasm instantiate ${{ env.code_id }} '{"token":"aseda","proxy":"${proxy}"}' --no-admin --from ${devAccount} --node ${nodeUrl} --keyring-dir . --keyring-backend test --label ${{ env.code_id }} --gas-prices 0.1aseda --gas auto --gas-adjustment 1.3 -y --output json) + OUTPUT=$(./seda-chaind tx wasm instantiate ${{ env.code_id }} '{"token":"aseda", "proxy": ${{ toJSON(github.event.inputs.proxy) }} }' --no-admin --from ${devAccount} --node ${nodeUrl} --keyring-dir . --keyring-backend test --label ${{ env.code_id }} --gas-prices 0.1aseda --gas auto --gas-adjustment 1.3 -y --output json) else OUTPUT=$(./seda-chaind tx wasm instantiate ${{ env.code_id }} '{"token":"aseda"}' --no-admin --from ${devAccount} --node ${nodeUrl} --keyring-dir . --keyring-backend test --label ${{ env.code_id }} --gas-prices 0.1aseda --gas auto --gas-adjustment 1.3 -y --output json) fi diff --git a/Cargo.lock b/Cargo.lock index c7258123..11a93123 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,9 +15,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.72" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "base16ct" @@ -69,9 +69,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cfg-if" @@ -274,6 +274,23 @@ dependencies = [ "thiserror", ] +[[package]] +name = "data-requests" +version = "0.1.0" +dependencies = [ + "common", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-storage-plus", + "cw2", + "hex", + "schemars", + "serde", + "sha3", + "thiserror", +] + [[package]] name = "der" version = "0.6.1" @@ -456,12 +473,13 @@ dependencies = [ "cw-multi-test", "cw-storage-plus", "cw2", + "data-requests", "hex", "proxy-contract", "schemars", - "seda-chain-contracts", "serde", "sha3", + "staking", "thiserror", ] @@ -653,24 +671,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "seda-chain-contracts" -version = "0.1.0" -dependencies = [ - "common", - "cosmwasm-schema", - "cosmwasm-std", - "cosmwasm-storage", - "cw-multi-test", - "cw-storage-plus", - "cw2", - "hex", - "schemars", - "serde", - "sha3", - "thiserror", -] - [[package]] name = "semver" version = "1.0.18" @@ -782,6 +782,18 @@ dependencies = [ "der", ] +[[package]] +name = "staking" +version = "0.1.0" +dependencies = [ + "common", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "cw2", + "thiserror", +] + [[package]] name = "subtle" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 99f529d5..40a7194b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,8 @@ [workspace] resolver = "2" members = [ - "packages/seda-chain-contracts", + "packages/staking", + "packages/data-requests", "packages/wasm-bin-storage", "packages/integration-tests", "packages/proxy" @@ -28,7 +29,8 @@ cw2 = "1.1" hex = "0.4.3" proxy-contract = { path = "./packages/proxy" } schemars = "0.8.10" -seda-chain-contracts = { path = "./packages/seda-chain-contracts" } +data-requests = { path = "./packages/data-requests" } +staking = { path = "./packages/staking" } serde = { version = "1.0.145", default-features = false, features = ["derive"] } sha3 = "0.10.8" thiserror = { version = "1.0.31" } diff --git a/DEVELOPING.md b/DEVELOPING.md index 90abab79..b22ed0b6 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -43,7 +43,7 @@ We must mount the contract code to `/code`. You can use a absolute path instead This is rather slow compared to local compilations, especially the first compile of a given contract. The use of the two volume caches is very useful to speed up following compiles of the same contract. -This produces an `artifacts` directory with a `seda_chain_contracts.wasm`, as well as `checksums.txt`, containing the Sha256 hash of the wasm file. The wasm file is compiled deterministically (anyone else running the same docker on the same git commit should get the identical file with the same Sha256 hash). It is also stripped and minimized for upload to a blockchain (we will also gzip it in the uploading process to make it even smaller). +This produces an `artifacts` directory including a WASM binary for each contract, as well as `checksums.txt`, containing the Sha256 hash of the wasm file. The wasm file is compiled deterministically (anyone else running the same docker on the same git commit should get the identical file with the same Sha256 hash). It is also stripped and minimized for upload to a blockchain (we will also gzip it in the uploading process to make it even smaller). ## Verification diff --git a/packages/common/src/msg.rs b/packages/common/src/msg.rs index a0a0cb12..38f303b1 100644 --- a/packages/common/src/msg.rs +++ b/packages/common/src/msg.rs @@ -20,7 +20,7 @@ pub struct PostDataRequestArgs { } #[cw_serde] -pub enum ExecuteMsg { +pub enum DataRequestsExecuteMsg { PostDataRequest { posted_dr: PostDataRequestArgs, }, @@ -34,6 +34,10 @@ pub enum ExecuteMsg { reveal: Reveal, sender: Option, }, +} + +#[cw_serde] +pub enum StakingExecuteMsg { RegisterDataRequestExecutor { p2p_multi_address: Option, sender: Option, @@ -56,7 +60,7 @@ pub enum ExecuteMsg { #[cw_serde] #[derive(QueryResponses)] -pub enum QueryMsg { +pub enum DataRequestsQueryMsg { #[returns(GetDataRequestResponse)] GetDataRequest { dr_id: Hash }, #[returns(GetDataRequestsFromPoolResponse)] @@ -74,8 +78,15 @@ pub enum QueryMsg { GetRevealedDataResults { dr_id: Hash }, #[returns(GetResolvedDataResultResponse)] GetResolvedDataResult { dr_id: Hash }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum StakingQueryMsg { #[returns(GetDataRequestExecutorResponse)] GetDataRequestExecutor { executor: Addr }, + #[returns(IsDataRequestExecutorEligibleResponse)] + IsDataRequestExecutorEligible { executor: Addr }, } #[cw_serde] @@ -126,3 +137,8 @@ pub struct GetDataRequestExecutorResponse { pub struct GetCommittedExecutorsResponse { pub value: Vec, } + +#[cw_serde] +pub struct IsDataRequestExecutorEligibleResponse { + pub value: bool, +} diff --git a/packages/seda-chain-contracts/Cargo.toml b/packages/data-requests/Cargo.toml similarity index 94% rename from packages/seda-chain-contracts/Cargo.toml rename to packages/data-requests/Cargo.toml index feb40842..ed9630e0 100644 --- a/packages/seda-chain-contracts/Cargo.toml +++ b/packages/data-requests/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "seda-chain-contracts" +name = "data-requests" version = "0.1.0" edition = "2021" @@ -39,6 +39,3 @@ schemars = { workspace = true } serde = { workspace = true } sha3 = { workspace = true } thiserror = { workspace = true } - -[dev-dependencies] -cw-multi-test = "0.16.2" diff --git a/packages/data-requests/src/bin/schema.rs b/packages/data-requests/src/bin/schema.rs new file mode 100644 index 00000000..a574addd --- /dev/null +++ b/packages/data-requests/src/bin/schema.rs @@ -0,0 +1,12 @@ +use cosmwasm_schema::write_api; + +use common::msg::{DataRequestsExecuteMsg, DataRequestsQueryMsg}; +use data_requests::msg::InstantiateMsg; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: DataRequestsExecuteMsg, + query: DataRequestsQueryMsg, + } +} diff --git a/packages/data-requests/src/contract.rs b/packages/data-requests/src/contract.rs new file mode 100644 index 00000000..70f78913 --- /dev/null +++ b/packages/data-requests/src/contract.rs @@ -0,0 +1,104 @@ +use common::msg::DataRequestsExecuteMsg as ExecuteMsg; +use common::msg::DataRequestsQueryMsg as QueryMsg; +#[cfg(not(feature = "library"))] +use cosmwasm_std::{entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; +use cw2::set_contract_version; + +use crate::data_request::data_requests; +use crate::error::ContractError; +use crate::msg::InstantiateMsg; +use crate::state::{DATA_REQUESTS_COUNT, PROXY_CONTRACT, TOKEN}; + +use crate::data_request_result::data_request_results; +use cosmwasm_std::StdResult; + +// version info for migration info +const CONTRACT_NAME: &str = "data-requests"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + _info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + DATA_REQUESTS_COUNT.save(deps.storage, &0)?; + TOKEN.save(deps.storage, &msg.token)?; + PROXY_CONTRACT.save(deps.storage, &deps.api.addr_validate(&msg.proxy)?)?; + Ok(Response::new().add_attribute("method", "instantiate")) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::PostDataRequest { posted_dr } => { + data_requests::post_data_request(deps, info, posted_dr) + } + ExecuteMsg::CommitDataResult { + dr_id, + commitment, + sender, + } => data_request_results::commit_result(deps, info, dr_id, commitment, sender), + ExecuteMsg::RevealDataResult { + dr_id, + reveal, + sender, + } => data_request_results::reveal_result(deps, info, env, dr_id, reveal, sender), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetDataRequest { dr_id } => { + to_binary(&data_requests::get_data_request(deps, dr_id)?) + } + QueryMsg::GetDataRequestsFromPool { position, limit } => to_binary( + &data_requests::get_data_requests_from_pool(deps, position, limit)?, + ), + QueryMsg::GetCommittedDataResult { dr_id, executor } => to_binary( + &data_request_results::get_committed_data_result(deps, dr_id, executor)?, + ), + QueryMsg::GetCommittedDataResults { dr_id } => to_binary( + &data_request_results::get_committed_data_results(deps, dr_id)?, + ), + QueryMsg::GetRevealedDataResult { dr_id, executor } => to_binary( + &data_request_results::get_revealed_data_result(deps, dr_id, executor)?, + ), + QueryMsg::GetRevealedDataResults { dr_id } => to_binary( + &data_request_results::get_revealed_data_results(deps, dr_id)?, + ), + QueryMsg::GetResolvedDataResult { dr_id } => to_binary( + &data_request_results::get_resolved_data_result(deps, dr_id)?, + ), + } +} + +#[cfg(test)] +mod init_tests { + use super::*; + use cosmwasm_std::coins; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + + #[test] + fn proper_initialization() { + let mut deps = mock_dependencies(); + + let msg = InstantiateMsg { + token: "token".to_string(), + proxy: "proxy".to_string(), + }; + let info = mock_info("creator", &coins(1000, "token")); + + // we can just call .unwrap() to assert this was a success + let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + assert_eq!(0, res.messages.len()); + } +} diff --git a/packages/seda-chain-contracts/src/data_request.rs b/packages/data-requests/src/data_request.rs similarity index 87% rename from packages/seda-chain-contracts/src/data_request.rs rename to packages/data-requests/src/data_request.rs index 8d1ccd04..0d7c9533 100644 --- a/packages/seda-chain-contracts/src/data_request.rs +++ b/packages/data-requests/src/data_request.rs @@ -154,12 +154,12 @@ mod dr_tests { use crate::contract::query; use crate::msg::InstantiateMsg; use crate::state::DataRequestInputs; - use crate::state::ELIGIBLE_DATA_REQUEST_EXECUTORS; use crate::utils::hash_data_request; use crate::utils::hash_update; + use common::msg::DataRequestsExecuteMsg as ExecuteMsg; + use common::msg::DataRequestsQueryMsg as QueryMsg; use common::msg::GetDataRequestResponse; use common::msg::PostDataRequestArgs; - use common::msg::{ExecuteMsg, QueryMsg}; use common::state::Reveal; use common::types::Bytes; use common::types::Commitment; @@ -797,119 +797,4 @@ mod dr_tests { response ); } - - #[test] - fn no_duplicate_dr_ids() { - let mut deps = mock_dependencies(); - - let msg = InstantiateMsg { - token: "token".to_string(), - proxy: "proxy".to_string(), - }; - let info = mock_info("creator", &coins(2, "token")); - let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); - - // register dr executor - let info = mock_info("anyone", &coins(1, "token")); - let msg = ExecuteMsg::RegisterDataRequestExecutor { - p2p_multi_address: Some("address".to_string()), - sender: None, - }; - - let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - let executor_is_eligible: bool = ELIGIBLE_DATA_REQUEST_EXECUTORS - .load(&deps.storage, info.sender.clone()) - .unwrap(); - assert!(executor_is_eligible); - - let dr_binary_id: Hash = "".to_string(); - let tally_binary_id: Hash = "".to_string(); - let dr_inputs: Bytes = Vec::new(); - let tally_inputs: Bytes = Vec::new(); - - let replication_factor: u16 = 3; - - // set by dr creator - let gas_price: u128 = 10; - let gas_limit: u128 = 10; - - // set by relayer and SEDA protocol - let seda_payload: Bytes = Vec::new(); - - let chain_id = 31337; - let nonce = 1; - let value = 1; - let mut hasher = Keccak256::new(); - hash_update(&mut hasher, &chain_id); - hash_update(&mut hasher, &nonce); - hash_update(&mut hasher, &value); - let binary_hash1 = format!("0x{}", hex::encode(hasher.clone().finalize())); - let memo1: Memo = binary_hash1.clone().into_bytes(); - let payback_address: Bytes = Vec::new(); - - let dr_inputs1 = DataRequestInputs { - dr_binary_id: dr_binary_id.clone(), - tally_binary_id: tally_binary_id.clone(), - dr_inputs: dr_inputs.clone(), - tally_inputs: tally_inputs.clone(), - memo: memo1.clone(), - replication_factor, - - gas_price, - gas_limit, - - seda_payload: seda_payload.clone(), - payback_address: payback_address.clone(), - }; - let constructed_dr_id1 = hash_data_request(dr_inputs1); - - let posted_dr: PostDataRequestArgs = PostDataRequestArgs { - dr_id: constructed_dr_id1.clone(), - dr_binary_id: dr_binary_id.clone(), - tally_binary_id: tally_binary_id.clone(), - dr_inputs: dr_inputs.clone(), - tally_inputs: tally_inputs.clone(), - - memo: memo1.clone(), - replication_factor, - gas_price, - gas_limit, - seda_payload: seda_payload.clone(), - payback_address: payback_address.clone(), - }; - // someone posts a data request - let info = mock_info("anyone", &coins(2, "token")); - let msg = ExecuteMsg::PostDataRequest { posted_dr }; - let _res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); - - // someone posts a data result - let info = mock_info("anyone", &coins(2, "token")); - let msg = ExecuteMsg::CommitDataResult { - dr_id: constructed_dr_id1.clone(), - commitment: "dr 0 result".to_string(), - sender: None, - }; - let _res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); - - let posted_dr: PostDataRequestArgs = PostDataRequestArgs { - dr_id: constructed_dr_id1.clone(), - dr_binary_id: dr_binary_id.clone(), - tally_binary_id: tally_binary_id.clone(), - dr_inputs: dr_inputs.clone(), - tally_inputs: tally_inputs.clone(), - - memo: memo1.clone(), - replication_factor, - gas_price, - gas_limit, - seda_payload: seda_payload.clone(), - payback_address: payback_address.clone(), - }; - - // can't create a data request with the same id as a data result - let info = mock_info("anyone", &coins(2, "token")); - let msg = ExecuteMsg::PostDataRequest { posted_dr }; - let res = execute(deps.as_mut(), mock_env(), info, msg); - assert!(res.is_err()); - } } diff --git a/packages/data-requests/src/data_request_result.rs b/packages/data-requests/src/data_request_result.rs new file mode 100644 index 00000000..64a42b4b --- /dev/null +++ b/packages/data-requests/src/data_request_result.rs @@ -0,0 +1,232 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::{Deps, DepsMut, MessageInfo, Response, StdResult}; + +use crate::state::DATA_REQUESTS; +use common::msg::{GetCommittedDataResultResponse, GetRevealedDataResultResponse}; +use common::types::Hash; + +use crate::error::ContractError; + +pub mod data_request_results { + + use cosmwasm_std::{Addr, Env}; + use sha3::{Digest, Keccak256}; + + use common::msg::{ + GetCommittedDataResultsResponse, GetCommittedExecutorsResponse, GetIdsResponse, + GetResolvedDataResultResponse, GetRevealedDataResultsResponse, + }; + use common::state::{DataResult, Reveal}; + use common::types::Bytes; + + use crate::{ + state::DATA_RESULTS, + utils::{check_eligibility, hash_data_result, validate_sender}, + ContractError::{ + AlreadyCommitted, AlreadyRevealed, IneligibleExecutor, NotCommitted, RevealMismatch, + RevealNotStarted, + }, + }; + + use super::*; + + /// Posts a data result of a data request with an attached hash of the answer and salt. + /// This removes the data request from the pool and creates a new entry in the data results. + pub fn commit_result( + deps: DepsMut, + info: MessageInfo, + dr_id: Hash, + commitment: Hash, + sender: Option, + ) -> Result { + let sender = validate_sender(&deps, info.sender, sender)?; + if !check_eligibility(&deps, sender.clone())? { + return Err(IneligibleExecutor); + } + + // find the data request from the pool (if it exists, otherwise error) + let mut dr = DATA_REQUESTS.load(deps.storage, dr_id.clone())?; + if dr.commits.contains_key(&sender.to_string()) { + return Err(AlreadyCommitted); + } + dr.commits.insert(sender.to_string(), commitment.clone()); + + DATA_REQUESTS.save(deps.storage, dr_id.clone(), &dr)?; + + Ok(Response::new() + .add_attribute("action", "commit_result") + .add_attribute("dr_id", dr_id) + .add_attribute("result", commitment)) + } + + /// Posts a data result of a data request with an attached result. + /// This removes the data request from the pool and creates a new entry in the data results. + pub fn reveal_result( + deps: DepsMut, + info: MessageInfo, + env: Env, + dr_id: Hash, + reveal: Reveal, + sender: Option, + ) -> Result { + let sender = validate_sender(&deps, info.sender, sender)?; + if !check_eligibility(&deps, sender.clone())? { + return Err(IneligibleExecutor); + } + + // find the data request from the committed pool (if it exists, otherwise error) + let mut dr = DATA_REQUESTS.load(deps.storage, dr_id.clone())?; + let committed_dr_results = dr.clone().commits; + + if u16::try_from(committed_dr_results.len()).unwrap() < dr.replication_factor { + return Err(RevealNotStarted); + } + if !committed_dr_results.contains_key(&sender.to_string()) { + return Err(NotCommitted); + } + if dr.reveals.contains_key(&sender.to_string()) { + return Err(AlreadyRevealed); + } + + let committed_dr_result = committed_dr_results + .get(&sender.to_string()) + .unwrap() + .clone(); + + let calculated_dr_result = compute_hash(&reveal.reveal, &reveal.salt); + if calculated_dr_result != committed_dr_result { + return Err(RevealMismatch); + } + + dr.reveals.insert(sender.to_string(), reveal.clone()); + + DATA_REQUESTS.save(deps.storage, dr_id.clone(), &dr)?; + + if u16::try_from(dr.reveals.len()).unwrap() == dr.replication_factor { + let block_height: u64 = env.block.height; + let exit_code: u8 = 0; + let result: Bytes = reveal.reveal.as_bytes().to_vec(); + + let payback_address: Bytes = dr.payback_address.clone(); + let seda_payload: Bytes = dr.seda_payload.clone(); + + let result_id = hash_data_result(&dr, block_height, exit_code, &result); + + let dr_result = DataResult { + result_id, + dr_id: dr_id.clone(), + block_height, + exit_code, + result, + payback_address, + seda_payload, + }; + DATA_RESULTS.save(deps.storage, dr_id.clone(), &dr_result)?; + DATA_REQUESTS.remove(deps.storage, dr_id.clone()); + } + + Ok(Response::new() + .add_attribute("action", "reveal_result") + .add_attribute("dr_id", dr_id) + .add_attribute("reveal", reveal.reveal)) + } + + /// Returns a data result from the results with the given id, if it exists. + pub fn get_committed_data_result( + deps: Deps, + dr_id: Hash, + executor: Addr, + ) -> StdResult { + let dr = DATA_REQUESTS.load(deps.storage, dr_id)?; + let commitment = dr.commits.get(&executor.to_string()); + Ok(GetCommittedDataResultResponse { + value: commitment.cloned(), + }) + } + + /// Returns a data result from the results with the given id, if it exists. + pub fn get_committed_data_results( + deps: Deps, + dr_id: Hash, + ) -> StdResult { + let dr = DATA_REQUESTS.load(deps.storage, dr_id)?; + Ok(GetCommittedDataResultsResponse { value: dr.commits }) + } + + /// Returns a data result from the results with the given id, if it exists. + pub fn get_revealed_data_result( + deps: Deps, + dr_id: Hash, + executor: Addr, + ) -> StdResult { + let dr = DATA_REQUESTS.load(deps.storage, dr_id)?; + let reveal = dr.reveals.get(&executor.to_string()); + Ok(GetRevealedDataResultResponse { + value: reveal.cloned(), + }) + } + + /// Returns a data result from the results with the given id, if it exists. + pub fn get_revealed_data_results( + deps: Deps, + dr_id: Hash, + ) -> StdResult { + let dr = DATA_REQUESTS.load(deps.storage, dr_id)?; + Ok(GetRevealedDataResultsResponse { value: dr.reveals }) + } + + /// Returns a data result from the results with the given id, if it exists. + pub fn get_resolved_data_result( + deps: Deps, + dr_id: Hash, + ) -> StdResult { + let result = DATA_RESULTS.load(deps.storage, dr_id)?; + Ok(GetResolvedDataResultResponse { value: result }) + } + + /// Returns a vector of data requests ids + pub fn get_drs_ids(deps: Deps) -> StdResult { + let mut ids = Vec::new(); + for (_, key) in DATA_REQUESTS + .keys(deps.storage, None, None, cosmwasm_std::Order::Ascending) + .enumerate() + { + ids.push(key?) + } + Ok(GetIdsResponse { value: ids }) + } + + /// Returns a vector of data results ids + pub fn get_results_ids(deps: Deps) -> StdResult { + let mut ids = Vec::new(); + for (_, key) in DATA_RESULTS + .keys(deps.storage, None, None, cosmwasm_std::Order::Ascending) + .enumerate() + { + ids.push(key?) + } + Ok(GetIdsResponse { value: ids }) + } + + /// Returns a vector of committed executors + pub fn get_committed_executors( + deps: Deps, + dr_id: Hash, + ) -> StdResult { + let mut executors = Vec::new(); + for key in DATA_REQUESTS.load(deps.storage, dr_id)?.commits.keys() { + executors.push(key.clone()) + } + Ok(GetCommittedExecutorsResponse { value: executors }) + } + + /// Returns a vector of revealed data requests ids, if it exists. + + fn compute_hash(reveal: &str, salt: &str) -> String { + let mut hasher = Keccak256::new(); + hasher.update(reveal.as_bytes()); + hasher.update(salt.as_bytes()); + let digest = hasher.finalize(); + format!("0x{}", hex::encode(digest)) + } +} diff --git a/packages/seda-chain-contracts/src/error.rs b/packages/data-requests/src/error.rs similarity index 91% rename from packages/seda-chain-contracts/src/error.rs rename to packages/data-requests/src/error.rs index 0136d06e..a839791d 100644 --- a/packages/seda-chain-contracts/src/error.rs +++ b/packages/data-requests/src/error.rs @@ -10,8 +10,6 @@ pub enum ContractError { NoFunds, #[error("Insufficient funds. Required: {0}, available: {1}")] InsufficientFunds(u128, u128), - #[error("Executor has staked tokens or tokens pending withdrawal")] - ExecutorHasTokens, #[error("Invalid data request id, expected: {0}, actual: {1}")] InvalidDataRequestId(Hash, Hash), #[error("Data request already exists")] diff --git a/packages/seda-chain-contracts/src/lib.rs b/packages/data-requests/src/lib.rs similarity index 74% rename from packages/seda-chain-contracts/src/lib.rs rename to packages/data-requests/src/lib.rs index 2552e28d..84776dda 100644 --- a/packages/seda-chain-contracts/src/lib.rs +++ b/packages/data-requests/src/lib.rs @@ -1,11 +1,8 @@ -pub mod consts; pub mod contract; pub mod data_request; pub mod data_request_result; mod error; -pub mod executors_registry; pub mod msg; -pub mod staking; pub mod state; pub mod utils; pub use crate::error::ContractError; diff --git a/packages/seda-chain-contracts/src/msg.rs b/packages/data-requests/src/msg.rs similarity index 100% rename from packages/seda-chain-contracts/src/msg.rs rename to packages/data-requests/src/msg.rs diff --git a/packages/seda-chain-contracts/src/state.rs b/packages/data-requests/src/state.rs similarity index 77% rename from packages/seda-chain-contracts/src/state.rs rename to packages/data-requests/src/state.rs index feb6d084..10290e6c 100644 --- a/packages/seda-chain-contracts/src/state.rs +++ b/packages/data-requests/src/state.rs @@ -1,4 +1,4 @@ -use common::state::{DataRequest, DataRequestExecutor, DataResult}; +use common::state::{DataRequest, DataResult}; use common::types::{Bytes, Hash}; use cosmwasm_std::Addr; use cw_storage_plus::{Item, Map}; @@ -46,16 +46,9 @@ pub const DATA_REQUESTS_BY_NONCE: Map = Map::new("DATA_REQUESTS_BY_N /// An auto-incrementing counter for the data requests pub const DATA_REQUESTS_COUNT: Item = Item::new("data_requests_count"); -/// A map of data request executors (of address to info) that have not yet been marked as active -pub const DATA_REQUEST_EXECUTORS: Map = - Map::new("data_request_executors"); - -/// Address of the token used for data request executor staking +/// Address of the token used for deposit for posting a data request +// TODO: implement deposit for posting data requests pub const TOKEN: Item = Item::new("token"); -/// A map of data request executors (of address to info) that are eligible for committee inclusion -pub const ELIGIBLE_DATA_REQUEST_EXECUTORS: Map = - Map::new("eligible_data_request_executors"); - /// Address of proxy contract which has permission to set the sender on one's behalf pub const PROXY_CONTRACT: Item = Item::new("proxy_contract"); diff --git a/packages/seda-chain-contracts/src/types.rs b/packages/data-requests/src/types.rs similarity index 100% rename from packages/seda-chain-contracts/src/types.rs rename to packages/data-requests/src/types.rs diff --git a/packages/seda-chain-contracts/src/utils.rs b/packages/data-requests/src/utils.rs similarity index 75% rename from packages/seda-chain-contracts/src/utils.rs rename to packages/data-requests/src/utils.rs index 515f364c..35b848b1 100644 --- a/packages/seda-chain-contracts/src/utils.rs +++ b/packages/data-requests/src/utils.rs @@ -1,32 +1,23 @@ use crate::error::ContractError; +use common::msg::{IsDataRequestExecutorEligibleResponse, StakingQueryMsg}; use common::state::DataRequest; use common::types::Bytes; -use cosmwasm_std::{Addr, Coin, DepsMut}; +use cosmwasm_std::{to_binary, Addr, Coin, DepsMut, QueryRequest, WasmQuery}; use sha3::{Digest, Keccak256}; -use crate::{ - consts::MINIMUM_STAKE_FOR_COMMITTEE_ELIGIBILITY, - state::{DataRequestInputs, ELIGIBLE_DATA_REQUEST_EXECUTORS, PROXY_CONTRACT}, -}; - -pub fn apply_validator_eligibility( - deps: DepsMut, - sender: Addr, - tokens_staked: u128, -) -> Result<(), ContractError> { - if tokens_staked < MINIMUM_STAKE_FOR_COMMITTEE_ELIGIBILITY { - if ELIGIBLE_DATA_REQUEST_EXECUTORS.has(deps.storage, sender.clone()) { - ELIGIBLE_DATA_REQUEST_EXECUTORS.remove(deps.storage, sender); - } - } else { - ELIGIBLE_DATA_REQUEST_EXECUTORS.save(deps.storage, sender, &true)?; - } - Ok(()) -} +use crate::state::{DataRequestInputs, PROXY_CONTRACT}; pub fn check_eligibility(deps: &DepsMut, dr_executor: Addr) -> Result { - let is_eligible = ELIGIBLE_DATA_REQUEST_EXECUTORS.load(deps.storage, dr_executor)?; - Ok(is_eligible) + // query proxy contract to see if this executor is eligible + let msg = StakingQueryMsg::IsDataRequestExecutorEligible { + executor: dr_executor, + }; + let query_response: IsDataRequestExecutorEligibleResponse = + deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: PROXY_CONTRACT.load(deps.storage)?.to_string(), + msg: to_binary(&msg)?, + }))?; + Ok(query_response.value) } pub fn pad_to_32_bytes(value: &u128) -> [u8; 32] { diff --git a/packages/integration-tests/Cargo.toml b/packages/integration-tests/Cargo.toml index 19e8c27f..d94a258f 100644 --- a/packages/integration-tests/Cargo.toml +++ b/packages/integration-tests/Cargo.toml @@ -35,7 +35,8 @@ cw2 = { workspace = true } hex = { workspace = true } proxy-contract = { workspace = true } schemars = { workspace = true } -seda-chain-contracts = { workspace = true } +data-requests = { workspace = true } +staking = { workspace = true } serde = { workspace = true } sha3 = { workspace = true } thiserror = { workspace = true } diff --git a/packages/integration-tests/src/data_request.rs b/packages/integration-tests/src/data_request.rs new file mode 100644 index 00000000..65f3bb2d --- /dev/null +++ b/packages/integration-tests/src/data_request.rs @@ -0,0 +1,105 @@ +use crate::tests::utils::{proper_instantiate, USER}; +use common::msg::{GetDataRequestResponse, GetDataRequestsFromPoolResponse, PostDataRequestArgs}; +use common::types::{Bytes, Hash, Memo}; +use cosmwasm_std::Addr; +use cw_multi_test::Executor; +use data_requests::state::DataRequestInputs; +use data_requests::utils::{hash_data_request, hash_update}; +use proxy_contract::msg::{ProxyExecuteMsg, ProxyQueryMsg}; +use sha3::{Digest, Keccak256}; + +#[test] +fn post_data_request() { + let (mut app, proxy_contract) = proper_instantiate(); + + // format inputs to post data request + let dr_binary_id: Hash = "".to_string(); + let tally_binary_id: Hash = "".to_string(); + let dr_inputs: Bytes = Vec::new(); + let tally_inputs: Bytes = Vec::new(); + let replication_factor: u16 = 3; + let gas_price: u128 = 10; + let gas_limit: u128 = 10; + let seda_payload: Bytes = Vec::new(); + let chain_id = 31337; + let nonce = 1; + let value = "test".to_string(); + let mut hasher = Keccak256::new(); + hash_update(&mut hasher, &chain_id); + hash_update(&mut hasher, &nonce); + hasher.update(value); + let binary_hash = format!("0x{}", hex::encode(hasher.finalize())); + let memo1: Memo = binary_hash.clone().into_bytes(); + let payback_address: Bytes = Vec::new(); + let dr_inputs1 = DataRequestInputs { + dr_binary_id: dr_binary_id.clone(), + tally_binary_id: tally_binary_id.clone(), + dr_inputs: dr_inputs.clone(), + tally_inputs: tally_inputs.clone(), + memo: memo1.clone(), + replication_factor, + gas_price, + gas_limit, + seda_payload: seda_payload.clone(), + payback_address: payback_address.clone(), + }; + let constructed_dr_id: String = hash_data_request(dr_inputs1); + let payback_address: Bytes = Vec::new(); + let posted_dr: PostDataRequestArgs = PostDataRequestArgs { + dr_id: constructed_dr_id, + dr_binary_id, + tally_binary_id, + dr_inputs, + tally_inputs, + memo: memo1, + replication_factor, + gas_price, + gas_limit, + seda_payload, + payback_address, + }; + + // post the data request + let msg = ProxyExecuteMsg::PostDataRequest { posted_dr }; + let cosmos_msg = proxy_contract.call(msg).unwrap(); + let res = app + .execute(Addr::unchecked(USER), cosmos_msg.clone()) + .unwrap(); + + // try posting again, expecting error + assert!(app.execute(Addr::unchecked(USER), cosmos_msg).is_err()); + + // should be able to fetch data request + // TODO: this is ugly to loop through events, use Response.data once it's merged + let dr_id = &res.events.last().unwrap().attributes.last().unwrap().value; + let msg = ProxyQueryMsg::GetDataRequest { + dr_id: dr_id.clone(), + }; + let res: GetDataRequestResponse = app + .wrap() + .query_wasm_smart(proxy_contract.addr(), &msg) + .unwrap(); + assert!(res.value.is_some()); + + // can also fetch it via `get_data_requests_from_pool` + let msg = ProxyQueryMsg::GetDataRequestsFromPool { + position: None, + limit: None, + }; + let res: GetDataRequestsFromPoolResponse = app + .wrap() + .query_wasm_smart(proxy_contract.addr(), &msg) + .unwrap(); + assert_eq!(res.value.len(), 1); + assert_eq!(res.value.first().unwrap().dr_id, dr_id.clone()); + + // non-existent data request should fail + let msg = ProxyQueryMsg::GetDataRequest { + dr_id: "non-existent".to_string(), + }; + let res: GetDataRequestResponse = app + .wrap() + .query_wasm_smart(proxy_contract.addr(), &msg) + .unwrap(); + assert!(res.value.is_none()); +} diff --git a/packages/integration-tests/src/data_result.rs b/packages/integration-tests/src/data_result.rs new file mode 100644 index 00000000..394e93bb --- /dev/null +++ b/packages/integration-tests/src/data_result.rs @@ -0,0 +1,268 @@ +use crate::tests::utils::{proper_instantiate, send_tokens, EXECUTOR_1, EXECUTOR_2, USER}; +use common::msg::{ + GetCommittedDataResultResponse, GetResolvedDataResultResponse, GetRevealedDataResultResponse, + PostDataRequestArgs, +}; +use common::state::Reveal; +use common::types::{Bytes, Hash, Memo}; +use cosmwasm_std::Addr; +use cw_multi_test::Executor; +use data_requests::state::DataRequestInputs; +use data_requests::utils::{hash_data_request, hash_update}; +use proxy_contract::msg::{ProxyExecuteMsg, ProxyQueryMsg}; +use sha3::{Digest, Keccak256}; +use staking::consts::MINIMUM_STAKE_TO_REGISTER; + +#[test] +fn commit_reveal_result() { + let (mut app, proxy_contract) = proper_instantiate(); + + // send tokens from USER to executor1 and executor2 so they can register + send_tokens(&mut app, USER, EXECUTOR_1, 1); + send_tokens(&mut app, USER, EXECUTOR_2, 1); + let msg = ProxyExecuteMsg::RegisterDataRequestExecutor { + p2p_multi_address: Some("address".to_string()), + }; + let cosmos_msg = proxy_contract + .call_with_deposit(msg, MINIMUM_STAKE_TO_REGISTER) + .unwrap(); + app.execute(Addr::unchecked(EXECUTOR_1), cosmos_msg.clone()) + .unwrap(); + app.execute(Addr::unchecked(EXECUTOR_2), cosmos_msg) + .unwrap(); + + // can't commit on a data request if it doesn't exist + let msg = ProxyExecuteMsg::CommitDataResult { + dr_id: "nonexistent".to_string(), + commitment: "result".to_string(), + }; + let cosmos_msg = proxy_contract.call(msg).unwrap(); + let res = app.execute(Addr::unchecked(EXECUTOR_1), cosmos_msg.clone()); + assert!(res.is_err()); + + // format inputs to post data request with replication factor of 2 + let dr_binary_id: Hash = "".to_string(); + let tally_binary_id: Hash = "".to_string(); + let dr_inputs: Bytes = Vec::new(); + let tally_inputs: Bytes = Vec::new(); + let replication_factor: u16 = 2; + let gas_price: u128 = 10; + let gas_limit: u128 = 10; + let seda_payload: Bytes = Vec::new(); + let chain_id = 31337; + let nonce = 1; + let value = "test".to_string(); + let mut hasher = Keccak256::new(); + hash_update(&mut hasher, &chain_id); + hash_update(&mut hasher, &nonce); + hasher.update(value); + let binary_hash = format!("0x{}", hex::encode(hasher.finalize())); + let memo1: Memo = binary_hash.clone().into_bytes(); + let payback_address: Bytes = Vec::new(); + let dr_inputs1 = DataRequestInputs { + dr_binary_id: dr_binary_id.clone(), + tally_binary_id: tally_binary_id.clone(), + dr_inputs: dr_inputs.clone(), + tally_inputs: tally_inputs.clone(), + memo: memo1.clone(), + replication_factor, + gas_price, + gas_limit, + seda_payload: seda_payload.clone(), + payback_address: payback_address.clone(), + }; + let constructed_dr_id: String = hash_data_request(dr_inputs1); + let payback_address: Bytes = Vec::new(); + let posted_dr: PostDataRequestArgs = PostDataRequestArgs { + dr_id: constructed_dr_id, + dr_binary_id, + tally_binary_id, + dr_inputs, + tally_inputs, + memo: memo1, + replication_factor, + gas_price, + gas_limit, + seda_payload, + payback_address, + }; + + // post the data request + let msg = ProxyExecuteMsg::PostDataRequest { posted_dr }; + let cosmos_msg = proxy_contract.call(msg).unwrap(); + let res = app + .execute(Addr::unchecked(USER), cosmos_msg.clone()) + .unwrap(); + + // get dr_id + // TODO: this is ugly to loop through events, use Response.data once it's merged + let dr_id = &res.events.last().unwrap().attributes.last().unwrap().value; + + // executor1 commits on the data request + let reveal = "2000"; + let salt = EXECUTOR_1; + let mut hasher = Keccak256::new(); + hasher.update(reveal.as_bytes()); + hasher.update(salt.as_bytes()); + let digest = hasher.finalize(); + let commitment1 = format!("0x{}", hex::encode(digest)); + let msg = ProxyExecuteMsg::CommitDataResult { + dr_id: dr_id.to_string(), + commitment: commitment1, + }; + let cosmos_msg = proxy_contract.call(msg).unwrap(); + app.execute(Addr::unchecked(EXECUTOR_1), cosmos_msg) + .unwrap(); + + // executor2 commits on the data request + let reveal = "3000"; + let salt = EXECUTOR_2; + let mut hasher = Keccak256::new(); + hasher.update(reveal.as_bytes()); + hasher.update(salt.as_bytes()); + let digest = hasher.finalize(); + let commitment2 = format!("0x{}", hex::encode(digest)); + let msg = ProxyExecuteMsg::CommitDataResult { + dr_id: dr_id.to_string(), + commitment: commitment2, + }; + let cosmos_msg = proxy_contract.call(msg).unwrap(); + app.execute(Addr::unchecked(EXECUTOR_2), cosmos_msg) + .unwrap(); + + // should be able to fetch committed data result + let msg = ProxyQueryMsg::GetCommittedDataResult { + dr_id: dr_id.to_string(), + executor: Addr::unchecked(EXECUTOR_1), + }; + let res: GetCommittedDataResultResponse = app + .wrap() + .query_wasm_smart(proxy_contract.addr(), &msg) + .unwrap(); + assert!(res.value.is_some()); + println!("res: {:?}", res); + + // executor1 reveals data result + let reveal1 = Reveal { + reveal: "2000".to_string(), + salt: "executor1".to_string(), + }; + let msg = ProxyExecuteMsg::RevealDataResult { + dr_id: dr_id.to_string(), + reveal: reveal1, + }; + let cosmos_msg = proxy_contract.call(msg).unwrap(); + app.execute(Addr::unchecked(EXECUTOR_1), cosmos_msg) + .unwrap(); + + // should be able to fetch revealed data result + let msg = ProxyQueryMsg::GetRevealedDataResult { + dr_id: dr_id.to_string(), + executor: Addr::unchecked(EXECUTOR_1), + }; + let res: GetRevealedDataResultResponse = app + .wrap() + .query_wasm_smart(proxy_contract.addr(), &msg) + .unwrap(); + assert!(res.value.is_some()); + + // executor 2 reveals data result + let reveal2 = Reveal { + reveal: "3000".to_string(), + salt: "executor2".to_string(), + }; + let msg = ProxyExecuteMsg::RevealDataResult { + dr_id: dr_id.to_string(), + reveal: reveal2, + }; + let cosmos_msg = proxy_contract.call(msg).unwrap(); + app.execute(Addr::unchecked(EXECUTOR_2), cosmos_msg) + .unwrap(); + + // now data request is resolved, let's check + let msg = ProxyQueryMsg::GetResolvedDataResult { + dr_id: dr_id.to_string(), + }; + let res: GetResolvedDataResultResponse = app + .wrap() + .query_wasm_smart(proxy_contract.addr(), &msg) + .unwrap(); + assert_eq!(res.value.dr_id, dr_id.to_string()); +} + +#[test] +fn ineligible_post_data_result() { + let (mut app, proxy_contract) = proper_instantiate(); + + // post a data request + let dr_binary_id: Hash = "".to_string(); + let tally_binary_id: Hash = "".to_string(); + let dr_inputs: Bytes = Vec::new(); + let tally_inputs: Bytes = Vec::new(); + let replication_factor: u16 = 2; + let gas_price: u128 = 10; + let gas_limit: u128 = 10; + let seda_payload: Bytes = Vec::new(); + let chain_id = 31337; + let nonce = 1; + let value = "test".to_string(); + let mut hasher = Keccak256::new(); + hash_update(&mut hasher, &chain_id); + hash_update(&mut hasher, &nonce); + hasher.update(value); + let binary_hash = format!("0x{}", hex::encode(hasher.finalize())); + let memo1: Memo = binary_hash.clone().into_bytes(); + let payback_address: Bytes = Vec::new(); + let dr_inputs1 = DataRequestInputs { + dr_binary_id: dr_binary_id.clone(), + tally_binary_id: tally_binary_id.clone(), + dr_inputs: dr_inputs.clone(), + tally_inputs: tally_inputs.clone(), + memo: memo1.clone(), + replication_factor, + gas_price, + gas_limit, + seda_payload: seda_payload.clone(), + payback_address: payback_address.clone(), + }; + let constructed_dr_id: String = hash_data_request(dr_inputs1); + let payback_address: Bytes = Vec::new(); + let posted_dr: PostDataRequestArgs = PostDataRequestArgs { + dr_id: constructed_dr_id, + dr_binary_id, + tally_binary_id, + dr_inputs, + tally_inputs, + memo: memo1, + replication_factor, + gas_price, + gas_limit, + seda_payload, + payback_address, + }; + let msg = ProxyExecuteMsg::PostDataRequest { posted_dr }; + let cosmos_msg = proxy_contract.call(msg).unwrap(); + let res = app + .execute(Addr::unchecked(USER), cosmos_msg.clone()) + .unwrap(); + + // get dr_id + // TODO: this is ugly to loop through events, use Response.data once it's merged + let dr_id = &res.events.last().unwrap().attributes.last().unwrap().value; + + // ineligible shouldn't be able to post a data result + let reveal = "2000"; + let salt = EXECUTOR_1; + let mut hasher = Keccak256::new(); + hasher.update(reveal.as_bytes()); + hasher.update(salt.as_bytes()); + let digest = hasher.finalize(); + let commitment1 = format!("0x{}", hex::encode(digest)); + let msg = ProxyExecuteMsg::CommitDataResult { + dr_id: dr_id.to_string(), + commitment: commitment1, + }; + let cosmos_msg = proxy_contract.call(msg).unwrap(); + let res = app.execute(Addr::unchecked(EXECUTOR_1), cosmos_msg); + assert!(res.is_err()); +} diff --git a/packages/integration-tests/src/helpers.rs b/packages/integration-tests/src/helpers.rs deleted file mode 100644 index 5cb483c1..00000000 --- a/packages/integration-tests/src/helpers.rs +++ /dev/null @@ -1,25 +0,0 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use cosmwasm_std::{to_binary, Addr, CosmosMsg, StdResult, WasmMsg}; - -/// CwTemplateContract is a wrapper around Addr that provides a lot of helpers -/// for working with this. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct CwTemplateContract(pub Addr); - -impl CwTemplateContract { - pub fn addr(&self) -> Addr { - self.0.clone() - } - - pub fn call>(&self, msg: T) -> StdResult { - let msg = to_binary(&msg.into())?; - Ok(WasmMsg::Execute { - contract_addr: self.addr().into(), - msg, - funds: vec![], - } - .into()) - } -} diff --git a/packages/integration-tests/src/lib.rs b/packages/integration-tests/src/lib.rs index 955a2298..dbbe0725 100644 --- a/packages/integration-tests/src/lib.rs +++ b/packages/integration-tests/src/lib.rs @@ -1,6 +1,7 @@ #[path = ""] #[cfg(test)] mod tests { - mod test; + mod data_request; + mod data_result; + pub mod utils; } -pub mod helpers; diff --git a/packages/integration-tests/src/test.rs b/packages/integration-tests/src/test.rs deleted file mode 100644 index 6a3d1848..00000000 --- a/packages/integration-tests/src/test.rs +++ /dev/null @@ -1,174 +0,0 @@ -use crate::helpers::CwTemplateContract; -use common::msg::{GetDataRequestResponse, PostDataRequestArgs, QueryMsg}; -use common::types::{Bytes, Hash, Memo}; -use cosmwasm_std::{Addr, Coin, Empty, Uint128}; -use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper, Executor}; -use proxy_contract::msg::ExecuteMsg; -use seda_chain_contracts::state::DataRequestInputs; -use seda_chain_contracts::utils::{hash_data_request, hash_update}; -use sha3::{Digest, Keccak256}; - -pub fn proxy_contract_template() -> Box> { - let contract = ContractWrapper::new( - proxy_contract::contract::execute, - proxy_contract::contract::instantiate, - proxy_contract::contract::query, - ); - Box::new(contract) -} - -pub fn seda_chain_contracts_template() -> Box> { - let contract = ContractWrapper::new( - seda_chain_contracts::contract::execute, - seda_chain_contracts::contract::instantiate, - seda_chain_contracts::contract::query, - ); - Box::new(contract) -} - -#[allow(dead_code)] -const USER: &str = "USER"; -const ADMIN: &str = "ADMIN"; -const NATIVE_DENOM: &str = "denom"; - -fn mock_app() -> App { - AppBuilder::new().build(|router, _, storage| { - router - .bank - .init_balance( - storage, - &Addr::unchecked(USER), - vec![Coin { - denom: NATIVE_DENOM.to_string(), - amount: Uint128::new(1), - }], - ) - .unwrap(); - }) -} - -fn proper_instantiate() -> (App, CwTemplateContract) { - let mut app = mock_app(); - - // instantiate proxy-contract - let proxy_contract_template_id = app.store_code(proxy_contract_template()); - let msg = proxy_contract::msg::InstantiateMsg { - token: "token".to_string(), - }; - let proxy_contract_template_contract_addr = app - .instantiate_contract( - proxy_contract_template_id, - Addr::unchecked(ADMIN), - &msg, - &[], - "test", - None, - ) - .unwrap(); - let proxy_contract_template_contract = - CwTemplateContract(proxy_contract_template_contract_addr.clone()); - - // instantiate seda-chain-contracts - let seda_chain_contracts_template_id = app.store_code(seda_chain_contracts_template()); - let msg = seda_chain_contracts::msg::InstantiateMsg { - token: "token".to_string(), - proxy: proxy_contract_template_contract_addr.to_string(), - }; - let seda_chain_contracts_template_contract_addr = app - .instantiate_contract( - seda_chain_contracts_template_id, - Addr::unchecked(ADMIN), - &msg, - &[], - "test", - None, - ) - .unwrap(); - - // set seda-chain-contract address on proxy-contract - let msg = proxy_contract::msg::ExecuteMsg::SetSedaChainContracts { - contract: seda_chain_contracts_template_contract_addr.to_string(), - }; - let cosmos_msg = proxy_contract_template_contract.call(msg).unwrap(); - app.execute(Addr::unchecked(USER), cosmos_msg).unwrap(); - - (app, proxy_contract_template_contract) -} - -#[test] -fn post_data_request() { - let (mut app, proxy_contract_template_contract) = proper_instantiate(); - let dr_binary_id: Hash = "".to_string(); - let tally_binary_id: Hash = "".to_string(); - let dr_inputs: Bytes = Vec::new(); - let tally_inputs: Bytes = Vec::new(); - - let replication_factor: u16 = 3; - - // set by dr creator - let gas_price: u128 = 10; - let gas_limit: u128 = 10; - - // set by relayer and SEDA protocol - let seda_payload: Bytes = Vec::new(); - - let chain_id = 31337; - let nonce = 1; - let value = "test".to_string(); - let mut hasher = Keccak256::new(); - hash_update(&mut hasher, &chain_id); - hash_update(&mut hasher, &nonce); - hasher.update(value); - let binary_hash = format!("0x{}", hex::encode(hasher.finalize())); - let memo1: Memo = binary_hash.clone().into_bytes(); - let payback_address: Bytes = Vec::new(); - - let dr_inputs1 = DataRequestInputs { - dr_binary_id: dr_binary_id.clone(), - tally_binary_id: tally_binary_id.clone(), - dr_inputs: dr_inputs.clone(), - tally_inputs: tally_inputs.clone(), - memo: memo1.clone(), - replication_factor, - - gas_price, - gas_limit, - - seda_payload: seda_payload.clone(), - payback_address: payback_address.clone(), - }; - let constructed_dr_id: String = hash_data_request(dr_inputs1); - - let payback_address: Bytes = Vec::new(); - let posted_dr: PostDataRequestArgs = PostDataRequestArgs { - dr_id: constructed_dr_id, - - dr_binary_id, - tally_binary_id, - dr_inputs, - tally_inputs, - memo: memo1, - replication_factor, - - gas_price, - gas_limit, - - seda_payload, - payback_address, - }; - let msg = ExecuteMsg::PostDataRequest { posted_dr }; - let cosmos_msg = proxy_contract_template_contract.call(msg).unwrap(); - let res = app.execute(Addr::unchecked(USER), cosmos_msg).unwrap(); - - // get data request - // TODO: this is ugly to loop through events, use Response.data once it's merged - let dr_id = &res.events.last().unwrap().attributes.last().unwrap().value; - let msg = QueryMsg::GetDataRequest { - dr_id: dr_id.clone(), - }; - let res: GetDataRequestResponse = app - .wrap() - .query_wasm_smart(proxy_contract_template_contract.addr(), &msg) - .unwrap(); - println!("{:?}", res); -} diff --git a/packages/integration-tests/src/utils.rs b/packages/integration-tests/src/utils.rs new file mode 100644 index 00000000..4907b01e --- /dev/null +++ b/packages/integration-tests/src/utils.rs @@ -0,0 +1,176 @@ +use cosmwasm_std::{to_binary, Addr, BankMsg, Coin, CosmosMsg, Empty, StdResult, Uint128, WasmMsg}; +use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper, Executor}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +pub const USER: &str = "user"; +pub const EXECUTOR_1: &str = "executor1"; +pub const EXECUTOR_2: &str = "executor2"; +const ADMIN: &str = "admin"; +const NATIVE_DENOM: &str = "seda"; + +/// CwTemplateContract is a wrapper around Addr that provides a lot of helpers +/// for working with this. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct CwTemplateContract(pub Addr); + +impl CwTemplateContract { + pub fn addr(&self) -> Addr { + self.0.clone() + } + + pub fn call>( + &self, + msg: T, + ) -> StdResult { + let msg = to_binary(&msg.into())?; + Ok(WasmMsg::Execute { + contract_addr: self.addr().into(), + msg, + funds: vec![], + } + .into()) + } + + pub fn call_with_deposit>( + &self, + msg: T, + amount: u128, + ) -> StdResult { + let coin = Coin { + denom: NATIVE_DENOM.to_string(), + amount: amount.into(), + }; + let msg = to_binary(&msg.into())?; + Ok(WasmMsg::Execute { + contract_addr: self.addr().into(), + msg, + funds: vec![coin], + } + .into()) + } +} + +pub fn proxy_contract_template() -> Box> { + let contract = ContractWrapper::new( + proxy_contract::contract::execute, + proxy_contract::contract::instantiate, + proxy_contract::contract::query, + ); + Box::new(contract) +} + +pub fn data_requests_template() -> Box> { + let contract = ContractWrapper::new( + data_requests::contract::execute, + data_requests::contract::instantiate, + data_requests::contract::query, + ); + Box::new(contract) +} + +pub fn staking_template() -> Box> { + let contract = ContractWrapper::new( + staking::contract::execute, + staking::contract::instantiate, + staking::contract::query, + ); + Box::new(contract) +} + +fn mock_app() -> App { + AppBuilder::new().build(|router, _, storage| { + router + .bank + .init_balance( + storage, + &Addr::unchecked(USER), + vec![Coin { + denom: NATIVE_DENOM.to_string(), + amount: Uint128::new(100), + }], + ) + .unwrap(); + }) +} + +pub fn send_tokens(app: &mut App, from: &str, to: &str, amount: u128) { + let coin = Coin { + denom: NATIVE_DENOM.to_string(), + amount: amount.into(), + }; + let cosmos_msg = CosmosMsg::Bank(BankMsg::Send { + to_address: to.to_string(), + amount: vec![coin], + }); + app.execute(Addr::unchecked(from), cosmos_msg).unwrap(); +} + +pub fn proper_instantiate() -> (App, CwTemplateContract) { + let mut app = mock_app(); + + // instantiate proxy-contract + let proxy_contract_template_id = app.store_code(proxy_contract_template()); + let msg = proxy_contract::msg::InstantiateMsg { + token: NATIVE_DENOM.to_string(), + }; + let proxy_contract_addr = app + .instantiate_contract( + proxy_contract_template_id, + Addr::unchecked(ADMIN), + &msg, + &[], + "test", + None, + ) + .unwrap(); + let proxy_template_contract = CwTemplateContract(proxy_contract_addr.clone()); + + // instantiate staking + let staking_template_id = app.store_code(staking_template()); + let msg = staking::msg::InstantiateMsg { + token: NATIVE_DENOM.to_string(), + proxy: proxy_contract_addr.to_string(), + }; + let staking_contract_addr = app + .instantiate_contract( + staking_template_id, + Addr::unchecked(ADMIN), + &msg, + &[], + "test", + None, + ) + .unwrap(); + + // instantiate data-requests + let data_requests_template_id = app.store_code(data_requests_template()); + let msg = data_requests::msg::InstantiateMsg { + token: NATIVE_DENOM.to_string(), + proxy: proxy_contract_addr.to_string(), + }; + let data_requests_contract_addr = app + .instantiate_contract( + data_requests_template_id, + Addr::unchecked(ADMIN), + &msg, + &[], + "test", + None, + ) + .unwrap(); + + // set contract addresses on proxy-contract + let msg = proxy_contract::msg::ProxyExecuteMsg::SetStaking { + contract: staking_contract_addr.to_string(), + }; + let cosmos_msg = proxy_template_contract.call(msg).unwrap(); + app.execute(Addr::unchecked(USER), cosmos_msg).unwrap(); + let msg = proxy_contract::msg::ProxyExecuteMsg::SetDataRequests { + contract: data_requests_contract_addr.to_string(), + }; + let cosmos_msg = proxy_template_contract.call(msg).unwrap(); + app.execute(Addr::unchecked(USER), cosmos_msg).unwrap(); + + (app, proxy_template_contract) +} diff --git a/packages/proxy/src/bin/schema.rs b/packages/proxy/src/bin/schema.rs new file mode 100644 index 00000000..3ae96d7e --- /dev/null +++ b/packages/proxy/src/bin/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; + +use proxy_contract::msg::{InstantiateMsg, ProxyExecuteMsg, ProxyQueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ProxyExecuteMsg, + query: ProxyQueryMsg, + } +} diff --git a/packages/proxy/src/contract.rs b/packages/proxy/src/contract.rs index 52f0c53f..cb2c1511 100644 --- a/packages/proxy/src/contract.rs +++ b/packages/proxy/src/contract.rs @@ -3,12 +3,11 @@ use cosmwasm_std::entry_point; use crate::state::TOKEN; use crate::utils::get_attached_funds; -use common::msg::ExecuteMsg as SedaChainContractsExecuteMsg; use common::msg::{ - GetCommittedDataResultResponse, GetCommittedDataResultsResponse, + DataRequestsExecuteMsg, GetCommittedDataResultResponse, GetCommittedDataResultsResponse, GetDataRequestExecutorResponse, GetDataRequestResponse, GetDataRequestsFromPoolResponse, GetResolvedDataResultResponse, GetRevealedDataResultResponse, GetRevealedDataResultsResponse, - QueryMsg, + IsDataRequestExecutorEligibleResponse, StakingExecuteMsg, }; use cosmwasm_std::{ to_binary, Binary, Coin, CosmosMsg, Deps, DepsMut, Env, MessageInfo, QueryRequest, Response, @@ -18,8 +17,8 @@ use cw2::set_contract_version; use crate::{ error::ContractError, - msg::{ExecuteMsg, InstantiateMsg}, - state::SEDA_CHAIN_CONTRACTS, + msg::{InstantiateMsg, ProxyExecuteMsg, ProxyQueryMsg}, + state::{DATA_REQUESTS, STAKING}, }; // version info @@ -43,33 +42,45 @@ pub fn execute( deps: DepsMut, _env: Env, info: MessageInfo, - msg: ExecuteMsg, + msg: ProxyExecuteMsg, ) -> Result { match msg { // Admin - ExecuteMsg::SetSedaChainContracts { contract } => { + ProxyExecuteMsg::SetDataRequests { contract } => { // TODO: this should be a sudo call // if already set, return error - if SEDA_CHAIN_CONTRACTS.may_load(deps.storage)?.is_some() { + if DATA_REQUESTS.may_load(deps.storage)?.is_some() { return Err(ContractError::ContractAlreadySet {}); } - SEDA_CHAIN_CONTRACTS.save(deps.storage, &deps.api.addr_validate(&contract)?)?; - Ok(Response::new().add_attribute("method", "set_seda_chain_contracts")) + DATA_REQUESTS.save(deps.storage, &deps.api.addr_validate(&contract)?)?; + Ok(Response::new().add_attribute("method", "set_data_requests")) + } + ProxyExecuteMsg::SetStaking { contract } => { + // TODO: this should be a sudo call + // if already set, return error + if STAKING.may_load(deps.storage)?.is_some() { + return Err(ContractError::ContractAlreadySet {}); + } + + STAKING.save(deps.storage, &deps.api.addr_validate(&contract)?)?; + Ok(Response::new().add_attribute("method", "set_staking")) } // Delegated calls to contracts - ExecuteMsg::PostDataRequest { posted_dr } => Ok(Response::new() + + // DataRequests + ProxyExecuteMsg::PostDataRequest { posted_dr } => Ok(Response::new() .add_message(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: SEDA_CHAIN_CONTRACTS.load(deps.storage)?.to_string(), - msg: to_binary(&SedaChainContractsExecuteMsg::PostDataRequest { posted_dr })?, + contract_addr: DATA_REQUESTS.load(deps.storage)?.to_string(), + msg: to_binary(&DataRequestsExecuteMsg::PostDataRequest { posted_dr })?, funds: vec![], })) .add_attribute("action", "post_data_request")), - ExecuteMsg::CommitDataResult { dr_id, commitment } => Ok(Response::new() + ProxyExecuteMsg::CommitDataResult { dr_id, commitment } => Ok(Response::new() .add_message(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: SEDA_CHAIN_CONTRACTS.load(deps.storage)?.to_string(), - msg: to_binary(&SedaChainContractsExecuteMsg::CommitDataResult { + contract_addr: DATA_REQUESTS.load(deps.storage)?.to_string(), + msg: to_binary(&DataRequestsExecuteMsg::CommitDataResult { dr_id, commitment, sender: Some(info.sender.to_string()), @@ -77,10 +88,10 @@ pub fn execute( funds: vec![], })) .add_attribute("action", "post_data_result")), - ExecuteMsg::RevealDataResult { dr_id, reveal } => Ok(Response::new() + ProxyExecuteMsg::RevealDataResult { dr_id, reveal } => Ok(Response::new() .add_message(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: SEDA_CHAIN_CONTRACTS.load(deps.storage)?.to_string(), - msg: to_binary(&SedaChainContractsExecuteMsg::RevealDataResult { + contract_addr: DATA_REQUESTS.load(deps.storage)?.to_string(), + msg: to_binary(&DataRequestsExecuteMsg::RevealDataResult { dr_id, reveal, sender: Some(info.sender.to_string()), @@ -88,15 +99,17 @@ pub fn execute( funds: vec![], })) .add_attribute("action", "post_data_result")), - ExecuteMsg::RegisterDataRequestExecutor { p2p_multi_address } => { + + // Staking + ProxyExecuteMsg::RegisterDataRequestExecutor { p2p_multi_address } => { // require token deposit let token = TOKEN.load(deps.storage)?; let amount = get_attached_funds(&info.funds, &token)?; Ok(Response::new() .add_message(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: SEDA_CHAIN_CONTRACTS.load(deps.storage)?.to_string(), - msg: to_binary(&SedaChainContractsExecuteMsg::RegisterDataRequestExecutor { + contract_addr: STAKING.load(deps.storage)?.to_string(), + msg: to_binary(&StakingExecuteMsg::RegisterDataRequestExecutor { p2p_multi_address, sender: Some(info.sender.to_string()), })?, @@ -107,26 +120,24 @@ pub fn execute( })) .add_attribute("action", "register_data_request_executor")) } - ExecuteMsg::UnregisterDataRequestExecutor {} => Ok(Response::new() + ProxyExecuteMsg::UnregisterDataRequestExecutor {} => Ok(Response::new() .add_message(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: SEDA_CHAIN_CONTRACTS.load(deps.storage)?.to_string(), - msg: to_binary( - &SedaChainContractsExecuteMsg::UnregisterDataRequestExecutor { - sender: Some(info.sender.to_string()), - }, - )?, + contract_addr: STAKING.load(deps.storage)?.to_string(), + msg: to_binary(&StakingExecuteMsg::UnregisterDataRequestExecutor { + sender: Some(info.sender.to_string()), + })?, funds: vec![], })) .add_attribute("action", "unregister_data_request_executor")), - ExecuteMsg::DepositAndStake {} => { + ProxyExecuteMsg::DepositAndStake {} => { // require token deposit let token = TOKEN.load(deps.storage)?; let amount = get_attached_funds(&info.funds, &token)?; Ok(Response::new() .add_message(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: SEDA_CHAIN_CONTRACTS.load(deps.storage)?.to_string(), - msg: to_binary(&SedaChainContractsExecuteMsg::DepositAndStake { + contract_addr: STAKING.load(deps.storage)?.to_string(), + msg: to_binary(&StakingExecuteMsg::DepositAndStake { sender: Some(info.sender.to_string()), })?, funds: vec![Coin { @@ -136,20 +147,20 @@ pub fn execute( })) .add_attribute("action", "deposit_and_stake")) } - ExecuteMsg::Unstake { amount } => Ok(Response::new() + ProxyExecuteMsg::Unstake { amount } => Ok(Response::new() .add_message(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: SEDA_CHAIN_CONTRACTS.load(deps.storage)?.to_string(), - msg: to_binary(&SedaChainContractsExecuteMsg::Unstake { + contract_addr: STAKING.load(deps.storage)?.to_string(), + msg: to_binary(&StakingExecuteMsg::Unstake { amount, sender: Some(info.sender.to_string()), })?, funds: vec![], })) .add_attribute("action", "unstake")), - ExecuteMsg::Withdraw { amount } => Ok(Response::new() + ProxyExecuteMsg::Withdraw { amount } => Ok(Response::new() .add_message(CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: SEDA_CHAIN_CONTRACTS.load(deps.storage)?.to_string(), - msg: to_binary(&SedaChainContractsExecuteMsg::Withdraw { + contract_addr: STAKING.load(deps.storage)?.to_string(), + msg: to_binary(&StakingExecuteMsg::Withdraw { amount, sender: Some(info.sender.to_string()), })?, @@ -160,79 +171,92 @@ pub fn execute( } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> cosmwasm_std::StdResult { +pub fn query(deps: Deps, _env: Env, msg: ProxyQueryMsg) -> cosmwasm_std::StdResult { match msg.clone() { - QueryMsg::GetDataRequest { dr_id: _dr_id } => { + // DataRequests + ProxyQueryMsg::GetDataRequest { dr_id: _dr_id } => { let query_response: GetDataRequestResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: SEDA_CHAIN_CONTRACTS.load(deps.storage)?.to_string(), + contract_addr: DATA_REQUESTS.load(deps.storage)?.to_string(), msg: to_binary(&msg)?, }))?; Ok(to_binary(&query_response)?) } - QueryMsg::GetDataRequestsFromPool { + ProxyQueryMsg::GetDataRequestsFromPool { position: _position, limit: _limit, } => { let query_response: GetDataRequestsFromPoolResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: SEDA_CHAIN_CONTRACTS.load(deps.storage)?.to_string(), + contract_addr: DATA_REQUESTS.load(deps.storage)?.to_string(), msg: to_binary(&msg)?, }))?; Ok(to_binary(&query_response)?) } - QueryMsg::GetCommittedDataResult { + ProxyQueryMsg::GetCommittedDataResult { dr_id: _dr_id, executor: _executor, } => { let query_response: GetCommittedDataResultResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: SEDA_CHAIN_CONTRACTS.load(deps.storage)?.to_string(), + contract_addr: DATA_REQUESTS.load(deps.storage)?.to_string(), msg: to_binary(&msg)?, }))?; Ok(to_binary(&query_response)?) } - QueryMsg::GetCommittedDataResults { dr_id: _dr_id } => { + ProxyQueryMsg::GetCommittedDataResults { dr_id: _dr_id } => { let query_response: GetCommittedDataResultsResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: SEDA_CHAIN_CONTRACTS.load(deps.storage)?.to_string(), + contract_addr: DATA_REQUESTS.load(deps.storage)?.to_string(), msg: to_binary(&msg)?, }))?; Ok(to_binary(&query_response)?) } - QueryMsg::GetRevealedDataResult { + ProxyQueryMsg::GetRevealedDataResult { dr_id: _dr_id, executor: _executor, } => { let query_response: GetRevealedDataResultResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: SEDA_CHAIN_CONTRACTS.load(deps.storage)?.to_string(), + contract_addr: DATA_REQUESTS.load(deps.storage)?.to_string(), msg: to_binary(&msg)?, }))?; Ok(to_binary(&query_response)?) } - QueryMsg::GetRevealedDataResults { dr_id: _dr_id } => { + ProxyQueryMsg::GetRevealedDataResults { dr_id: _dr_id } => { let query_response: GetRevealedDataResultsResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: SEDA_CHAIN_CONTRACTS.load(deps.storage)?.to_string(), + contract_addr: DATA_REQUESTS.load(deps.storage)?.to_string(), msg: to_binary(&msg)?, }))?; Ok(to_binary(&query_response)?) } - QueryMsg::GetResolvedDataResult { dr_id: _dr_id } => { + ProxyQueryMsg::GetResolvedDataResult { dr_id: _dr_id } => { let query_response: GetResolvedDataResultResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: SEDA_CHAIN_CONTRACTS.load(deps.storage)?.to_string(), + contract_addr: DATA_REQUESTS.load(deps.storage)?.to_string(), msg: to_binary(&msg)?, }))?; Ok(to_binary(&query_response)?) } - QueryMsg::GetDataRequestExecutor { + + // Staking + ProxyQueryMsg::GetDataRequestExecutor { executor: _executor, } => { let query_response: GetDataRequestExecutorResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: SEDA_CHAIN_CONTRACTS.load(deps.storage)?.to_string(), + contract_addr: STAKING.load(deps.storage)?.to_string(), + msg: to_binary(&msg)?, + }))?; + Ok(to_binary(&query_response)?) + } + ProxyQueryMsg::IsDataRequestExecutorEligible { + executor: _executor, + } => { + let query_response: IsDataRequestExecutorEligibleResponse = + deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: STAKING.load(deps.storage)?.to_string(), msg: to_binary(&msg)?, }))?; Ok(to_binary(&query_response)?) diff --git a/packages/proxy/src/msg.rs b/packages/proxy/src/msg.rs index ea1e4272..19d92a53 100644 --- a/packages/proxy/src/msg.rs +++ b/packages/proxy/src/msg.rs @@ -1,7 +1,14 @@ -use common::msg::PostDataRequestArgs; +#[allow(unused_imports)] +use common::msg::{ + GetCommittedDataResultResponse, GetCommittedDataResultsResponse, + GetDataRequestExecutorResponse, GetDataRequestResponse, GetDataRequestsFromPoolResponse, + GetResolvedDataResultResponse, GetRevealedDataResultResponse, GetRevealedDataResultsResponse, + IsDataRequestExecutorEligibleResponse, PostDataRequestArgs, +}; use common::state::Reveal; use common::types::Hash; -use cosmwasm_schema::cw_serde; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Addr; #[cw_serde] pub struct InstantiateMsg { @@ -9,17 +16,50 @@ pub struct InstantiateMsg { } #[cw_serde] -pub enum ExecuteMsg { +pub enum ProxyExecuteMsg { // Admin - SetSedaChainContracts { contract: String }, + SetDataRequests { contract: String }, + SetStaking { contract: String }, // Delegated calls to contracts + + // DataRequests PostDataRequest { posted_dr: PostDataRequestArgs }, CommitDataResult { dr_id: Hash, commitment: String }, RevealDataResult { dr_id: Hash, reveal: Reveal }, + // Staking RegisterDataRequestExecutor { p2p_multi_address: Option }, UnregisterDataRequestExecutor {}, DepositAndStake, Unstake { amount: u128 }, Withdraw { amount: u128 }, } + +#[cw_serde] +#[derive(QueryResponses)] +pub enum ProxyQueryMsg { + // DataRequests + #[returns(GetDataRequestResponse)] + GetDataRequest { dr_id: Hash }, + #[returns(GetDataRequestsFromPoolResponse)] + GetDataRequestsFromPool { + position: Option, + limit: Option, + }, + #[returns(GetCommittedDataResultResponse)] + GetCommittedDataResult { dr_id: Hash, executor: Addr }, + #[returns(GetCommittedDataResultsResponse)] + GetCommittedDataResults { dr_id: Hash }, + #[returns(GetRevealedDataResultResponse)] + GetRevealedDataResult { dr_id: Hash, executor: Addr }, + #[returns(GetRevealedDataResultsResponse)] + GetRevealedDataResults { dr_id: Hash }, + #[returns(GetResolvedDataResultResponse)] + GetResolvedDataResult { dr_id: Hash }, + + // Staking + #[returns(GetDataRequestExecutorResponse)] + GetDataRequestExecutor { executor: Addr }, + #[returns(IsDataRequestExecutorEligibleResponse)] + IsDataRequestExecutorEligible { executor: Addr }, +} diff --git a/packages/proxy/src/state.rs b/packages/proxy/src/state.rs index 6fe856c0..3008d0d4 100644 --- a/packages/proxy/src/state.rs +++ b/packages/proxy/src/state.rs @@ -4,5 +4,8 @@ use cw_storage_plus::Item; /// Token denomination used for data request executor staking and deposit for posting a data request pub const TOKEN: Item = Item::new("token"); -/// Contract address of `seda-chain-contracts` -pub const SEDA_CHAIN_CONTRACTS: Item = Item::new("seda_chain_contracts"); +/// Contract address of `data-requests` contract +pub const DATA_REQUESTS: Item = Item::new("data_requests"); + +/// Contract address of `staking` contract +pub const STAKING: Item = Item::new("staking"); diff --git a/packages/seda-chain-contracts/src/bin/schema.rs b/packages/seda-chain-contracts/src/bin/schema.rs deleted file mode 100644 index ece7f370..00000000 --- a/packages/seda-chain-contracts/src/bin/schema.rs +++ /dev/null @@ -1,12 +0,0 @@ -use cosmwasm_schema::write_api; - -use common::msg::{ExecuteMsg, QueryMsg}; -use seda_chain_contracts::msg::InstantiateMsg; - -fn main() { - write_api! { - instantiate: InstantiateMsg, - execute: ExecuteMsg, - query: QueryMsg, - } -} diff --git a/packages/seda-chain-contracts/src/data_request_result.rs b/packages/seda-chain-contracts/src/data_request_result.rs deleted file mode 100644 index c434ff8c..00000000 --- a/packages/seda-chain-contracts/src/data_request_result.rs +++ /dev/null @@ -1,567 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::{Deps, DepsMut, MessageInfo, Response, StdResult}; - -use crate::state::DATA_REQUESTS; -use common::msg::{GetCommittedDataResultResponse, GetRevealedDataResultResponse}; -use common::types::Hash; - -use crate::error::ContractError; - -pub mod data_request_results { - - use cosmwasm_std::{Addr, Env}; - use sha3::{Digest, Keccak256}; - - use common::msg::{ - GetCommittedDataResultsResponse, GetCommittedExecutorsResponse, GetIdsResponse, - GetResolvedDataResultResponse, GetRevealedDataResultsResponse, - }; - use common::state::{DataResult, Reveal}; - use common::types::Bytes; - - use crate::{ - state::DATA_RESULTS, - utils::{check_eligibility, hash_data_result, validate_sender}, - ContractError::{ - AlreadyCommitted, AlreadyRevealed, IneligibleExecutor, NotCommitted, RevealMismatch, - RevealNotStarted, - }, - }; - - use super::*; - - /// Posts a data result of a data request with an attached hash of the answer and salt. - /// This removes the data request from the pool and creates a new entry in the data results. - pub fn commit_result( - deps: DepsMut, - info: MessageInfo, - dr_id: Hash, - commitment: Hash, - sender: Option, - ) -> Result { - let sender = validate_sender(&deps, info.sender, sender)?; - - if !check_eligibility(&deps, sender.clone())? { - return Err(IneligibleExecutor); - } - - // find the data request from the pool (if it exists, otherwise error) - let mut dr = DATA_REQUESTS.load(deps.storage, dr_id.clone())?; - if dr.commits.contains_key(&sender.to_string()) { - return Err(AlreadyCommitted); - } - dr.commits.insert(sender.to_string(), commitment.clone()); - - DATA_REQUESTS.save(deps.storage, dr_id.clone(), &dr)?; - - Ok(Response::new() - .add_attribute("action", "commit_result") - .add_attribute("dr_id", dr_id) - .add_attribute("result", commitment)) - } - - /// Posts a data result of a data request with an attached result. - /// This removes the data request from the pool and creates a new entry in the data results. - pub fn reveal_result( - deps: DepsMut, - info: MessageInfo, - env: Env, - dr_id: Hash, - reveal: Reveal, - sender: Option, - ) -> Result { - let sender = validate_sender(&deps, info.sender, sender)?; - if !check_eligibility(&deps, sender.clone())? { - return Err(IneligibleExecutor); - } - - // find the data request from the committed pool (if it exists, otherwise error) - let mut dr = DATA_REQUESTS.load(deps.storage, dr_id.clone())?; - let committed_dr_results = dr.clone().commits; - - if u16::try_from(committed_dr_results.len()).unwrap() < dr.replication_factor { - return Err(RevealNotStarted); - } - if !committed_dr_results.contains_key(&sender.to_string()) { - return Err(NotCommitted); - } - if dr.reveals.contains_key(&sender.to_string()) { - return Err(AlreadyRevealed); - } - - let committed_dr_result = committed_dr_results - .get(&sender.to_string()) - .unwrap() - .clone(); - - let calculated_dr_result = compute_hash(&reveal.reveal, &reveal.salt); - if calculated_dr_result != committed_dr_result { - return Err(RevealMismatch); - } - - dr.reveals.insert(sender.to_string(), reveal.clone()); - - DATA_REQUESTS.save(deps.storage, dr_id.clone(), &dr)?; - - if u16::try_from(dr.reveals.len()).unwrap() == dr.replication_factor { - let block_height: u64 = env.block.height; - let exit_code: u8 = 0; - let result: Bytes = reveal.reveal.as_bytes().to_vec(); - - let payback_address: Bytes = dr.payback_address.clone(); - let seda_payload: Bytes = dr.seda_payload.clone(); - - let result_id = hash_data_result(&dr, block_height, exit_code, &result); - - let dr_result = DataResult { - result_id, - dr_id: dr_id.clone(), - block_height, - exit_code, - result, - payback_address, - seda_payload, - }; - DATA_RESULTS.save(deps.storage, dr_id.clone(), &dr_result)?; - DATA_REQUESTS.remove(deps.storage, dr_id.clone()); - } - - Ok(Response::new() - .add_attribute("action", "reveal_result") - .add_attribute("dr_id", dr_id) - .add_attribute("reveal", reveal.reveal)) - } - - /// Returns a data result from the results with the given id, if it exists. - pub fn get_committed_data_result( - deps: Deps, - dr_id: Hash, - executor: Addr, - ) -> StdResult { - let dr = DATA_REQUESTS.load(deps.storage, dr_id)?; - let commitment = dr.commits.get(&executor.to_string()); - Ok(GetCommittedDataResultResponse { - value: commitment.cloned(), - }) - } - - /// Returns a data result from the results with the given id, if it exists. - pub fn get_committed_data_results( - deps: Deps, - dr_id: Hash, - ) -> StdResult { - let dr = DATA_REQUESTS.load(deps.storage, dr_id)?; - Ok(GetCommittedDataResultsResponse { value: dr.commits }) - } - - /// Returns a data result from the results with the given id, if it exists. - pub fn get_revealed_data_result( - deps: Deps, - dr_id: Hash, - executor: Addr, - ) -> StdResult { - let dr = DATA_REQUESTS.load(deps.storage, dr_id)?; - let reveal = dr.reveals.get(&executor.to_string()); - Ok(GetRevealedDataResultResponse { - value: reveal.cloned(), - }) - } - - /// Returns a data result from the results with the given id, if it exists. - pub fn get_revealed_data_results( - deps: Deps, - dr_id: Hash, - ) -> StdResult { - let dr = DATA_REQUESTS.load(deps.storage, dr_id)?; - Ok(GetRevealedDataResultsResponse { value: dr.reveals }) - } - - /// Returns a data result from the results with the given id, if it exists. - pub fn get_resolved_data_result( - deps: Deps, - dr_id: Hash, - ) -> StdResult { - let result = DATA_RESULTS.load(deps.storage, dr_id)?; - Ok(GetResolvedDataResultResponse { value: result }) - } - - /// Returns a vector of data requests ids - pub fn get_drs_ids(deps: Deps) -> StdResult { - let mut ids = Vec::new(); - for (_, key) in DATA_REQUESTS - .keys(deps.storage, None, None, cosmwasm_std::Order::Ascending) - .enumerate() - { - ids.push(key?) - } - Ok(GetIdsResponse { value: ids }) - } - - /// Returns a vector of data results ids - pub fn get_results_ids(deps: Deps) -> StdResult { - let mut ids = Vec::new(); - for (_, key) in DATA_RESULTS - .keys(deps.storage, None, None, cosmwasm_std::Order::Ascending) - .enumerate() - { - ids.push(key?) - } - Ok(GetIdsResponse { value: ids }) - } - - /// Returns a vector of committed executors - pub fn get_committed_executors( - deps: Deps, - dr_id: Hash, - ) -> StdResult { - let mut executors = Vec::new(); - for key in DATA_REQUESTS.load(deps.storage, dr_id)?.commits.keys() { - executors.push(key.clone()) - } - Ok(GetCommittedExecutorsResponse { value: executors }) - } - - /// Returns a vector of revealed data requests ids, if it exists. - - fn compute_hash(reveal: &str, salt: &str) -> String { - let mut hasher = Keccak256::new(); - hasher.update(reveal.as_bytes()); - hasher.update(salt.as_bytes()); - let digest = hasher.finalize(); - format!("0x{}", hex::encode(digest)) - } -} - -#[cfg(test)] -mod dr_result_tests { - use super::*; - use crate::contract::execute; - use crate::contract::query; - use crate::state::DataRequestInputs; - use crate::state::ELIGIBLE_DATA_REQUEST_EXECUTORS; - use crate::utils::hash_data_request; - use crate::utils::hash_update; - use common::msg::{ - ExecuteMsg, GetDataRequestResponse, GetDataRequestsFromPoolResponse, - GetResolvedDataResultResponse, PostDataRequestArgs, QueryMsg, - }; - use common::state::Reveal; - use common::types::{Bytes, Memo}; - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cosmwasm_std::Addr; - use cosmwasm_std::{coins, from_binary}; - use sha3::{Digest, Keccak256}; - - use crate::contract::instantiate; - use crate::msg::InstantiateMsg; - - #[test] - fn commit_reveal_result() { - let mut deps = mock_dependencies(); - - let msg = InstantiateMsg { - token: "token".to_string(), - proxy: "proxy".to_string(), - }; - let info = mock_info("creator", &coins(2, "token")); - let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); - // register dr executor - let info = mock_info("executor1", &coins(1, "token")); - let msg = ExecuteMsg::RegisterDataRequestExecutor { - p2p_multi_address: Some("address1".to_string()), - sender: None, - }; - - let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - let executor_is_eligible: bool = ELIGIBLE_DATA_REQUEST_EXECUTORS - .load(&deps.storage, info.sender.clone()) - .unwrap(); - assert!(executor_is_eligible); - - // register dr executor - let info = mock_info("executor2", &coins(1, "token")); - let msg = ExecuteMsg::RegisterDataRequestExecutor { - p2p_multi_address: Some("address2".to_string()), - sender: None, - }; - - let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - let executor_is_eligible: bool = ELIGIBLE_DATA_REQUEST_EXECUTORS - .load(&deps.storage, info.sender.clone()) - .unwrap(); - assert!(executor_is_eligible); - - // can't post a data result for a data request that doesn't exist - let info = mock_info("executor1", &coins(2, "token")); - let msg = ExecuteMsg::CommitDataResult { - dr_id: "0x7e059b547de461457d49cd4b229c5cd172a6ac8063738068b932e26c3868e4ae".to_string(), - commitment: "dr 0 result".to_string(), - sender: None, - }; - let res = execute(deps.as_mut(), mock_env(), info, msg); - assert!(res.is_err()); - - let dr_binary_id: Hash = "".to_string(); - let tally_binary_id: Hash = "".to_string(); - let dr_inputs: Bytes = Vec::new(); - let tally_inputs: Bytes = Vec::new(); - let replication_factor: u16 = 2; - - let gas_price: u128 = 0; - let gas_limit: u128 = 0; - - let seda_payload: Bytes = Vec::new(); - - let chain_id = 31337; - let nonce = 1; - let value = "ETH/USD".to_string(); - let payback_address: Bytes = Vec::new(); - - let mut hasher = Keccak256::new(); - hash_update(&mut hasher, &chain_id); - hash_update(&mut hasher, &nonce); - hasher.update(value); - let binary_hash = format!("0x{}", hex::encode(hasher.finalize())); - let memo: Memo = binary_hash.clone().into_bytes(); - let dr_inputs1 = DataRequestInputs { - dr_binary_id: dr_binary_id.clone(), - tally_binary_id: tally_binary_id.clone(), - dr_inputs: dr_inputs.clone(), - tally_inputs: tally_inputs.clone(), - memo: memo.clone(), - replication_factor, - - gas_price, - gas_limit, - - seda_payload: seda_payload.clone(), - payback_address: payback_address.clone(), - }; - let constructed_dr_id = hash_data_request(dr_inputs1); - - let posted_dr: PostDataRequestArgs = PostDataRequestArgs { - dr_id: constructed_dr_id.clone(), - - dr_binary_id: dr_binary_id.clone(), - tally_binary_id, - dr_inputs, - tally_inputs, - memo, - replication_factor, - - gas_price, - gas_limit, - - seda_payload, - payback_address, - }; - // someone posts a data request - let info = mock_info("anyone", &coins(2, "token")); - let msg = ExecuteMsg::PostDataRequest { posted_dr }; - let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - // can fetch it via `get_data_requests_from_pool` - let res = query( - deps.as_ref(), - mock_env(), - QueryMsg::GetDataRequestsFromPool { - position: None, - limit: None, - }, - ); - let value: GetDataRequestsFromPoolResponse = from_binary(&res.unwrap()).unwrap(); - assert_eq!(value.value.len(), 1); - let executor: Addr = info.sender.clone(); - // data result with id 0x66... does not yet exist - let res = query( - deps.as_ref(), - mock_env(), - QueryMsg::GetCommittedDataResult { - dr_id: constructed_dr_id.clone(), - executor: executor.clone(), - }, - ) - .unwrap(); - let value: GetDataRequestResponse = from_binary(&res).unwrap(); - assert_eq!(None, value.value); - - let reveal = "2000"; - let salt = "executor1"; - - let mut hasher = Keccak256::new(); - hasher.update(reveal.as_bytes()); - hasher.update(salt.as_bytes()); - let digest = hasher.finalize(); - let commitment1 = format!("0x{}", hex::encode(digest)); - - // executor1 commits a data result - let info = mock_info("executor1", &coins(2, "token")); - - let msg = ExecuteMsg::CommitDataResult { - dr_id: constructed_dr_id.clone(), - commitment: commitment1, - sender: None, - }; - let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - let reveal = "2200"; - let salt = "executor2"; - let mut hasher = Keccak256::new(); - hasher.update(reveal.as_bytes()); - hasher.update(salt.as_bytes()); - let digest = hasher.finalize(); - let commitment2 = format!("0x{}", hex::encode(digest)); - - // executor2 commits a data result - let info = mock_info("executor2", &coins(2, "token")); - let msg = ExecuteMsg::CommitDataResult { - dr_id: constructed_dr_id.clone(), - commitment: commitment2.clone(), - sender: None, - }; - let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - let executor2: Addr = info.sender.clone(); - - // should be able to fetch data result with id 0x66... - let res = query( - deps.as_ref(), - mock_env(), - QueryMsg::GetCommittedDataResult { - dr_id: constructed_dr_id.clone(), - executor: executor2.clone(), - }, - ) - .unwrap(); - let value: GetCommittedDataResultResponse = from_binary(&res).unwrap(); - - assert_eq!(Some(commitment2.clone()), value.value); - let reveal1 = Reveal { - reveal: "2000".to_string(), - salt: "executor1".to_string(), - }; - let info = mock_info("executor1", &coins(2, "token")); - let executor1 = info.sender.clone(); - let msg = ExecuteMsg::RevealDataResult { - dr_id: constructed_dr_id.clone(), - reveal: reveal1.clone(), - sender: None, - }; - let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - let res = query( - deps.as_ref(), - mock_env(), - QueryMsg::GetRevealedDataResult { - dr_id: constructed_dr_id.clone(), - executor: executor1.clone(), - }, - ) - .unwrap(); - let value: GetRevealedDataResultResponse = from_binary(&res).unwrap(); - - assert_eq!(Some(reveal1.clone()), value.value); - let reveal2 = Reveal { - reveal: "2200".to_string(), - salt: "executor2".to_string(), - }; - - let info = mock_info("executor2", &coins(2, "token")); - let msg = ExecuteMsg::RevealDataResult { - dr_id: constructed_dr_id.clone(), - reveal: reveal2.clone(), - sender: None, - }; - let _res = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - - let res = query( - deps.as_ref(), - mock_env(), - QueryMsg::GetResolvedDataResult { - dr_id: constructed_dr_id.clone(), - }, - ) - .unwrap(); - let value: GetResolvedDataResultResponse = from_binary(&res).unwrap(); - assert_eq!(reveal2.reveal.as_bytes().to_vec(), value.value.result); - } - - #[test] - #[should_panic] - - fn ineligible_post_data_result() { - let mut deps = mock_dependencies(); - - let msg = InstantiateMsg { - token: "token".to_string(), - proxy: "proxy".to_string(), - }; - let info = mock_info("creator", &coins(2, "token")); - let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); - let dr_binary_id: Hash = "".to_string(); - let tally_binary_id: Hash = "".to_string(); - let dr_inputs: Bytes = Vec::new(); - let tally_inputs: Bytes = Vec::new(); - let replication_factor: u16 = 3; - - let gas_price: u128 = 0; - let gas_limit: u128 = 0; - - let seda_payload: Bytes = Vec::new(); - - let chain_id = 31337; - let nonce = 1; - let value = "hello world".to_string(); - let mut hasher = Keccak256::new(); - hash_update(&mut hasher, &chain_id); - hash_update(&mut hasher, &nonce); - hasher.update(value); - let binary_hash = format!("0x{}", hex::encode(hasher.finalize())); - let memo: Memo = binary_hash.clone().into_bytes(); - let payback_address: Bytes = Vec::new(); - - let dr_inputs1 = DataRequestInputs { - dr_binary_id: dr_binary_id.clone(), - tally_binary_id: tally_binary_id.clone(), - dr_inputs: dr_inputs.clone(), - tally_inputs: tally_inputs.clone(), - memo: memo.clone(), - replication_factor, - - gas_price, - gas_limit, - - seda_payload: seda_payload.clone(), - payback_address: payback_address.clone(), - }; - let dr_id: String = hash_data_request(dr_inputs1); - let posted_dr: PostDataRequestArgs = PostDataRequestArgs { - dr_id: dr_id.clone(), - - dr_binary_id: dr_binary_id.clone(), - tally_binary_id, - dr_inputs, - tally_inputs, - memo, - replication_factor, - - gas_price, - gas_limit, - - seda_payload, - payback_address, - }; - // someone posts a data request - let info = mock_info("anyone", &coins(2, "token")); - let msg = ExecuteMsg::PostDataRequest { posted_dr }; - let _res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); - - // ineligible shouldn't be able to post a data result - let info = mock_info("ineligible", &coins(2, "token")); - let msg = ExecuteMsg::CommitDataResult { - dr_id: binary_hash.clone(), - commitment: "dr 0 result".to_string(), - sender: None, - }; - execute(deps.as_mut(), mock_env(), info, msg).unwrap(); - } -} diff --git a/packages/staking/Cargo.toml b/packages/staking/Cargo.toml new file mode 100644 index 00000000..c0a89fd6 --- /dev/null +++ b/packages/staking/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "staking" +version = "0.1.0" +edition = "2021" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[package.metadata.scripts] +optimize = """docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.12.10 +""" + +[dependencies] +common = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +thiserror = { workspace = true } +cw2 = { workspace = true } diff --git a/packages/staking/src/bin/schema.rs b/packages/staking/src/bin/schema.rs new file mode 100644 index 00000000..25869d6c --- /dev/null +++ b/packages/staking/src/bin/schema.rs @@ -0,0 +1,12 @@ +use cosmwasm_schema::write_api; + +use common::msg::{StakingExecuteMsg, StakingQueryMsg}; +use staking::msg::InstantiateMsg; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: StakingExecuteMsg, + query: StakingQueryMsg, + } +} diff --git a/packages/seda-chain-contracts/src/consts.rs b/packages/staking/src/consts.rs similarity index 100% rename from packages/seda-chain-contracts/src/consts.rs rename to packages/staking/src/consts.rs diff --git a/packages/seda-chain-contracts/src/contract.rs b/packages/staking/src/contract.rs similarity index 67% rename from packages/seda-chain-contracts/src/contract.rs rename to packages/staking/src/contract.rs index 829275a1..a3041fd6 100644 --- a/packages/seda-chain-contracts/src/contract.rs +++ b/packages/staking/src/contract.rs @@ -1,20 +1,19 @@ -use common::msg::{ExecuteMsg, QueryMsg}; #[cfg(not(feature = "library"))] use cosmwasm_std::{entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response}; use cw2::set_contract_version; -use crate::data_request::data_requests; use crate::error::ContractError; use crate::executors_registry::data_request_executors; use crate::msg::InstantiateMsg; use crate::staking::staking; -use crate::state::{DATA_REQUESTS_COUNT, PROXY_CONTRACT, TOKEN}; +use crate::state::{PROXY_CONTRACT, TOKEN}; +use common::msg::StakingExecuteMsg as ExecuteMsg; +use common::msg::StakingQueryMsg as QueryMsg; -use crate::data_request_result::data_request_results; use cosmwasm_std::StdResult; // version info for migration info -const CONTRACT_NAME: &str = "seda-chain-contracts"; +const CONTRACT_NAME: &str = "staking"; const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[cfg_attr(not(feature = "library"), entry_point)] @@ -25,7 +24,6 @@ pub fn instantiate( msg: InstantiateMsg, ) -> Result { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - DATA_REQUESTS_COUNT.save(deps.storage, &0)?; TOKEN.save(deps.storage, &msg.token)?; PROXY_CONTRACT.save(deps.storage, &deps.api.addr_validate(&msg.proxy)?)?; Ok(Response::new().add_attribute("method", "instantiate")) @@ -39,19 +37,6 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::PostDataRequest { posted_dr } => { - data_requests::post_data_request(deps, info, posted_dr) - } - ExecuteMsg::CommitDataResult { - dr_id, - commitment, - sender, - } => data_request_results::commit_result(deps, info, dr_id, commitment, sender), - ExecuteMsg::RevealDataResult { - dr_id, - reveal, - sender, - } => data_request_results::reveal_result(deps, info, env, dr_id, reveal, sender), ExecuteMsg::RegisterDataRequestExecutor { p2p_multi_address, sender, @@ -77,30 +62,12 @@ pub fn execute( #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::GetDataRequest { dr_id } => { - to_binary(&data_requests::get_data_request(deps, dr_id)?) - } - QueryMsg::GetDataRequestsFromPool { position, limit } => to_binary( - &data_requests::get_data_requests_from_pool(deps, position, limit)?, - ), - QueryMsg::GetCommittedDataResult { dr_id, executor } => to_binary( - &data_request_results::get_committed_data_result(deps, dr_id, executor)?, - ), - QueryMsg::GetCommittedDataResults { dr_id } => to_binary( - &data_request_results::get_committed_data_results(deps, dr_id)?, - ), - QueryMsg::GetRevealedDataResult { dr_id, executor } => to_binary( - &data_request_results::get_revealed_data_result(deps, dr_id, executor)?, - ), - QueryMsg::GetRevealedDataResults { dr_id } => to_binary( - &data_request_results::get_revealed_data_results(deps, dr_id)?, - ), - QueryMsg::GetResolvedDataResult { dr_id } => to_binary( - &data_request_results::get_resolved_data_result(deps, dr_id)?, - ), QueryMsg::GetDataRequestExecutor { executor } => to_binary( &data_request_executors::get_data_request_executor(deps, executor)?, ), + QueryMsg::IsDataRequestExecutorEligible { executor } => to_binary( + &data_request_executors::is_data_request_executor_eligible(deps, executor)?, + ), } } diff --git a/packages/staking/src/error.rs b/packages/staking/src/error.rs new file mode 100644 index 00000000..932e92ef --- /dev/null +++ b/packages/staking/src/error.rs @@ -0,0 +1,16 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + #[error("No funds provided")] + NoFunds, + #[error("Insufficient funds. Required: {0}, available: {1}")] + InsufficientFunds(u128, u128), + #[error("Only proxy can pass a sender")] + NotProxy, + #[error("Executor has staked tokens or tokens pending withdrawal")] + ExecutorHasTokens, +} diff --git a/packages/seda-chain-contracts/src/executors_registry.rs b/packages/staking/src/executors_registry.rs similarity index 91% rename from packages/seda-chain-contracts/src/executors_registry.rs rename to packages/staking/src/executors_registry.rs index f96278fa..73224d50 100644 --- a/packages/seda-chain-contracts/src/executors_registry.rs +++ b/packages/staking/src/executors_registry.rs @@ -2,18 +2,18 @@ use cosmwasm_std::{Deps, DepsMut, MessageInfo, Response, StdResult}; use crate::consts::MINIMUM_STAKE_TO_REGISTER; -use crate::state::DATA_REQUEST_EXECUTORS; -use crate::state::TOKEN; +use crate::error::ContractError; +use crate::state::{DATA_REQUEST_EXECUTORS, TOKEN}; use crate::utils::{get_attached_funds, validate_sender}; -use crate::error::ContractError; use common::msg::GetDataRequestExecutorResponse; use common::state::DataRequestExecutor; pub mod data_request_executors { + use common::msg::IsDataRequestExecutorEligibleResponse; use cosmwasm_std::Addr; - use crate::utils::apply_validator_eligibility; + use crate::{state::ELIGIBLE_DATA_REQUEST_EXECUTORS, utils::apply_validator_eligibility}; use super::*; @@ -81,6 +81,17 @@ pub mod data_request_executors { let executor = DATA_REQUEST_EXECUTORS.may_load(deps.storage, executor)?; Ok(GetDataRequestExecutorResponse { value: executor }) } + + /// Returns whether a data request executor is eligible to participate in the committee. + pub fn is_data_request_executor_eligible( + deps: Deps, + executor: Addr, + ) -> StdResult { + let executor = ELIGIBLE_DATA_REQUEST_EXECUTORS.may_load(deps.storage, executor)?; + Ok(IsDataRequestExecutorEligibleResponse { + value: executor.is_some(), + }) + } } #[cfg(test)] @@ -90,7 +101,8 @@ mod executers_tests { use crate::contract::instantiate; use crate::contract::query; use crate::msg::InstantiateMsg; - use common::msg::{ExecuteMsg, QueryMsg}; + use common::msg::StakingExecuteMsg as ExecuteMsg; + use common::msg::StakingQueryMsg as QueryMsg; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use cosmwasm_std::{coins, from_binary, Addr}; diff --git a/packages/staking/src/lib.rs b/packages/staking/src/lib.rs new file mode 100644 index 00000000..03e47050 --- /dev/null +++ b/packages/staking/src/lib.rs @@ -0,0 +1,8 @@ +pub mod consts; +pub mod contract; +pub mod error; +pub mod executors_registry; +pub mod msg; +pub mod staking; +pub mod state; +pub mod utils; diff --git a/packages/staking/src/msg.rs b/packages/staking/src/msg.rs new file mode 100644 index 00000000..35a55f31 --- /dev/null +++ b/packages/staking/src/msg.rs @@ -0,0 +1,7 @@ +use cosmwasm_schema::cw_serde; + +#[cw_serde] +pub struct InstantiateMsg { + pub token: String, + pub proxy: String, +} diff --git a/packages/seda-chain-contracts/src/staking.rs b/packages/staking/src/staking.rs similarity index 99% rename from packages/seda-chain-contracts/src/staking.rs rename to packages/staking/src/staking.rs index 5cce8fca..9b03893e 100644 --- a/packages/seda-chain-contracts/src/staking.rs +++ b/packages/staking/src/staking.rs @@ -122,7 +122,8 @@ mod staking_tests { use crate::msg::InstantiateMsg; use crate::state::ELIGIBLE_DATA_REQUEST_EXECUTORS; use common::msg::GetDataRequestExecutorResponse; - use common::msg::{ExecuteMsg, QueryMsg}; + use common::msg::StakingExecuteMsg as ExecuteMsg; + use common::msg::StakingQueryMsg as QueryMsg; use common::state::DataRequestExecutor; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use cosmwasm_std::{coins, from_binary, Addr}; diff --git a/packages/staking/src/state.rs b/packages/staking/src/state.rs new file mode 100644 index 00000000..8bc0431a --- /dev/null +++ b/packages/staking/src/state.rs @@ -0,0 +1,17 @@ +use common::state::DataRequestExecutor; +use cosmwasm_std::Addr; +use cw_storage_plus::{Item, Map}; + +/// Address of the token used for data request executor staking +pub const TOKEN: Item = Item::new("token"); + +/// A map of data request executors (of address to info) that have not yet been marked as active +pub const DATA_REQUEST_EXECUTORS: Map = + Map::new("data_request_executors"); + +/// A map of data request executors (of address to info) that are eligible for committee inclusion +pub const ELIGIBLE_DATA_REQUEST_EXECUTORS: Map = + Map::new("eligible_data_request_executors"); + +/// Address of proxy contract which has permission to set the sender on one's behalf +pub const PROXY_CONTRACT: Item = Item::new("proxy_contract"); diff --git a/packages/staking/src/utils.rs b/packages/staking/src/utils.rs new file mode 100644 index 00000000..46154547 --- /dev/null +++ b/packages/staking/src/utils.rs @@ -0,0 +1,45 @@ +use crate::error::ContractError; +use cosmwasm_std::{Addr, Coin, DepsMut}; + +use crate::{ + consts::MINIMUM_STAKE_FOR_COMMITTEE_ELIGIBILITY, + state::{ELIGIBLE_DATA_REQUEST_EXECUTORS, PROXY_CONTRACT}, +}; + +pub fn apply_validator_eligibility( + deps: DepsMut, + sender: Addr, + tokens_staked: u128, +) -> Result<(), ContractError> { + if tokens_staked < MINIMUM_STAKE_FOR_COMMITTEE_ELIGIBILITY { + if ELIGIBLE_DATA_REQUEST_EXECUTORS.has(deps.storage, sender.clone()) { + ELIGIBLE_DATA_REQUEST_EXECUTORS.remove(deps.storage, sender); + } + } else { + ELIGIBLE_DATA_REQUEST_EXECUTORS.save(deps.storage, sender, &true)?; + } + Ok(()) +} + +pub fn get_attached_funds(funds: &[Coin], token: &str) -> Result { + let amount: Option = funds + .iter() + .find(|coin| coin.denom == token) + .map(|coin| coin.amount.u128()); + amount.ok_or(ContractError::NoFunds) +} + +pub fn validate_sender( + deps: &DepsMut, + caller: Addr, + sender: Option, +) -> Result { + // if a sender is passed, caller must be the proxy contract + match sender { + Some(_sender) if caller != PROXY_CONTRACT.load(deps.storage)? => { + Err(ContractError::NotProxy {}) + } + Some(sender) => Ok(deps.api.addr_validate(&sender)?), + None => Ok(caller), + } +}