1234567891011121314151617181920use 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;45464748495051525354const GAS_PER_SECOND: Weight = 1_000_000_000_000;55565758const GAS_LIMIT: Weight = 5 * GAS_PER_SECOND;596061struct 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}798081#[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}919293#[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}104105106#[rpc]107pub trait ContractsApi<BlockHash, BlockNumber, AccountId, Balance, Hash> {108 109 110 111 112 113 114 #[rpc(name = "contracts_call")]115 fn call(116 &self,117 call_request: CallRequest<AccountId>,118 at: Option<BlockHash>,119 ) -> Result<ContractExecResult>;120121 122 123 124 125 126 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 135 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 145 146 147 148 149 150 #[rpc(name = "contracts_rentProjection")]151 fn rent_projection(152 &self,153 address: AccountId,154 at: Option<BlockHash>,155 ) -> Result<Option<BlockNumber>>;156}157158159pub struct Contracts<C, B> {160 client: Arc<C>,161 _marker: std::marker::PhantomData<B>,162}163164impl<C, B> Contracts<C, B> {165 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 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 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 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 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}295296297fn 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}