Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ink_e2e] improve call API, remove build_message + callback #1782

Merged
merged 30 commits into from
May 19, 2023
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f773699
Cherry picking changes from #1669
ascjones May 12, 2023
4693132
Don't display verbose Debug message for module error.
ascjones May 12, 2023
ad372d1
Initial conversion of erc20 to use new call
ascjones May 12, 2023
d4c9570
Remove constraint from InstantiationResult
ascjones May 12, 2023
ece5722
Merge branch 'master' into aj/e2e-improve-call
ascjones May 17, 2023
e733996
Use 3.0.0 release
ascjones May 17, 2023
ffc218f
Revert "Use 3.0.0 release"
ascjones May 17, 2023
feaa89b
Move instantiate generic parameter
ascjones May 17, 2023
134d976
Merge branch 'master' into aj/e2e-improve-call
ascjones May 17, 2023
d752a4d
Update call-runtime example
ascjones May 17, 2023
1072175
Update contract-terminate tests
ascjones May 17, 2023
580141a
Update contract-transfer tests
ascjones May 17, 2023
1bab0c7
Custom allocator
ascjones May 18, 2023
604053a
Custom environment
ascjones May 18, 2023
5734729
e2e-call-runtime
ascjones May 18, 2023
4d8da7f
flipper
ascjones May 18, 2023
97ac9f8
lang-err-integration-tests, compiles but fails
ascjones May 18, 2023
748103f
Don't debug log the entire dispatch error
ascjones May 18, 2023
21b898b
constructor-return-value
ascjones May 18, 2023
5569857
contract-ref
ascjones May 18, 2023
036dd5f
integration-flipper
ascjones May 18, 2023
f5c1496
mapping-integration-tests
ascjones May 18, 2023
9b181d3
multi-contract-caller
ascjones May 18, 2023
0197363
set-code-hash
ascjones May 18, 2023
46a5c02
wildcard-selector
ascjones May 18, 2023
1d4846e
Merge branch 'master' into aj/e2e-improve-call
ascjones May 18, 2023
867da3b
trait-dyn-cross-contract-calls
ascjones May 18, 2023
4570150
Clippy
ascjones May 18, 2023
0611141
Export CallBuilderFinal type alias
ascjones May 19, 2023
5087d30
CHANGELOG.md
ascjones May 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 0 additions & 132 deletions crates/e2e/src/builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use ink::codegen::TraitCallBuilder;
use ink_env::{
call::{
utils::{
ReturnType,
Set,
Unset,
},
Call,
CallBuilder,
CreateBuilder,
ExecutionInput,
FromAccountId,
},
Environment,
};
Expand Down Expand Up @@ -59,131 +55,3 @@ where
.exec_input()
.encode()
}

/// Captures the encoded input for an `ink!` message call, together with the account id of
/// the contract being called.
#[derive(Debug, Clone)]
pub struct Message<E: Environment, RetType> {
account_id: E::AccountId,
exec_input: Vec<u8>,
_return_type: std::marker::PhantomData<RetType>,
}

impl<E, RetType> Message<E, RetType>
where
E: Environment,
{
/// Create a new message from the given account id and encoded message data.
pub fn new(account_id: E::AccountId, exec_input: Vec<u8>) -> Self {
Self {
account_id,
exec_input,
_return_type: Default::default(),
}
}

/// The account id of the contract being called to invoke the message.
pub fn account_id(&self) -> &E::AccountId {
&self.account_id
}

/// The encoded message data, comprised of the selector and the message arguments.
pub fn exec_input(&self) -> &[u8] {
&self.exec_input
}
}

/// Convenience method for building messages for the default environment.
///
/// # Note
///
/// This is hardcoded to [`ink_env::DefaultEnvironment`] so the user does not have to
/// specify this generic parameter, which currently is hardcoded in the E2E testing suite.
pub fn build_message<Ref>(
account_id: <ink_env::DefaultEnvironment as Environment>::AccountId,
) -> MessageBuilder<ink_env::DefaultEnvironment, Ref>
where
Ref: TraitCallBuilder + FromAccountId<ink_env::DefaultEnvironment>,
{
MessageBuilder::from_account_id(account_id)
}

/// Build messages using a contract ref.
pub struct MessageBuilder<E: Environment, Ref> {
account_id: E::AccountId,
contract_ref: Ref,
}

