use uuid::Uuid;
use std::{str::FromStr, sync::Arc};
use crate::{
error::RegoError,
evaluate,
mock_newton_policy_client::MockNewtonPolicyClient,
newton_policy::{INewtonPolicy, NewtonPolicy},
newton_prover_task_manager::{
INewtonPolicyClient::PolicySpec,
INewtonProverTaskManager::{self, Task},
NewtonMessage::{self, Intent},
},
rego::validate_schema,
PolicyId, TaskId,
};
use alloy::{
dyn_abi::DynSolValue,
primitives::{keccak256, Address, Bytes, ChainId, B256, U256},
sol_types::SolValue,
};
use cid::Cid;
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
pub use crate::eval::{decode_calldata, parse_intent, serialize_sol_value, ParsedIntent};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskRequest {
pub task_id: TaskId,
pub intent: NewtonMessage::Intent,
pub intent_signature: Option<Bytes>,
pub policy_client: Address,
pub policy_id: B256,
pub policies: Vec<PolicySpec>,
pub policy_revision: u64,
pub wasm_args: Vec<Bytes>,
pub quorum_numbers: Vec<u8>,
pub quorum_threshold_percentage: u32,
pub task_created_block: u64,
pub initialization_timestamp: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[serde(alias = "proofCid")]
pub proof_cid: Option<String>,
}
impl From<TaskRequest> for Task {
fn from(task_request: TaskRequest) -> Self {
Self {
taskId: task_request.task_id,
intent: task_request.intent,
intentSignature: task_request.intent_signature.unwrap_or_default(),
policyClient: task_request.policy_client,
policyId: task_request.policy_id,
policyRevision: task_request.policy_revision,
policies: task_request.policies,
taskCreatedBlock: task_request.task_created_block as u32,
wasmArgs: task_request.wasm_args,
quorumNumbers: task_request.quorum_numbers.into(),
quorumThresholdPercentage: task_request.quorum_threshold_percentage,
initializationTimestamp: U256::from(task_request.initialization_timestamp),
}
}
}
pub fn write_serialized(buffer: &mut Vec<u8>, data: &[u8]) -> Result<(), bincode::error::EncodeError> {
let mut input: Vec<u8> = Vec::new();
bincode::encode_into_slice(data, &mut input, bincode::config::standard())?;
buffer.extend_from_slice(&input);
Ok(())
}
pub fn task_id(seed: Option<&str>) -> TaskId {
let uuid = if let Some(seed) = seed {
Uuid::from_str(seed).unwrap_or_else(|_| Uuid::new_v4())
} else {
Uuid::new_v4()
};
let hash = keccak256(uuid.as_bytes());
TaskId::from(hash)
}
pub fn merge_jsons(jsons: Vec<serde_json::Value>) -> serde_json::Value {
let merged = jsons.iter().fold(serde_json::Map::new(), |mut merged, data| {
if let serde_json::Value::Object(map) = data {
merged.extend(map.iter().map(|(k, v)| (k.clone(), v.clone())));
}
merged
});
serde_json::Value::Object(merged)
}
pub fn merge_secrets_schemas(schema_docs: Vec<(String, serde_json::Value)>) -> eyre::Result<serde_json::Value> {
let mut merged_properties: serde_json::Map<String, serde_json::Value> = serde_json::Map::new();
let mut merged_required: BTreeSet<String> = BTreeSet::new();
for (cid, schema_json) in schema_docs {
let Some(schema_obj) = schema_json.as_object() else {
continue;
};
if let Some(props_val) = schema_obj.get("properties") {
if let Some(props_obj) = props_val.as_object() {
for (k, v) in props_obj.iter() {
if !merged_properties.contains_key(k) {
merged_properties.insert(k.clone(), v.clone());
}
}
} else if !props_val.is_null() {
continue;
}
}
if let Some(req_val) = schema_obj.get("required") {
if let Some(req_arr) = req_val.as_array() {
let mut local_required: Vec<String> = Vec::with_capacity(req_arr.len());
for item in req_arr {
let Some(key) = item.as_str() else {
local_required.clear();
break;
};
local_required.push(key.to_string());
}
if local_required.is_empty() && !req_arr.is_empty() {
continue;
}
for k in local_required {
merged_required.insert(k);
}
} else if !req_val.is_null() {
continue;
}
}
}
let mut merged = serde_json::Map::new();
merged.insert("type".to_string(), serde_json::Value::String("object".to_string()));
merged.insert("properties".to_string(), serde_json::Value::Object(merged_properties));
if !merged_required.is_empty() {
merged.insert(
"required".to_string(),
serde_json::Value::Array(merged_required.into_iter().map(serde_json::Value::String).collect()),
);
}
Ok(serde_json::Value::Object(merged))
}
pub mod rpc {
#![cfg(feature = "rpc")]
use crate::{
common::{
intent::ParsedIntent,
policy_runtime::PolicyRuntime,
policy_set::{
check_policy_data_bounds_with_cap, ResolvedPolicySet, MAX_POLICIES, MAX_RESPONSE_POLICY_BYTES,
},
},
error::RegoError,
evaluate,
newton_policy::{INewtonPolicy, NewtonPolicy},
newton_prover_task_manager::NewtonMessage,
rego::validate_schema,
};
use alloy::{
primitives::{keccak256, Address, Bytes, B256},
providers::Provider,
};
use newton_rpc_provider::get_provider;
use regorus::extensions::PolicyDomainData;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PolicyEvaluationResult {
pub policy_index: usize,
pub policy: Address,
pub rego: Bytes,
pub entrypoint: String,
pub params: serde_json::Value,
pub policy_input: serde_json::Value,
pub result: regorus::Value,
pub allowed: bool,
pub expire_after: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PolicySetEvaluationResult {
pub parsed_intent: ParsedIntent,
pub policy_id: B256,
pub policies: Vec<PolicyEvaluationResult>,
pub allowed: bool,
}
pub async fn parse_and_evaluate_policy_set(
intent: &NewtonMessage::Intent,
policy_client: Address,
oracle_outputs: &[Bytes],
rpc_url: &str,
fetcher: &dyn ObjectFetcher,
additional_data: Option<serde_json::Value>,
) -> Result<PolicySetEvaluationResult, RegoError> {
tracing::info!("evaluating policy set for policy client {}", policy_client);
let policy_set = resolve_policy_set(policy_client, rpc_url, fetcher).await?;
evaluate_resolved_policy_set(
serde_json::json!(intent),
&policy_set,
oracle_outputs,
&[],
additional_data,
newton_rego_kernel::ErrorDisposition::Propagate,
)
}
pub fn evaluate_resolved_policy_set(
intent: serde_json::Value,
policy_set: &ResolvedPolicySet,
oracle_outputs: &[Bytes],
domain_data: &[Box<dyn regorus::extensions::PolicyDomainData>],
additional_data: Option<serde_json::Value>,
on_error: newton_rego_kernel::ErrorDisposition,
) -> Result<PolicySetEvaluationResult, RegoError> {
use crate::common::parse_intent;
if policy_set.policies.is_empty() {
return Err(RegoError::FailedToEvaluateTask(
"policy set cannot be empty".to_string(),
));
}
if policy_set.policies.len() != oracle_outputs.len() {
return Err(RegoError::FailedToEvaluateTask(format!(
"oracle output count mismatch: policy set has {} policies, got {} oracle outputs",
policy_set.policies.len(),
oracle_outputs.len()
)));
}
check_policy_data_bounds_with_cap(
policy_set
.policies
.iter()
.zip(oracle_outputs)
.enumerate()
.flat_map(|(i, (policy, output))| {
[
(i, "params", policy.policy_config.policyParams.len()),
(i, "rego", policy.rego.len()),
(i, "output", output.len()),
]
}),
MAX_RESPONSE_POLICY_BYTES,
)
.map_err(|e| RegoError::FailedToEvaluateTask(e.to_string()))?;
let parsed_intent = parse_intent(intent).map_err(|e| RegoError::FailedToParseIntent(e.to_string()))?;
let parsed_intent_str: String = parsed_intent.clone().into();
let evals: Vec<newton_rego_kernel::PolicyEvaluation<'_>> = policy_set
.policies
.iter()
.zip(oracle_outputs.iter())
.map(|(p, output)| newton_rego_kernel::PolicyEvaluation {
rego: &p.rego,
entrypoint: &p.entrypoint,
params: &p.policy_config.policyParams,
oracle_output: output,
})
.collect();
let verdict = newton_rego_kernel::evaluate_policy_set(
&evals,
&parsed_intent_str,
domain_data,
additional_data.as_ref(),
on_error,
)
.map_err(|e| RegoError::FailedToEvaluateTask(e.to_string()))?;
let policy_results: Vec<PolicyEvaluationResult> = verdict
.policies
.into_iter()
.enumerate()
.zip(policy_set.policies.iter())
.map(|((i, v), p)| PolicyEvaluationResult {
policy_index: i,
policy: p.policy_address,
rego: p.rego.clone(),
entrypoint: p.entrypoint.clone(),
params: v.params,
policy_input: v.oracle_output,
result: v.value,
allowed: v.allowed,
expire_after: p.policy_config.expireAfter,
})
.collect();
Ok(PolicySetEvaluationResult {
parsed_intent,
policy_id: policy_set.policy_id,
policies: policy_results,
allowed: verdict.allowed,
})
}
#[async_trait::async_trait]
pub trait ObjectFetcher: Send + Sync {
async fn get_object(&self, cid: &str) -> eyre::Result<Vec<u8>>;
}
pub async fn resolve_policy_set(
policy_client: Address,
rpc_url: &str,
fetcher: &dyn ObjectFetcher,
) -> Result<ResolvedPolicySet, RegoError> {
let provider = get_provider(rpc_url);
let policy_client_contract =
crate::newton_policy_client::NewtonPolicyClient::new(policy_client, provider.clone());
let snapshot = policy_client_contract
.getPolicySetSnapshot()
.call()
.await
.map_err(|e| RegoError::FailedToGetPolicies(e.to_string()))?;
let policy_id = snapshot._0;
let revision = snapshot._1;
let policy_specs = snapshot._2;
if policy_specs.is_empty() {
return Err(RegoError::FailedToGetPolicies("policy set cannot be empty".to_string()));
}
if policy_specs.len() > MAX_POLICIES {
return Err(RegoError::FailedToGetPolicies(format!(
"policy set exceeds MAX_POLICIES ({}): got {}",
MAX_POLICIES,
policy_specs.len()
)));
}
let (chain_id, current_block) = tokio::try_join!(
async {
provider
.get_chain_id()
.await
.map_err(|e| format!("failed to get chain ID: {e}"))
},
async {
provider
.get_block_number()
.await
.map_err(|e| format!("failed to read chain head: {e}"))
},
)
.map_err(RegoError::FailedToGetPolicyId)?;
let current_block = current_block as u32;
let policies = futures_util::future::try_join_all(policy_specs.iter().map(|policy_spec| {
resolve_policy(
policy_client,
policy_id,
chain_id,
current_block,
policy_spec.policy,
&policy_spec.config.policyParams,
policy_spec.config.expireAfter,
rpc_url,
fetcher,
)
}))
.await?;
let policy_set = ResolvedPolicySet {
policy_client,
policy_id,
revision,
policies,
};
policy_set
.verify_policy_id(chain_id)
.map_err(|e| RegoError::FailedToGetPolicyId(format!("policy ID verification failed: {}", e)))?;
check_policy_data_bounds_with_cap(
policy_set.policies.iter().enumerate().flat_map(|(i, p)| {
[
(i, "params", p.policy_config.policyParams.len()),
(i, "rego", p.rego.len()),
]
}),
MAX_RESPONSE_POLICY_BYTES,
)
.map_err(|e| RegoError::FailedToGetPolicies(e.to_string()))?;
Ok(policy_set)
}
pub async fn resolve_policy_set_snapshot(
policy_client: Address,
policy_id: B256,
revision: u64,
specs: &[crate::common::types::PolicySpec],
rpc_url: &str,
fetcher: &dyn ObjectFetcher,
) -> Result<ResolvedPolicySet, RegoError> {
if specs.is_empty() {
return Err(RegoError::FailedToGetPolicies("policy set cannot be empty".to_string()));
}
if specs.len() > MAX_POLICIES {
return Err(RegoError::FailedToGetPolicies(format!(
"policy set exceeds MAX_POLICIES ({}): got {}",
MAX_POLICIES,
specs.len()
)));
}
let provider = get_provider(rpc_url);
let (chain_id, current_block) = tokio::try_join!(
async {
provider
.get_chain_id()
.await
.map_err(|e| format!("failed to get chain ID: {e}"))
},
async {
provider
.get_block_number()
.await
.map_err(|e| format!("failed to read chain head: {e}"))
},
)
.map_err(RegoError::FailedToGetPolicyId)?;
let current_block = current_block as u32;
let policies = futures_util::future::try_join_all(specs.iter().map(|spec| {
resolve_policy(
policy_client,
policy_id,
chain_id,
current_block,
spec.policy,
&spec.config.policy_params,
spec.config.expire_after,
rpc_url,
fetcher,
)
}))
.await?;
let policy_set = ResolvedPolicySet {
policy_client,
policy_id,
revision,
policies,
};
policy_set
.verify_policy_id(chain_id)
.map_err(|e| RegoError::FailedToGetPolicyId(format!("policy ID verification failed: {}", e)))?;
check_policy_data_bounds_with_cap(
policy_set.policies.iter().enumerate().flat_map(|(i, p)| {
[
(i, "params", p.policy_config.policyParams.len()),
(i, "rego", p.rego.len()),
]
}),
MAX_RESPONSE_POLICY_BYTES,
)
.map_err(|e| RegoError::FailedToGetPolicies(e.to_string()))?;
Ok(policy_set)
}
#[allow(clippy::too_many_arguments)]
async fn resolve_policy(
policy_client: Address,
set_policy_id: B256,
chain_id: u64,
current_block: u32,
policy_address: Address,
policy_params: &Bytes,
expire_after: u32,
rpc_url: &str,
fetcher: &dyn ObjectFetcher,
) -> Result<PolicyRuntime, RegoError> {
let provider = get_provider(rpc_url);
let policy_contract = NewtonPolicy::new(policy_address, provider.clone());
let (policy_cid, entrypoint, schema_cid, policy_code_hash, wasm_cid, secrets_schema_cid) = tokio::try_join!(
async {
policy_contract
.getPolicyCid()
.call()
.await
.map_err(|e| RegoError::FailedToGetArtifact(e.to_string()))
},
async {
policy_contract
.getEntrypoint()
.call()
.await
.map_err(|e| RegoError::FailedToGetPolicyEntrypoint(e.to_string()))
},
async {
policy_contract
.getSchemaCid()
.call()
.await
.map_err(|e| RegoError::FailedToGetPolicySchemaCid(e.to_string()))
},
async {
policy_contract
.getPolicyCodeHash()
.call()
.await
.map_err(|e| RegoError::FailedToGetArtifact(e.to_string()))
},
async {
policy_contract
.getWasmCid()
.call()
.await
.map_err(|e| RegoError::FailedToGetArtifact(e.to_string()))
},
async {
policy_contract
.getSecretsSchemaCid()
.call()
.await
.map_err(|e| RegoError::FailedToGetArtifact(e.to_string()))
},
)?;
let rego_bytes = fetcher
.get_object(&policy_cid)
.await
.map_err(|e| RegoError::FailedToFetchRego(e.to_string()))?;
let computed = keccak256(®o_bytes);
if computed != policy_code_hash {
return Err(RegoError::RegoCodeHashMismatch {
policy: policy_address.to_string(),
expected: policy_code_hash.to_string(),
computed: computed.to_string(),
});
}
let schema = validate_policy_params(policy_address, policy_params, &schema_cid, fetcher).await?;
Ok(PolicyRuntime {
chain_id,
policy_client,
policy_id: set_policy_id,
policy_address,
policy_config: INewtonPolicy::PolicyConfig {
policyParams: policy_params.clone(),
expireAfter: expire_after,
},
entrypoint,
schema,
policy_cid,
policy_code_hash,
current_block,
expire_block: current_block.saturating_add(expire_after),
wasm_cid,
secrets_schema_cid,
rego: Bytes::from(rego_bytes),
})
}
async fn validate_policy_params(
policy_address: Address,
params: &Bytes,
schema_cid: &str,
fetcher: &dyn ObjectFetcher,
) -> Result<serde_json::Value, RegoError> {
if schema_cid.is_empty() {
return Ok(serde_json::Value::Null);
}
let params_json = if params.is_empty() {
serde_json::json!({})
} else {
let text = std::str::from_utf8(params).map_err(|e| {
RegoError::FailedToValidateParamsSchema(format!(
"policy {policy_address} params are not valid UTF-8: {e}"
))
})?;
serde_json::from_str(text).map_err(|e| {
RegoError::FailedToValidateParamsSchema(format!(
"policy {policy_address} params are not valid JSON: {e}"
))
})?
};
let schema = load_policy_schema(schema_cid, fetcher).await?;
validate_schema(schema.clone(), params_json)
.map_err(|e| RegoError::FailedToValidateParamsSchema(format!("policy {policy_address}: {e}")))?;
Ok(schema)
}
async fn load_policy_schema(schema_cid: &str, fetcher: &dyn ObjectFetcher) -> Result<serde_json::Value, RegoError> {
let bytes = fetcher
.get_object(schema_cid)
.await
.map_err(|e| RegoError::FailedToFetchPolicySchemaJson(e.to_string()))?;
let schema_text =
String::from_utf8(bytes).map_err(|e| RegoError::FailedToDecodePolicySchemaJson(e.to_string()))?;
serde_json::from_str(&schema_text).map_err(|e| RegoError::FailedToDecodePolicySchemaJson(e.to_string()))
}
#[cfg(test)]
mod params_schema_tests {
use super::*;
const SCHEMA_CID: &str = "schema";
struct StaticFetcher(&'static str);
#[async_trait::async_trait]
impl ObjectFetcher for StaticFetcher {
async fn get_object(&self, _cid: &str) -> eyre::Result<Vec<u8>> {
Ok(self.0.as_bytes().to_vec())
}
}
const SCHEMA: &str = r#"{"type":"object","properties":{"limit":{"type":"number"}},"required":["limit"]}"#;
async fn check(params: &[u8], schema_cid: &str) -> Result<serde_json::Value, RegoError> {
validate_policy_params(
Address::ZERO,
&Bytes::from(params.to_vec()),
schema_cid,
&StaticFetcher(SCHEMA),
)
.await
}
#[tokio::test]
async fn accepts_params_matching_the_declared_schema() {
check(br#"{"limit":5}"#, SCHEMA_CID).await.unwrap();
}
#[tokio::test]
async fn rejects_params_the_schema_does_not_admit() {
let err = check(br#"{"limit":"five"}"#, SCHEMA_CID).await.unwrap_err();
assert!(matches!(err, RegoError::FailedToValidateParamsSchema(_)));
}
#[tokio::test]
async fn rejects_empty_params_against_a_schema_with_required_properties() {
let err = check(b"", SCHEMA_CID).await.unwrap_err();
assert!(matches!(err, RegoError::FailedToValidateParamsSchema(_)));
}
#[tokio::test]
async fn rejects_params_that_are_not_json() {
let err = check(b"not json", SCHEMA_CID).await.unwrap_err();
assert!(matches!(err, RegoError::FailedToValidateParamsSchema(_)));
}
#[tokio::test]
async fn skips_validation_when_the_policy_declares_no_schema() {
check(b"anything at all", "").await.unwrap();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::newton_prover_task_manager::{INewtonPolicy, INewtonPolicyClient, NewtonMessage};
use alloy::{json_abi::StateMutability, sol, sol_types::SolCall};
use serde_json::json;
sol! {
contract MockToken {
function mint(address account, uint256 amount) public {}
function buy(address token, uint256 amount) public {}
}
}
fn create_sample_intent() -> NewtonMessage::Intent {
let buy_call = MockToken::buyCall {
token: "0x8f86403a4de0bb5791fa46b8e795c547942fe4cf".parse().unwrap(),
amount: U256::from(200000000000u64),
};
let calldata = buy_call.abi_encode();
NewtonMessage::Intent {
from: "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse().unwrap(),
to: "0x8f86403a4de0bb5791fa46b8e795c547942fe4cf".parse().unwrap(),
value: U256::from(10000000000000000u64),
data: calldata.into(),
chainId: U256::from(31337),
functionSignature: "function buy(address token, uint256 amount)".as_bytes().to_vec().into(),
}
}
fn create_sample_policy_config() -> INewtonPolicy::PolicyConfig {
INewtonPolicy::PolicyConfig {
policyParams: Bytes::from(
r#"{
"allowed_actions": {
"31337": {
"function_signature": "function buy(address,uint256)",
"address": "0x8f86403a4de0bb5791fa46b8e795c547942fe4cf",
"max_limit": 1000000000000000000
},
"11155111": {
"function_signature": "function buy(address,uint256)",
"address": "0x8f86403a4de0bb5791fa46b8e795c547942fe4cf",
"max_limit": 1000000000000000000
}
},
"token_whitelist": {
"31337": {
"symbol": "NEWT",
"address": "0x8f86403a4de0bb5791fa46b8e795c547942fe4cf",
"max_limit": 1000000000000000000
},
"11155111": {
"symbol": "WBTC",
"address": "0x29f2D40B0605204364af54EC677bD022dA425d03",
"max_limit": 1000000000000000000
}
}
}"#
.as_bytes(),
),
expireAfter: 1000,
}
}
fn create_sample_policy_spec() -> INewtonPolicyClient::PolicySpec {
INewtonPolicyClient::PolicySpec {
policy: "0xed33e3a3f077bcd7c01fe5e2c1c38d5e016c012f".parse().unwrap(),
config: create_sample_policy_config(),
}
}
fn create_sample_task() -> INewtonProverTaskManager::Task {
INewtonProverTaskManager::Task {
taskId: "0x4261fbbb3dfc2863eb06e5c271096d9d839bc9c41e9a8d181e1403baca5d9902"
.parse()
.unwrap(),
policyClient: "0xed33e3a3f077bcd7c01fe5e2c1c38d5e016c012f".parse().unwrap(),
policyId: "0x4261fbbb3dfc2863eb06e5c271096d9d839bc9c41e9a8d181e1403baca5d9902"
.parse()
.unwrap(),
policyRevision: 1,
intent: create_sample_intent(),
intentSignature: Bytes::default(),
policies: vec![create_sample_policy_spec()],
wasmArgs: vec![newton_testing_utils::policy::TEST_POLICY_WASM_ARGS.as_bytes().into()],
taskCreatedBlock: 0,
quorumNumbers: Bytes::from([0]),
quorumThresholdPercentage: 0,
initializationTimestamp: U256::ZERO,
}
}
#[test]
fn test_decode_calldata_selector_mismatch() {
let calldata: Bytes = "0x12345678".parse().unwrap(); let function_signature: Bytes = "function _doSomething()".as_bytes().to_vec().into();
let result = decode_calldata(&calldata, &function_signature);
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.to_string().contains("Function selector mismatch"));
}
#[test]
fn test_decode_calldata_comprehensive_types() {
use alloy::{
primitives::{FixedBytes, I256},
sol,
};
sol! {
contract TestContract {
function testFunction(
bool b,
uint256 u,
int256 i,
address a,
bytes32 fb,
bytes dynBytes,
string s,
uint256[] dynamicArray,
uint256[3] fixedArray,
(address, uint256) tuple
) public {}
}
}
let test_call = TestContract::testFunctionCall {
b: true,
u: U256::from(123456789),
i: I256::try_from(-987654321i64).unwrap(),
a: "0x742d35Cc6634C0532925A3B8D4C9dB96C4B4d8B6".parse().unwrap(),
fb: FixedBytes::from([0x12u8; 32]),
dynBytes: Bytes::from(vec![0xab, 0xcd, 0xef]),
s: "Hello World".to_string(),
dynamicArray: vec![U256::from(1), U256::from(2), U256::from(3)],
fixedArray: [U256::from(10), U256::from(20), U256::from(30)],
tuple: (
"0x1234567890123456789012345678901234567890".parse().unwrap(),
U256::from(999),
),
};
let calldata = test_call.abi_encode();
let function_signature: Bytes = Bytes::from("function testFunction(bool,uint256,int256,address,bytes32,bytes,string,uint256[],uint256[3],(address,uint256))".as_bytes().to_vec());
let result = decode_calldata(&Bytes::from(calldata), &function_signature);
assert!(result.is_ok());
let (_func, inputs) = result.unwrap();
assert_eq!(inputs.len(), 10);
match &inputs[0] {
DynSolValue::Bool(b) => assert!(*b),
_ => panic!("Expected bool"),
}
match &inputs[1] {
DynSolValue::Uint(u, _) => assert_eq!(*u, U256::from(123456789)),
_ => panic!("Expected uint256"),
}
match &inputs[2] {
DynSolValue::Int(i, _) => assert_eq!(*i, I256::try_from(-987654321i64).unwrap()),
_ => panic!("Expected int256"),
}
match &inputs[3] {
DynSolValue::Address(a) => assert_eq!(
*a,
"0x742d35Cc6634C0532925A3B8D4C9dB96C4B4d8B6".parse::<Address>().unwrap()
),
_ => panic!("Expected address"),
}
match &inputs[4] {
DynSolValue::FixedBytes(fb, _) => assert_eq!(fb.as_slice(), &[0x12u8; 32]),
_ => panic!("Expected bytes32"),
}
match &inputs[5] {
DynSolValue::Bytes(b) => assert_eq!(b.as_slice(), &vec![0xab, 0xcd, 0xef]),
_ => panic!("Expected bytes"),
}
match &inputs[6] {
DynSolValue::String(s) => assert_eq!(s, "Hello World"),
_ => panic!("Expected string"),
}
match &inputs[7] {
DynSolValue::Array(arr) => {
assert_eq!(arr.len(), 3);
match &arr[0] {
DynSolValue::Uint(u, _) => assert_eq!(*u, U256::from(1)),
_ => panic!("Expected uint in array"),
}
}
_ => panic!("Expected array"),
}
match &inputs[8] {
DynSolValue::FixedArray(arr) => {
assert_eq!(arr.len(), 3);
match &arr[0] {
DynSolValue::Uint(u, _) => assert_eq!(*u, U256::from(10)),
_ => panic!("Expected uint in fixed array"),
}
}
_ => panic!("Expected fixed array"),
}
match &inputs[9] {
DynSolValue::Tuple(tuple) => {
assert_eq!(tuple.len(), 2);
match &tuple[0] {
DynSolValue::Address(a) => assert_eq!(
*a,
"0x1234567890123456789012345678901234567890".parse::<Address>().unwrap()
),
_ => panic!("Expected address in tuple"),
}
match &tuple[1] {
DynSolValue::Uint(u, _) => assert_eq!(*u, U256::from(999)),
_ => panic!("Expected uint in tuple"),
}
}
_ => panic!("Expected tuple"),
}
}
#[test]
fn test_decode_calldata_primitive_types() {
use alloy::{primitives::I256, sol};
sol! {
contract PrimitiveTest {
function primitiveTest(
uint8 u8,
uint16 u16,
uint32 u32,
uint64 u64,
uint128 u128,
uint256 u256,
int8 i8,
int16 i16,
int32 i32,
int64 i64,
int128 i128,
int256 i256
) public {}
}
}
let test_call = PrimitiveTest::primitiveTestCall {
u8: 255,
u16: 65535,
u32: 4294967295,
u64: 18446744073709551615,
u128: 340282366920938463463374607431768211455u128,
u256: U256::from_str_radix(
"115792089237316195423570985008687907853269984665640564039457584007913129639935",
10,
)
.unwrap(),
i8: -128,
i16: -32768,
i32: -2147483648,
i64: -9223372036854775808,
i128: -170141183460469231731687303715884105727i128,
i256: I256::try_from(-170141183460469231731687303715884105727i128).unwrap(),
};
let calldata = test_call.abi_encode();
let function_signature: Bytes = Bytes::from(
"function primitiveTest(uint8,uint16,uint32,uint64,uint128,uint256,int8,int16,int32,int64,int128,int256)"
.as_bytes()
.to_vec(),
);
let result = decode_calldata(&Bytes::from(calldata), &function_signature);
assert!(result.is_ok());
let (_, inputs) = result.unwrap();
assert_eq!(inputs.len(), 12);
for (i, input) in inputs.iter().enumerate() {
match input {
DynSolValue::Uint(_, bits) | DynSolValue::Int(_, bits) => {
assert!(*bits > 0 && *bits <= 256);
}
_ => panic!("Expected numeric type at index {}", i),
}
}
}
#[test]
fn test_parse_intent() {
let intent = json!(create_sample_intent());
let parsed_intent = parse_intent(intent).unwrap();
assert_eq!(
parsed_intent.from,
"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse::<Address>().unwrap()
);
assert_eq!(
parsed_intent.to,
"0x8f86403a4de0bb5791fa46b8e795c547942fe4cf".parse::<Address>().unwrap()
);
assert_eq!(parsed_intent.value, U256::from(10000000000000000u64)); assert_eq!(parsed_intent.chain_id, Some(ChainId::from(0x7a69u64)));
assert_eq!(
parsed_intent.decoded_function_signature,
Some("function buy(address token, uint256 amount)".to_string())
);
assert_eq!(parsed_intent.decoded_function_arguments.as_ref().unwrap().len(), 2);
}
#[test]
fn test_parse_intent_missing_fields() {
let incomplete_json = json!({
"from": "0xb9e89063d40f95bf2aac0c06777764d7378ead10",
"to": "0x2e2ed0cfd3ad2f1d34481277b3204d807ca2f8c2"
});
let result = parse_intent(incomplete_json);
assert!(result.is_err());
}
#[test]
fn test_parse_intent_optional_chain_id() {
let json_without_chain_id = json!({
"from": "0xb9e89063d40f95bf2aac0c06777764d7378ead10",
"to": "0x2e2ed0cfd3ad2f1d34481277b3204d807ca2f8c2",
"value": "0x0",
"data": "0x6b2305ce",
});
let result = parse_intent(json_without_chain_id);
assert!(result.is_ok());
let parsed = result.unwrap();
assert_eq!(parsed.chain_id, None);
let json_invalid_hex_chain_id = json!({
"from": "0xb9e89063d40f95bf2aac0c06777764d7378ead10",
"to": "0x2e2ed0cfd3ad2f1d34481277b3204d807ca2f8c2",
"value": "0x0",
"data": "0x6b2305ce",
"chainId": "0xGGGG"
});
let result = parse_intent(json_invalid_hex_chain_id);
assert!(result.is_ok());
let parsed = result.unwrap();
assert_eq!(parsed.chain_id, None);
let json_invalid_decimal_chain_id = json!({
"from": "0xb9e89063d40f95bf2aac0c06777764d7378ead10",
"to": "0x2e2ed0cfd3ad2f1d34481277b3204d807ca2f8c2",
"value": "0x0",
"data": "0x6b2305ce",
"chainId": "not_a_number"
});
let result = parse_intent(json_invalid_decimal_chain_id);
assert!(result.is_ok());
let parsed = result.unwrap();
assert_eq!(parsed.chain_id, None);
let json_valid_hex_chain_id = json!({
"from": "0xb9e89063d40f95bf2aac0c06777764d7378ead10",
"to": "0x2e2ed0cfd3ad2f1d34481277b3204d807ca2f8c2",
"value": "0x0",
"data": "0x6b2305ce",
"chainId": "0x7a69"
});
let result = parse_intent(json_valid_hex_chain_id);
assert!(result.is_ok());
let parsed = result.unwrap();
assert_eq!(parsed.chain_id, Some(31337u64));
let json_valid_decimal_chain_id = json!({
"from": "0xb9e89063d40f95bf2aac0c06777764d7378ead10",
"to": "0x2e2ed0cfd3ad2f1d34481277b3204d807ca2f8c2",
"value": "0x0",
"data": "0x6b2305ce",
"chainId": "1"
});
let result = parse_intent(json_valid_decimal_chain_id);
assert!(result.is_ok());
let parsed = result.unwrap();
assert_eq!(parsed.chain_id, Some(1u64));
}
#[test]
fn test_parse_intent_optional_function_signature() {
let json_without_sig = json!({
"from": "0xb9e89063d40f95bf2aac0c06777764d7378ead10",
"to": "0x2e2ed0cfd3ad2f1d34481277b3204d807ca2f8c2",
"value": "0x0",
"data": "0x6b2305ce",
"chainId": "1"
});
let result = parse_intent(json_without_sig);
assert!(result.is_ok());
let parsed = result.unwrap();
assert_eq!(parsed.function_signature, None);
assert_eq!(parsed.decoded_function_signature, None);
assert_eq!(parsed.decoded_function_arguments, None);
assert_eq!(parsed.function, None);
let json_with_sig_no_data = json!({
"from": "0xb9e89063d40f95bf2aac0c06777764d7378ead10",
"to": "0x2e2ed0cfd3ad2f1d34481277b3204d807ca2f8c2",
"value": "0x0",
"chainId": "1",
"functionSignature": "function doSomething()"
});
let result = parse_intent(json_with_sig_no_data);
assert!(result.is_ok());
let parsed = result.unwrap();
assert!(parsed.function_signature.is_some());
assert_eq!(parsed.data, None);
assert_eq!(parsed.decoded_function_signature, None);
}
#[test]
fn test_parse_intent_optional_data() {
let json_without_data = json!({
"from": "0xb9e89063d40f95bf2aac0c06777764d7378ead10",
"to": "0x2e2ed0cfd3ad2f1d34481277b3204d807ca2f8c2",
"value": "0x0",
"chainId": "1"
});
let result = parse_intent(json_without_data);
assert!(result.is_ok());
let parsed = result.unwrap();
assert_eq!(parsed.data, None);
let json_with_data = json!({
"from": "0xb9e89063d40f95bf2aac0c06777764d7378ead10",
"to": "0x2e2ed0cfd3ad2f1d34481277b3204d807ca2f8c2",
"value": "0x0",
"data": "0x6b2305ce",
"chainId": "1"
});
let result = parse_intent(json_with_data);
assert!(result.is_ok());
let parsed = result.unwrap();
assert!(parsed.data.is_some());
assert_eq!(parsed.data.unwrap(), Bytes::from(vec![0x6b, 0x23, 0x05, 0xce]));
}
#[test]
fn test_parse_intent_minimal_valid() {
let minimal_json = json!({
"from": "0xb9e89063d40f95bf2aac0c06777764d7378ead10",
"to": "0x2e2ed0cfd3ad2f1d34481277b3204d807ca2f8c2",
"value": "1000000000000000000"
});
let result = parse_intent(minimal_json);
assert!(result.is_ok());
let parsed = result.unwrap();
assert_eq!(
parsed.from,
"0xb9e89063d40f95bf2aac0c06777764d7378ead10".parse::<Address>().unwrap()
);
assert_eq!(
parsed.to,
"0x2e2ed0cfd3ad2f1d34481277b3204d807ca2f8c2".parse::<Address>().unwrap()
);
assert_eq!(parsed.value, U256::from(1000000000000000000u64));
assert_eq!(parsed.chain_id, None);
assert_eq!(parsed.data, None);
assert_eq!(parsed.function_signature, None);
assert_eq!(parsed.decoded_function_signature, None);
assert_eq!(parsed.decoded_function_arguments, None);
assert_eq!(parsed.function, None);
}
#[test]
fn test_parse_intent_full_valid() {
let buy_call = MockToken::buyCall {
token: "0x8f86403a4de0bb5791fa46b8e795c547942fe4cf".parse().unwrap(),
amount: U256::from(200000000000u64),
};
let calldata = buy_call.abi_encode();
let full_json = json!({
"from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"to": "0x8f86403a4de0bb5791fa46b8e795c547942fe4cf",
"value": "10000000000000000",
"data": format!("0x{}", crate::hex!(&calldata)),
"chainId": "0x7a69",
"functionSignature": "function buy(address token, uint256 amount)"
});
let result = parse_intent(full_json);
assert!(result.is_ok());
let parsed = result.unwrap();
assert!(parsed.chain_id.is_some());
assert_eq!(parsed.chain_id.unwrap(), 31337u64);
assert!(parsed.data.is_some());
assert!(parsed.function_signature.is_some());
assert!(parsed.decoded_function_signature.is_some());
assert_eq!(
parsed.decoded_function_signature.unwrap(),
"function buy(address token, uint256 amount)"
);
assert!(parsed.decoded_function_arguments.is_some());
assert_eq!(parsed.decoded_function_arguments.as_ref().unwrap().len(), 2);
assert!(parsed.function.is_some());
}
#[test]
fn test_parse_intent_invalid_address() {
let invalid_json = json!({
"from": "0x123456789012345678901234567890123456789012", "to": "0x2e2ed0cfd3ad2f1d34481277b3204d807ca2f8c2",
"value": "0x0",
"data": "0x6b2305ce",
"chainId": "0x7a69",
"functionSignature": "0x66756e6374696f6e205f646f536f6d657468696e672829"
});
let result = parse_intent(invalid_json);
assert!(result.is_err());
}
#[test]
fn test_serialize_sol_value() {
let bool_val = DynSolValue::Bool(true);
let serialized_bool = serialize_sol_value(&bool_val);
assert_eq!(serialized_bool, serde_json::Value::Bool(true));
let uint_val = DynSolValue::Uint(U256::from(123), 256);
let serialized_uint = serialize_sol_value(&uint_val);
assert_eq!(serialized_uint, serde_json::Value::String("123".to_string()));
let address_val = DynSolValue::Address("0x742d35Cc6634C0532925A3B8D4C9dB96C4B4d8B6".parse().unwrap());
let serialized_address = serialize_sol_value(&address_val);
assert_eq!(
serialized_address,
serde_json::Value::String("0x742d35cc6634c0532925a3b8d4c9db96c4b4d8b6".to_string())
);
let bytes_val = DynSolValue::Bytes(vec![0x12, 0x34, 0x56]);
let serialized_bytes = serialize_sol_value(&bytes_val);
assert_eq!(serialized_bytes, serde_json::Value::String("0x123456".to_string()));
let string_val = DynSolValue::String("Hello World".to_string());
let serialized_string = serialize_sol_value(&string_val);
assert_eq!(serialized_string, serde_json::Value::String("Hello World".to_string()));
use alloy::primitives::FixedBytes;
let mut fixed_bytes_array = [0u8; 32];
fixed_bytes_array[0] = 0x12;
fixed_bytes_array[1] = 0x34;
let fixed_bytes_val = DynSolValue::FixedBytes(FixedBytes::from(fixed_bytes_array), 32);
let serialized_fixed_bytes = serialize_sol_value(&fixed_bytes_val);
assert_eq!(
serialized_fixed_bytes,
serde_json::Value::String("0x1234000000000000000000000000000000000000000000000000000000000000".to_string())
);
let mut zero_tail_array = [0u8; 32];
zero_tail_array[0] = 0xaa;
zero_tail_array[1] = 0xbb;
let zero_tail_val = DynSolValue::FixedBytes(FixedBytes::from(zero_tail_array), 32);
let serialized_zero_tail = serialize_sol_value(&zero_tail_val);
assert_eq!(
serialized_zero_tail,
serde_json::Value::String("0xaabb000000000000000000000000000000000000000000000000000000000000".to_string()),
"bytes32 with trailing zeros must serialize to full 64-nibble hex"
);
let all_zero_array = [0u8; 32];
let all_zero_val = DynSolValue::FixedBytes(FixedBytes::from(all_zero_array), 32);
let serialized_all_zero = serialize_sol_value(&all_zero_val);
assert_eq!(
serialized_all_zero,
serde_json::Value::String("0x0000000000000000000000000000000000000000000000000000000000000000".to_string()),
"all-zero bytes32 must serialize to full 64-nibble hex, not '0x'"
);
let array_val = DynSolValue::Array(vec![
DynSolValue::Uint(U256::from(1), 256),
DynSolValue::Uint(U256::from(2), 256),
DynSolValue::Uint(U256::from(3), 256),
]);
let serialized_array = serialize_sol_value(&array_val);
let expected_array = serde_json::json!(["1", "2", "3"]);
assert_eq!(serialized_array, expected_array);
let tuple_val = DynSolValue::Tuple(vec![
DynSolValue::Address("0x742d35Cc6634C0532925A3B8D4C9dB96C4B4d8B6".parse().unwrap()),
DynSolValue::Uint(U256::from(1000), 256),
]);
let serialized_tuple = serialize_sol_value(&tuple_val);
let expected_tuple = serde_json::json!(["0x742d35cc6634c0532925a3b8d4c9db96c4b4d8b6", "1000"]);
assert_eq!(serialized_tuple, expected_tuple);
}
#[tokio::test]
#[cfg(feature = "rpc")]
async fn test_parse_and_evaluate_policy_set_success() {
let task = create_sample_task();
let intent_json = json!(task.intent);
let parsed_intent = parse_intent(intent_json).unwrap();
assert_eq!(
parsed_intent.from,
"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse::<Address>().unwrap()
);
assert_eq!(
parsed_intent.to,
"0x8f86403a4de0bb5791fa46b8e795c547942fe4cf".parse::<Address>().unwrap()
);
assert_eq!(parsed_intent.value, U256::from(10000000000000000u64));
assert_eq!(
parsed_intent.decoded_function_signature,
Some("function buy(address token, uint256 amount)".to_string())
);
let args = parsed_intent.decoded_function_arguments.as_ref().unwrap();
assert_eq!(args.len(), 2);
assert_eq!(args[0], "0x8f86403a4de0bb5791fa46b8e795c547942fe4cf");
assert_eq!(args[1], "200000000000");
let func = parsed_intent.function.as_ref().unwrap();
assert_eq!(func.name, "buy");
assert_eq!(func.inputs.len(), 2);
assert_eq!(func.inputs[0].ty, "address");
assert_eq!(func.inputs[1].ty, "uint256");
assert_eq!(func.outputs.len(), 0);
assert_eq!(func.state_mutability, StateMutability::NonPayable);
}
#[test]
fn test_intent_round_trip_alloy_serde_to_parsed_intent() {
use crate::common::intent::RawParsedIntent;
let intent = Intent {
from: "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse().unwrap(),
to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8".parse().unwrap(),
value: U256::from(1000000000000000000u64), data: Bytes::new(), chainId: U256::ZERO, functionSignature: Bytes::new(), };
let intent_json = serde_json::json!(intent);
let parsed_intent = parse_intent(intent_json).expect("should parse empty-calldata intent");
let final_json_str: String = parsed_intent.clone().into(); let final_json: serde_json::Value =
serde_json::from_str(&final_json_str).expect("ParsedIntent JSON should be valid");
assert_eq!(final_json["data"], "0x", "empty Bytes must serialize as '0x', not null");
assert_eq!(
final_json["chain_id"], "0",
"zero ChainId must serialize as '0', not null"
);
assert_eq!(
final_json["function_signature"], "0x",
"empty Bytes must serialize as '0x', not null"
);
let intent_with_data = Intent {
from: "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse().unwrap(),
to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8".parse().unwrap(),
value: U256::from(1000000000000000000u64),
data: Bytes::from(vec![0x6b, 0x23, 0x05, 0xce]),
chainId: U256::from(1u64),
functionSignature: Bytes::from("function transfer(address,uint256)".as_bytes()),
};
let intent_json = serde_json::json!(intent_with_data);
let parsed_intent = parse_intent(intent_json).expect("should parse intent with data");
let final_json_str: String = parsed_intent.clone().into();
let final_json: serde_json::Value =
serde_json::from_str(&final_json_str).expect("ParsedIntent JSON should be valid");
assert_eq!(final_json["data"], "0x6b2305ce");
assert_eq!(final_json["chain_id"], "1"); assert!(final_json["function_signature"].as_str().unwrap().starts_with("0x"));
}
#[tokio::test]
#[cfg(feature = "rpc")]
async fn test_parse_and_evaluate_policy_set_invalid_intent() {
use alloy::primitives::Bytes;
let mut task = create_sample_task();
task.intent.data = Bytes::from("invalid_data".as_bytes());
let intent_json = json!(task.intent);
let result = parse_intent(intent_json);
assert!(result.is_ok());
let parsed = result.unwrap();
assert!(parsed.data.is_some());
assert!(parsed.decoded_function_signature.is_none());
assert!(parsed.decoded_function_arguments.is_none());
assert!(parsed.function.is_none());
}
#[tokio::test]
async fn test_evaluate_basic_policy() {
use crate::evaluate;
let policy = newton_testing_utils::policy::TEST_POLICY_REGO;
let policy_params_and_data = newton_testing_utils::policy::TEST_POLICY_DATA;
let parsed_intent = newton_testing_utils::policy::TEST_POLICY_PARSED_INTENT;
let policy_rule = "data.basic.allow";
let result = evaluate(
policy.to_string(),
policy_params_and_data,
parsed_intent,
&[],
policy_rule,
None,
);
assert!(result.is_ok());
let evaluation_result = result.unwrap();
match evaluation_result {
regorus::Value::Bool(result) => {
assert!(result, "Policy evaluation failed");
}
_ => panic!("Expected boolean result, got: {}", evaluation_result),
}
}
#[test]
fn merge_secrets_schemas_unions() {
let schema_a = json!({
"type": "object",
"properties": {
"COIN_GECKO_API": { "type": "string", "minLength": 1 }
},
"required": ["COIN_GECKO_API"],
"additionalProperties": false
});
let schema_b = json!({
"type": "object",
"properties": {
"COIN_GECKO_API": { "type": "string", "minLength": 999 },
"WEATHER_API": { "type": "string", "minLength": 1 }
},
"required": ["WEATHER_API"]
});
let merged = merge_secrets_schemas(vec![("cid_a".to_string(), schema_a), ("cid_b".to_string(), schema_b)])
.expect("merge");
let props = merged
.get("properties")
.and_then(|v| v.as_object())
.expect("properties object");
assert!(props.contains_key("COIN_GECKO_API"));
assert!(props.contains_key("WEATHER_API"));
assert_eq!(
props.get("COIN_GECKO_API").unwrap().get("minLength").unwrap(),
&json!(1)
);
let required = merged
.get("required")
.and_then(|v| v.as_array())
.expect("required array");
assert!(required.contains(&json!("COIN_GECKO_API")));
assert!(required.contains(&json!("WEATHER_API")));
assert!(merged.get("additionalProperties").is_none());
}
#[test]
fn merge_secrets_schemas_ignores_additional_properties_false() {
let schema_a = json!({
"type": "object",
"properties": { "A": { "type": "string" } },
"additionalProperties": true
});
let schema_b = json!({
"type": "object",
"properties": { "B": { "type": "string" } },
"additionalProperties": false
});
let merged = merge_secrets_schemas(vec![("cid_a".to_string(), schema_a), ("cid_b".to_string(), schema_b)])
.expect("merge");
assert!(merged.get("additionalProperties").is_none());
}
#[test]
fn merge_secrets_schemas_ignores_non_object_schema() {
let merged = merge_secrets_schemas(vec![("cid_bad".to_string(), json!(["nope"]))]).expect("merge");
assert!(merged.get("additionalProperties").is_none());
let props = merged
.get("properties")
.and_then(|v| v.as_object())
.expect("properties object");
assert!(props.is_empty());
}
#[test]
fn merge_secrets_schemas_ignores_invalid_properties_shape() {
let schema = json!({
"type": "object",
"properties": ["not", "an", "object"]
});
let merged = merge_secrets_schemas(vec![("cid_bad".to_string(), schema)]).expect("merge");
assert!(merged.get("additionalProperties").is_none());
let props = merged
.get("properties")
.and_then(|v| v.as_object())
.expect("properties object");
assert!(props.is_empty());
}
#[test]
fn merge_secrets_schemas_ignores_invalid_required() {
let schema_not_array = json!({
"type": "object",
"properties": {},
"required": "NOPE"
});
let merged = merge_secrets_schemas(vec![("cid_bad".to_string(), schema_not_array)]).expect("merge");
assert!(merged.get("additionalProperties").is_none());
assert!(merged.get("required").is_none());
let schema_non_string = json!({
"type": "object",
"properties": {},
"required": ["OK", 123]
});
let merged = merge_secrets_schemas(vec![("cid_bad2".to_string(), schema_non_string)]).expect("merge");
assert!(merged.get("additionalProperties").is_none());
assert!(merged.get("required").is_none());
}
#[test]
fn task_request_proof_cid_serialization() {
use serde_json;
let request = TaskRequest {
task_id: B256::ZERO,
intent: NewtonMessage::Intent {
from: Address::ZERO,
to: Address::ZERO,
value: U256::ZERO,
data: Bytes::default(),
chainId: U256::from(1),
functionSignature: Bytes::default(),
},
intent_signature: None,
policy_client: Address::ZERO,
policy_id: B256::ZERO,
policies: vec![create_sample_policy_spec()],
policy_revision: 1,
wasm_args: vec![],
quorum_numbers: vec![0],
quorum_threshold_percentage: 40,
task_created_block: 100,
proof_cid: Some("bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi".to_string()),
initialization_timestamp: 0,
};
let json = serde_json::to_value(&request).unwrap();
assert_eq!(
json["proof_cid"],
"bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"
);
let request_no_proof = TaskRequest {
proof_cid: None,
..request
};
let json2 = serde_json::to_value(&request_no_proof).unwrap();
assert!(json2.get("proof_cid").is_none());
let mut json3 = json.clone();
json3.as_object_mut().unwrap().remove("proof_cid");
let deserialized: TaskRequest = serde_json::from_value(json3).unwrap();
assert!(deserialized.proof_cid.is_none());
let mut json4 = json.clone();
json4
.as_object_mut()
.unwrap()
.insert("proofCid".to_string(), serde_json::json!("bafycamelcase"));
json4.as_object_mut().unwrap().remove("proof_cid");
let deserialized_camel: TaskRequest = serde_json::from_value(json4).unwrap();
assert_eq!(deserialized_camel.proof_cid.as_deref(), Some("bafycamelcase"));
}
#[test]
fn tls_proof_data_injected_into_rego_root_namespace() {
let tls_proof = serde_json::json!({
"server_name": "api.twitter.com",
"verified": true,
"response_body": "{\"id\":\"123\",\"name\":\"test\"}",
"request_target": "/2/users/me"
});
let policy_data = serde_json::json!({
"params": {},
"wasm": { "some_key": "some_value" },
"tls_proof": tls_proof,
});
assert_eq!(policy_data["tls_proof"]["server_name"], "api.twitter.com");
assert_eq!(policy_data["tls_proof"]["verified"], true);
assert_eq!(policy_data["wasm"]["some_key"], "some_value");
}
}