use core::marker::PhantomData;
use alloy_sol_types::SolCall;
use tronz_primitives::Trx;
use tronz_provider::{PendingTransaction, TronProvider};
use crate::{
call::CallBuilder,
error::{ContractError, Result},
};
#[must_use = "a TronCallBuilder does nothing until `.call()` or `.send()` is awaited"]
pub struct TronCallBuilder<P, C> {
inner: CallBuilder<P>,
_call: PhantomData<fn() -> C>,
}
impl<P: TronProvider, C: SolCall> TronCallBuilder<P, C> {
#[inline]
pub fn new(inner: CallBuilder<P>) -> Self {
Self { inner, _call: PhantomData }
}
#[inline]
pub fn value(self, trx: Trx) -> Self {
Self { inner: self.inner.value(trx), _call: PhantomData }
}
#[inline]
pub fn token(self, token_id: i64, value: Trx) -> Self {
Self { inner: self.inner.token(token_id, value), _call: PhantomData }
}
pub async fn estimate_energy(&self) -> Result<i64> {
self.inner.estimate_energy().await
}
pub async fn call(self) -> Result<C::Return> {
let output = self.inner.call().await?;
let fn_name = C::SIGNATURE.split('(').next().unwrap_or(C::SIGNATURE);
C::abi_decode_returns_validate(&output)
.map_err(|e| ContractError::decode_err(fn_name, &output, e.into()))
}
pub async fn send(self) -> Result<PendingTransaction<P>> {
self.inner.send().await
}
}
#[cfg(test)]
mod tests {
use alloy_primitives::U256;
use alloy_sol_types::sol;
use tronz_primitives::{Address, Bytes};
use tronz_provider::{RootProvider, transport::mock::MockTransport, types::ConstantCallResult};
use super::*;
use crate::{call::CallBuilder, error::ContractError};
sol! {
function balanceOf(address) external view returns (uint256);
}
fn addr() -> Address {
Address::from_evm_bytes([1u8; 20])
}
fn make_builder(
provider: RootProvider<MockTransport>,
) -> TronCallBuilder<RootProvider<MockTransport>, balanceOfCall> {
TronCallBuilder::new(CallBuilder::new(provider, addr(), Bytes::new()))
}
fn canned(output: Vec<u8>) -> ConstantCallResult {
ConstantCallResult { output, energy_used: 0, revert_reason: None }
}
#[tokio::test]
async fn empty_output_yields_zero_data_error() {
let provider = RootProvider::new(MockTransport::new());
provider.transport().push_ok("trigger_constant_contract", canned(vec![]));
let err = make_builder(provider).call().await.unwrap_err();
assert!(
matches!(&err, ContractError::ZeroData(name, _) if name == "balanceOf"),
"expected ZeroData(\"balanceOf\", _), got {err:?}"
);
}
#[tokio::test]
async fn valid_output_decodes_correctly() {
let provider = RootProvider::new(MockTransport::new());
let mut out = [0u8; 32];
out[31] = 99;
provider.transport().push_ok("trigger_constant_contract", canned(out.to_vec()));
let value = make_builder(provider).call().await.unwrap();
assert_eq!(value, U256::from(99u64));
}
#[tokio::test]
async fn truncated_output_yields_abi_error() {
let provider = RootProvider::new(MockTransport::new());
provider.transport().push_ok("trigger_constant_contract", canned(vec![0xde, 0xad]));
let err = make_builder(provider).call().await.unwrap_err();
assert!(matches!(err, ContractError::Abi(_)), "expected Abi(_), got {err:?}");
}
}