impl<E, Ref> MessageBuilder<E, Ref>
where
E: Environment,
Ref: TraitCallBuilder + FromAccountId<E>,
{
/// Create a new [`MessageBuilder`] to invoke a message on the given contract.
pub fn from_account_id(account_id: E::AccountId) -> Self {
let contract_ref = <Ref as FromAccountId<E>>::from_account_id(account_id.clone());
Self {
account_id,
contract_ref,
}
}

/// Build an encoded call for a message from a [`CallBuilder`] instance returned from
/// a contract ref method.
///
/// This utilizes the generated message inherent methods on the contract ref
/// implementation, which returns a [`CallBuilder`] initialized with the selector
/// and message arguments.
///
/// # Example
/// ```
/// # #[ink::contract]
/// # pub mod my_contract {
/// #
/// # #[ink(storage)]
/// # pub struct MyContract { }
/// #
/// # impl MyContract {
/// # #[ink(constructor)]
/// # pub fn new() -> Self {
/// # Self {}
/// # }
/// #
/// # #[ink(message)]
/// # pub fn message(&self) {}
/// # }
/// # }
/// #
/// # fn message_builder_doc_test() {
/// # use my_contract::MyContractRef;
/// # let contract_acc_id = ink_primitives::AccountId::from([0x00; 32]);
/// ink_e2e::MessageBuilder::<ink::env::DefaultEnvironment, MyContractRef>::from_account_id(
/// contract_acc_id,
/// )
/// .call(|contract| contract.message());
/// # }
/// ```

pub fn call<F, Args, RetType>(mut self, mut message: F) -> Message<E, RetType>
where
F: FnMut(
&mut <Ref as TraitCallBuilder>::Builder,
) -> CallBuilder<
E,
Set<Call<E>>,
Set<ExecutionInput<Args>>,
Set<ReturnType<RetType>>,
>,
Args: scale::Encode,
RetType: scale::Decode,
{
let call_builder = <Ref as TraitCallBuilder>::call_mut(&mut self.contract_ref);
let builder = message(call_builder);
let exec_input = builder.params().exec_input().encode();
Message {
account_id: self.account_id.clone(),
exec_input,
_return_type: Default::default(),
}
}
}
97 changes: 74 additions & 23 deletions crates/e2e/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ use super::{
builders::{
constructor_exec_input,
CreateBuilderPartial,
Message,
},
log_error,
log_info,
Expand All @@ -27,7 +26,19 @@ use super::{
ContractsApi,
Signer,
};
use ink_env::Environment;
use ink::codegen::ContractCallBuilder;
use ink_env::{
call::{
utils::{
ReturnType,
Set,
},
Call,
ExecutionInput,
FromAccountId,
},
Environment,
};
use ink_primitives::MessageResult;
use pallet_contracts_primitives::ExecReturnValue;
use sp_core::Pair;
Expand Down Expand Up @@ -55,6 +66,13 @@ use subxt::{
tx::PairSigner,
};

type CallBuilder<E, Args, RetType> = ink_env::call::CallBuilder<
E,
Set<Call<E>>,
Set<ExecutionInput<Args>>,
Set<ReturnType<RetType>>,
>;

/// Result of a contract instantiation.
pub struct InstantiationResult<C: subxt::Config, E: Environment> {
/// The account id at which the contract was instantiated.
Expand All @@ -66,6 +84,22 @@ pub struct InstantiationResult<C: subxt::Config, E: Environment> {
pub events: ExtrinsicEvents<C>,
}

impl<C, E> InstantiationResult<C, E>
where
C: subxt::Config,
E: Environment,
{
pub fn call<Contract>(&self) -> <Contract as ContractCallBuilder>::Type
where
Contract: ContractCallBuilder,
<Contract as ContractCallBuilder>::Type: FromAccountId<E>,
{
<<Contract as ContractCallBuilder>::Type as FromAccountId<E>>::from_account_id(
self.account_id.clone(),
)
}
}

/// Result of a contract upload.
pub struct UploadResult<C: subxt::Config, E: Environment> {
/// The hash with which the contract can be instantiated.
Expand Down Expand Up @@ -465,11 +499,11 @@ where
/// Calling this function multiple times is idempotent, the contract is
/// newly instantiated each time using a unique salt. No existing contract
/// instance is reused!
pub async fn instantiate<ContractRef, Args, R>(
pub async fn instantiate<Contract, Args, R>(
&mut self,
contract_name: &str,
signer: &Signer<C>,
constructor: CreateBuilderPartial<E, ContractRef, Args, R>,
constructor: CreateBuilderPartial<E, Contract, Args, R>,
value: E::Balance,
storage_deposit_limit: Option<E::Balance>,
) -> Result<InstantiationResult<C, E>, Error<C, E>>
Expand All @@ -478,18 +512,24 @@ where
{
let code = self.load_code(contract_name);
let ret = self
.exec_instantiate(signer, code, constructor, value, storage_deposit_limit)
.exec_instantiate::<Contract, Args, R>(
signer,
code,
constructor,
value,
storage_deposit_limit,
)
.await?;
log_info(&format!("instantiated contract at {:?}", ret.account_id));
Ok(ret)
}

/// Dry run contract instantiation using the given constructor.
pub async fn instantiate_dry_run<ContractRef, Args, R>(
pub async fn instantiate_dry_run<Contract, Args, R>(
&mut self,
contract_name: &str,
signer: &Signer<C>,
constructor: CreateBuilderPartial<E, ContractRef, Args, R>,
constructor: CreateBuilderPartial<E, Contract, Args, R>,
value: E::Balance,
storage_deposit_limit: Option<E::Balance>,
) -> ContractInstantiateResult<C::AccountId, E::Balance>
Expand Down Expand Up @@ -533,11 +573,11 @@ where
}

/// Executes an `instantiate_with_code` call and captures the resulting events.
async fn exec_instantiate<ContractRef, Args, R>(
async fn exec_instantiate<Contract, Args, R>(
&mut self,
signer: &Signer<C>,
code: Vec<u8>,
constructor: CreateBuilderPartial<E, ContractRef, Args, R>,
constructor: CreateBuilderPartial<E, Contract, Args, R>,
value: E::Balance,
storage_deposit_limit: Option<E::Balance>,
) -> Result<InstantiationResult<C, E>, Error<C, E>>
Expand Down Expand Up @@ -608,17 +648,18 @@ where
subxt::error::DispatchError::decode_from(evt.field_bytes(), metadata)
.map_err(Error::Decoding)?;
log_error(&format!(
"extrinsic for instantiate failed: {dispatch_error:?}"
"extrinsic for instantiate failed: {dispatch_error}"
));
return Err(Error::InstantiateExtrinsic(dispatch_error))
}
}
let account_id = account_id.expect("cannot extract `account_id` from events");

Ok(InstantiationResult {
dry_run,
// The `account_id` must exist at this point. If the instantiation fails
// the dry-run must already return that.
account_id: account_id.expect("cannot extract `account_id` from events"),
account_id,
events: tx_events,
})
}
Expand Down Expand Up @@ -699,7 +740,7 @@ where
subxt::error::DispatchError::decode_from(evt.field_bytes(), metadata)
.map_err(Error::Decoding)?;

log_error(&format!("extrinsic for upload failed: {dispatch_error:?}"));
log_error(&format!("extrinsic for upload failed: {dispatch_error}"));
return Err(Error::UploadExtrinsic(dispatch_error))
}
}
Expand Down Expand Up @@ -729,19 +770,23 @@ where
///
/// Returns when the transaction is included in a block. The return value
/// contains all events that are associated with this transaction.
pub async fn call<RetType>(
pub async fn call<Args, RetType>(
&mut self,
signer: &Signer<C>,
message: Message<E, RetType>,
message: &CallBuilder<E, Args, RetType>,
value: E::Balance,
storage_deposit_limit: Option<E::Balance>,
) -> Result<CallResult<C, E, RetType>, Error<C, E>>
where
Args: scale::Encode,
RetType: scale::Decode,
CallBuilder<E, Args, RetType>: Clone,
{
log_info(&format!("call: {:02X?}", message.exec_input()));
let account_id = message.clone().params().callee().clone();
let exec_input = scale::Encode::encode(message.clone().params().exec_input());
log_info(&format!("call: {:02X?}", exec_input));

let dry_run = self.call_dry_run(signer, &message, value, None).await;
let dry_run = self.call_dry_run(signer, message, value, None).await;

if dry_run.exec_result.result.is_err() {
return Err(Error::CallDryRun(dry_run.exec_result))
Expand All @@ -750,11 +795,11 @@ where
let tx_events = self
.api
.call(
subxt::utils::MultiAddress::Id(message.account_id().clone()),
subxt::utils::MultiAddress::Id(account_id.clone()),
value,
dry_run.exec_result.gas_required.into(),
storage_deposit_limit,
message.exec_input().to_vec(),
exec_input,
signer,
)
.await;
Expand All @@ -769,7 +814,7 @@ where
let dispatch_error =
subxt::error::DispatchError::decode_from(evt.field_bytes(), metadata)
.map_err(Error::Decoding)?;
log_error(&format!("extrinsic for call failed: {dispatch_error:?}"));
log_error(&format!("extrinsic for call failed: {dispatch_error}"));
return Err(Error::CallExtrinsic(dispatch_error))
}
}
Expand Down Expand Up @@ -814,7 +859,7 @@ where
subxt::error::DispatchError::decode_from(evt.field_bytes(), metadata)
.map_err(Error::Decoding)?;

log_error(&format!("extrinsic for call failed: {dispatch_error:?}"));
log_error(&format!("extrinsic for call failed: {dispatch_error}"));
return Err(Error::CallExtrinsic(dispatch_error))
}
}
Expand All @@ -826,21 +871,27 @@ where
///
/// Returns the result of the dry run, together with the decoded return value of the
/// invoked message.
pub async fn call_dry_run<RetType>(
pub async fn call_dry_run<Args, RetType>(
&mut self,
signer: &Signer<C>,
message: &Message<E, RetType>,
message: &CallBuilder<E, Args, RetType>,
value: E::Balance,
storage_deposit_limit: Option<E::Balance>,
) -> CallDryRunResult<E, RetType>
where
Args: scale::Encode,
RetType: scale::Decode,
CallBuilder<E, Args, RetType>: Clone,
{
let dest = message.clone().params().callee().clone();
let exec_input = scale::Encode::encode(message.clone().params().exec_input());

let exec_result = self
.api
.call_dry_run(
Signer::account_id(signer).clone(),
message,
dest,
exec_input,
value,
storage_deposit_limit,
)
Expand Down
Loading