git.delta.rocks / unique-network / refs/commits / e8fdadb89923

difftreelog

source

pallets/contracts/rpc/src/lib.rs12.0 KiBsourcehistory
1// This file is part of Substrate.23// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd.4// SPDX-License-Identifier: Apache-2.056// Licensed under the Apache License, Version 2.0 (the "License");7// you may not use this file except in compliance with the License.8// You may obtain a copy of the License at9//10// 	http://www.apache.org/licenses/LICENSE-2.011//12// Unless required by applicable law or agreed to in writing, software13// distributed under the License is distributed on an "AS IS" BASIS,14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.15// See the License for the specific language governing permissions and16// limitations under the License.1718//! Node-specific RPC methods for interaction with contracts.1920use std::sync::Arc;2122use codec::Codec;23use jsonrpc_core::{Error, ErrorCode, Result};24use jsonrpc_derive::rpc;25use pallet_contracts_primitives::RentProjection;26use serde::{Deserialize, Serialize};27use sp_api::ProvideRuntimeApi;28use sp_blockchain::HeaderBackend;29use sp_core::{Bytes, H256};30use sp_rpc::number::NumberOrHex;31use sp_runtime::{32	generic::BlockId,33	traits::{Block as BlockT, Header as HeaderT},34};35use std::convert::{TryFrom, TryInto};36use pallet_contracts_primitives::{Code, ContractExecResult, ContractInstantiateResult};3738pub use pallet_contracts_rpc_runtime_api::ContractsApi as ContractsRuntimeApi;3940const RUNTIME_ERROR: i64 = 1;41const CONTRACT_DOESNT_EXIST: i64 = 2;42const CONTRACT_IS_A_TOMBSTONE: i64 = 3;4344pub type Weight = u64;4546/// A rough estimate of how much gas a decent hardware consumes per second,47/// using native execution.48/// This value is used to set the upper bound for maximal contract calls to49/// prevent blocking the RPC for too long.50///51/// As 1 gas is equal to 1 weight we base this on the conducted benchmarks which52/// determined runtime weights:53/// https://github.com/paritytech/substrate/pull/544654const GAS_PER_SECOND: Weight = 1_000_000_000_000;5556/// The maximum amount of weight that the call and instantiate rpcs are allowed to consume.57/// This puts a ceiling on the weight limit that is supplied to the rpc as an argument.58const GAS_LIMIT: Weight = 5 * GAS_PER_SECOND;5960/// A private newtype for converting `ContractAccessError` into an RPC error.61struct ContractAccessError(pallet_contracts_primitives::ContractAccessError);62impl From<ContractAccessError> for Error {63	fn from(e: ContractAccessError) -> Error {64		use pallet_contracts_primitives::ContractAccessError::*;65		match e.0 {66			DoesntExist => Error {67				code: ErrorCode::ServerError(CONTRACT_DOESNT_EXIST),68				message: "The specified contract doesn't exist.".into(),69				data: None,70			},71			IsTombstone => Error {72				code: ErrorCode::ServerError(CONTRACT_IS_A_TOMBSTONE),73				message: "The contract is a tombstone and doesn't have any storage.".into(),74				data: None,75			},76		}77	}78}7980/// A struct that encodes RPC parameters required for a call to a smart-contract.81#[derive(Serialize, Deserialize)]82#[serde(rename_all = "camelCase")]83#[serde(deny_unknown_fields)]84pub struct CallRequest<AccountId> {85	origin: AccountId,86	dest: AccountId,87	value: NumberOrHex,88	gas_limit: NumberOrHex,89	input_data: Bytes,90}9192/// A struct that encodes RPC parameters required to instantiate a new smart-contract.93#[derive(Serialize, Deserialize)]94#[serde(rename_all = "camelCase")]95#[serde(deny_unknown_fields)]96pub struct InstantiateRequest<AccountId, Hash> {97	origin: AccountId,98	endowment: NumberOrHex,99	gas_limit: NumberOrHex,100	code: Code<Hash>,101	data: Bytes,102	salt: Bytes,103}104105/// Contracts RPC methods.106#[rpc]107pub trait ContractsApi<BlockHash, BlockNumber, AccountId, Balance, Hash> {108	/// Executes a call to a contract.109	///110	/// This call is performed locally without submitting any transactions. Thus executing this111	/// won't change any state. Nonetheless, the calling state-changing contracts is still possible.112	///113	/// This method is useful for calling getter-like methods on contracts.114	#[rpc(name = "contracts_call")]115	fn call(116		&self,117		call_request: CallRequest<AccountId>,118		at: Option<BlockHash>,119	) -> Result<ContractExecResult>;120121	/// Instantiate a new contract.122	///123	/// This call is performed locally without submitting any transactions. Thus the contract124	/// is not actually created.125	///126	/// This method is useful for UIs to dry-run contract instantiations.127	#[rpc(name = "contracts_instantiate")]128	fn instantiate(129		&self,130		instantiate_request: InstantiateRequest<AccountId, Hash>,131		at: Option<BlockHash>,132	) -> Result<ContractInstantiateResult<AccountId, BlockNumber>>;133134	/// Returns the value under a specified storage `key` in a contract given by `address` param,135	/// or `None` if it is not set.136	#[rpc(name = "contracts_getStorage")]137	fn get_storage(138		&self,139		address: AccountId,140		key: H256,141		at: Option<BlockHash>,142	) -> Result<Option<Bytes>>;143144	/// Returns the projected time a given contract will be able to sustain paying its rent.145	///146	/// The returned projection is relevant for the given block, i.e. it is as if the contract was147	/// accessed at the beginning of that block.148	///149	/// Returns `None` if the contract is exempted from rent.150	#[rpc(name = "contracts_rentProjection")]151	fn rent_projection(152		&self,153		address: AccountId,154		at: Option<BlockHash>,155	) -> Result<Option<BlockNumber>>;156}157158/// An implementation of contract specific RPC methods.159pub struct Contracts<C, B> {160	client: Arc<C>,161	_marker: std::marker::PhantomData<B>,162}163164impl<C, B> Contracts<C, B> {165	/// Create new `Contracts` with the given reference to the client.166	pub fn new(client: Arc<C>) -> Self {167		Contracts {168			client,169			_marker: Default::default(),170		}171	}172}173impl<C, Block, AccountId, Balance, Hash>174	ContractsApi<175		<Block as BlockT>::Hash,176		<<Block as BlockT>::Header as HeaderT>::Number,177		AccountId,178		Balance,179		Hash,180	> for Contracts<C, Block>181where182	Block: BlockT,183	C: Send + Sync + 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,184	C::Api: ContractsRuntimeApi<185		Block,186		AccountId,187		Balance,188		<<Block as BlockT>::Header as HeaderT>::Number,189		Hash,190	>,191	AccountId: Codec,192	Balance: Codec + TryFrom<NumberOrHex>,193	Hash: Codec,194{195	fn call(196		&self,197		call_request: CallRequest<AccountId>,198		at: Option<<Block as BlockT>::Hash>,199	) -> Result<ContractExecResult> {200		let api = self.client.runtime_api();201		let at = BlockId::hash(at.unwrap_or_else(||202			// If the block hash is not supplied assume the best block.203			self.client.info().best_hash));204205		let CallRequest {206			origin,207			dest,208			value,209			gas_limit,210			input_data,211		} = call_request;212213		let value: Balance = decode_hex(value, "balance")?;214		let gas_limit: Weight = decode_hex(gas_limit, "weight")?;215		limit_gas(gas_limit)?;216217		let exec_result = api218			.call(&at, origin, dest, value, gas_limit, input_data.to_vec())219			.map_err(runtime_error_into_rpc_err)?;220221		Ok(exec_result)222	}223224	fn instantiate(225		&self,226		instantiate_request: InstantiateRequest<AccountId, Hash>,227		at: Option<<Block as BlockT>::Hash>,228	) -> Result<ContractInstantiateResult<AccountId, <<Block as BlockT>::Header as HeaderT>::Number>> {229		let api = self.client.runtime_api();230		let at = BlockId::hash(at.unwrap_or_else(||231			// If the block hash is not supplied assume the best block.232			self.client.info().best_hash));233234		let InstantiateRequest {235			origin,236			endowment,237			gas_limit,238			code,239			data,240			salt,241		} = instantiate_request;242243		let endowment: Balance = decode_hex(endowment, "balance")?;244		let gas_limit: Weight = decode_hex(gas_limit, "weight")?;245		limit_gas(gas_limit)?;246247		let exec_result = api248			.instantiate(&at, origin, endowment, gas_limit, code, data.to_vec(), salt.to_vec())249			.map_err(runtime_error_into_rpc_err)?;250251		Ok(exec_result)252	}253254	fn get_storage(255		&self,256		address: AccountId,257		key: H256,258		at: Option<<Block as BlockT>::Hash>,259	) -> Result<Option<Bytes>> {260		let api = self.client.runtime_api();261		let at = BlockId::hash(at.unwrap_or_else(||262			// If the block hash is not supplied assume the best block.263			self.client.info().best_hash));264265		let result = api266			.get_storage(&at, address, key.into())267			.map_err(runtime_error_into_rpc_err)?268			.map_err(ContractAccessError)?269			.map(Bytes);270271		Ok(result)272	}273274	fn rent_projection(275		&self,276		address: AccountId,277		at: Option<<Block as BlockT>::Hash>,278	) -> Result<Option<<<Block as BlockT>::Header as HeaderT>::Number>> {279		let api = self.client.runtime_api();280		let at = BlockId::hash(at.unwrap_or_else(||281			// If the block hash is not supplied assume the best block.282			self.client.info().best_hash));283284		let result = api285			.rent_projection(&at, address)286			.map_err(runtime_error_into_rpc_err)?287			.map_err(ContractAccessError)?;288289		Ok(match result {290			RentProjection::NoEviction => None,291			RentProjection::EvictionAt(block_num) => Some(block_num),292		})293	}294}295296/// Converts a runtime trap into an RPC error.297fn runtime_error_into_rpc_err(err: impl std::fmt::Debug) -> Error {298	Error {299		code: ErrorCode::ServerError(RUNTIME_ERROR),300		message: "Runtime error".into(),301		data: Some(format!("{:?}", err).into()),302	}303}304305fn decode_hex<H: std::fmt::Debug + Copy, T: TryFrom<H>>(from: H, name: &str) -> Result<T> {306	from.try_into().map_err(|_| Error {307		code: ErrorCode::InvalidParams,308		message: format!("{:?} does not fit into the {} type", from, name),309		data: None,310	})311}312313fn limit_gas(gas_limit: Weight) -> Result<()> {314	if gas_limit > GAS_LIMIT {315		Err(Error {316			code: ErrorCode::InvalidParams,317			message: format!(318				"Requested gas limit is greater than maximum allowed: {} > {}",319				gas_limit, GAS_LIMIT320			),321			data: None,322		})323	} else {324		Ok(())325	}326}327328#[cfg(test)]329mod tests {330	use super::*;331	use sp_core::U256;332333	fn trim(json: &str) -> String {334		json.chars().filter(|c| !c.is_whitespace()).collect()335	}336337	#[test]338	fn call_request_should_serialize_deserialize_properly() {339		type Req = CallRequest<String>;340		let req: Req = serde_json::from_str(r#"341		{342			"origin": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL",343			"dest": "5DRakbLVnjVrW6niwLfHGW24EeCEvDAFGEXrtaYS5M4ynoom",344			"value": "0x112210f4B16c1cb1",345			"gasLimit": 1000000000000,346			"inputData": "0x8c97db39"347		}348		"#).unwrap();349		assert_eq!(req.gas_limit.into_u256(), U256::from(0xe8d4a51000u64));350		assert_eq!(req.value.into_u256(), U256::from(1234567890987654321u128));351	}352353	#[test]354	fn instantiate_request_should_serialize_deserialize_properly() {355		type Req = InstantiateRequest<String, String>;356		let req: Req = serde_json::from_str(r#"357		{358			"origin": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL",359			"endowment": "0x88",360			"gasLimit": 42,361			"code": { "existing": "0x1122" },362			"data": "0x4299",363			"salt": "0x9988"364		}365		"#).unwrap();366367		assert_eq!(req.origin, "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL");368		assert_eq!(req.endowment.into_u256(), 0x88.into());369		assert_eq!(req.gas_limit.into_u256(), 42.into());370		assert_eq!(&*req.data, [0x42, 0x99].as_ref());371		assert_eq!(&*req.salt, [0x99, 0x88].as_ref());372		let code = match req.code {373			Code::Existing(hash) => hash,374			_ => panic!("json encoded an existing hash"),375		};376		assert_eq!(&code, "0x1122");377	}378379	#[test]380	fn call_result_should_serialize_deserialize_properly() {381		fn test(expected: &str) {382			let res: ContractExecResult = serde_json::from_str(expected).unwrap();383			let actual = serde_json::to_string(&res).unwrap();384			assert_eq!(actual, trim(expected).as_str());385		}386		test(r#"{387			"gasConsumed": 5000,388			"debugMessage": "0x68656c704f6b",389			"result": {390			  "Ok": {391				"flags": 5,392				"data": "0x1234"393			  }394			}395		}"#);396		test(r#"{397			"gasConsumed": 3400,398			"debugMessage": "0x68656c70457272",399			"result": {400			  "Err": "BadOrigin"401			}402		}"#);403	}404405	#[test]406	fn instantiate_result_should_serialize_deserialize_properly() {407		fn test(expected: &str) {408			let res: ContractInstantiateResult<String, u64> = serde_json::from_str(expected).unwrap();409			let actual = serde_json::to_string(&res).unwrap();410			assert_eq!(actual, trim(expected).as_str());411		}412		test(r#"{413			"gasConsumed": 5000,414			"debugMessage": "0x68656c704f6b",415			"result": {416			   "Ok": {417				  "result": {418					 "flags": 5,419					 "data": "0x1234"420				  },421				  "accountId": "5CiPP",422				  "rentProjection": null423			   }424			}425		}"#);426		test(r#"{427			"gasConsumed": 3400,428			"debugMessage": "0x68656c70457272",429			"result": {430			  "Err": "BadOrigin"431			}432		}"#);433	}434}