1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
// This file is part of Tetcore.

// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Node-specific RPC methods for interaction with contracts.

use std::sync::Arc;

use codec::Codec;
use tetsy_jsonrpc_core::{Error, ErrorCode, Result};
use tetsy_jsonrpc_derive::rpc;
use noble_contracts_primitives::RentProjection;
use serde::{Deserialize, Serialize};
use tp_api::ProvideRuntimeApi;
use tp_blockchain::HeaderBackend;
use tet_core::{Bytes, H256};
use tp_rpc::number;
use tp_runtime::{
	generic::BlockId,
	traits::{Block as BlockT, Header as HeaderT},
	DispatchError,
};
use std::convert::{TryFrom, TryInto};
use noble_contracts_primitives::ContractExecResult;

pub use noble_contracts_rpc_runtime_api::ContractsApi as ContractsRuntimeApi;

const RUNTIME_ERROR: i64 = 1;
const CONTRACT_DOESNT_EXIST: i64 = 2;
const CONTRACT_IS_A_TOMBSTONE: i64 = 3;

/// A rough estimate of how much gas a decent hardware consumes per second,
/// using native execution.
/// This value is used to set the upper bound for maximal contract calls to
/// prevent blocking the RPC for too long.
///
/// As 1 gas is equal to 1 weight we base this on the conducted benchmarks which
/// determined runtime weights:
/// https://github.com/tetcoin/tetcore/pull/5446
const GAS_PER_SECOND: u64 = 1_000_000_000_000;

/// A private newtype for converting `ContractAccessError` into an RPC error.
struct ContractAccessError(noble_contracts_primitives::ContractAccessError);
impl From<ContractAccessError> for Error {
	fn from(e: ContractAccessError) -> Error {
		use noble_contracts_primitives::ContractAccessError::*;
		match e.0 {
			DoesntExist => Error {
				code: ErrorCode::ServerError(CONTRACT_DOESNT_EXIST),
				message: "The specified contract doesn't exist.".into(),
				data: None,
			},
			IsTombstone => Error {
				code: ErrorCode::ServerError(CONTRACT_IS_A_TOMBSTONE),
				message: "The contract is a tombstone and doesn't have any storage.".into(),
				data: None,
			},
		}
	}
}

/// A struct that encodes RPC parameters required for a call to a smart-contract.
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct CallRequest<AccountId> {
	origin: AccountId,
	dest: AccountId,
	value: number::NumberOrHex,
	gas_limit: number::NumberOrHex,
	input_data: Bytes,
}

#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
struct RpcContractExecSuccess {
	/// The return flags. See `noble_contracts_primitives::ReturnFlags`.
	flags: u32,
	/// Data as returned by the contract.
	data: Bytes,
}

/// An RPC serializable result of contract execution
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "camelCase")]
pub struct RpcContractExecResult {
	/// How much gas was consumed by the call. In case of an error this is the amount
	/// that was used up until the error occurred.
	gas_consumed: u64,
	/// Additional dynamic human readable error information for debugging. An empty string
	/// indicates that no additional information is available.
	debug_message: String,
	/// Indicates whether the contract execution was successful or not.
	result: std::result::Result<RpcContractExecSuccess, DispatchError>,
}

impl From<ContractExecResult> for RpcContractExecResult {
	fn from(r: ContractExecResult) -> Self {
		match r.exec_result {
			Ok(val) => RpcContractExecResult {
				gas_consumed: r.gas_consumed,
				debug_message: String::new(),
				result: Ok(RpcContractExecSuccess {
					flags: val.flags.bits(),
					data: val.data.into(),
				}),
			},
			Err(err) => RpcContractExecResult {
				gas_consumed: r.gas_consumed,
				debug_message: String::new(),
				result: Err(err.error),
			},
		}
	}
}

/// Contracts RPC methods.
#[rpc]
pub trait ContractsApi<BlockHash, BlockNumber, AccountId, Balance> {
	/// Executes a call to a contract.
	///
	/// This call is performed locally without submitting any transactions. Thus executing this
	/// won't change any state. Nonetheless, the calling state-changing contracts is still possible.
	///
	/// This method is useful for calling getter-like methods on contracts.
	#[rpc(name = "contracts_call")]
	fn call(
		&self,
		call_request: CallRequest<AccountId>,
		at: Option<BlockHash>,
	) -> Result<RpcContractExecResult>;

	/// Returns the value under a specified storage `key` in a contract given by `address` param,
	/// or `None` if it is not set.
	#[rpc(name = "contracts_getStorage")]
	fn get_storage(
		&self,
		address: AccountId,
		key: H256,
		at: Option<BlockHash>,
	) -> Result<Option<Bytes>>;

	/// Returns the projected time a given contract will be able to sustain paying its rent.
	///
	/// The returned projection is relevant for the given block, i.e. it is as if the contract was
	/// accessed at the beginning of that block.
	///
	/// Returns `None` if the contract is exempted from rent.
	#[rpc(name = "contracts_rentProjection")]
	fn rent_projection(
		&self,
		address: AccountId,
		at: Option<BlockHash>,
	) -> Result<Option<BlockNumber>>;
}

/// An implementation of contract specific RPC methods.
pub struct Contracts<C, B> {
	client: Arc<C>,
	_marker: std::marker::PhantomData<B>,
}

impl<C, B> Contracts<C, B> {
	/// Create new `Contracts` with the given reference to the client.
	pub fn new(client: Arc<C>) -> Self {
		Contracts {
			client,
			_marker: Default::default(),
		}
	}
}
impl<C, Block, AccountId, Balance>
	ContractsApi<
		<Block as BlockT>::Hash,
		<<Block as BlockT>::Header as HeaderT>::Number,
		AccountId,
		Balance,
	> for Contracts<C, Block>
where
	Block: BlockT,
	C: Send + Sync + 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,
	C::Api: ContractsRuntimeApi<
		Block,
		AccountId,
		Balance,
		<<Block as BlockT>::Header as HeaderT>::Number,
	>,
	AccountId: Codec,
	Balance: Codec + TryFrom<number::NumberOrHex>,
{
	fn call(
		&self,
		call_request: CallRequest<AccountId>,
		at: Option<<Block as BlockT>::Hash>,
	) -> Result<RpcContractExecResult> {
		let api = self.client.runtime_api();
		let at = BlockId::hash(at.unwrap_or_else(||
			// If the block hash is not supplied assume the best block.
			self.client.info().best_hash));

		let CallRequest {
			origin,
			dest,
			value,
			gas_limit,
			input_data,
		} = call_request;

		// Make sure that value fits into the balance type.
		let value: Balance = value.try_into().map_err(|_| Error {
			code: ErrorCode::InvalidParams,
			message: format!("{:?} doesn't fit into the balance type", value),
			data: None,
		})?;

		// Make sure that gas_limit fits into 64 bits.
		let gas_limit: u64 = gas_limit.try_into().map_err(|_| Error {
			code: ErrorCode::InvalidParams,
			message: format!("{:?} doesn't fit in 64 bit unsigned value", gas_limit),
			data: None,
		})?;

		let max_gas_limit = 5 * GAS_PER_SECOND;
		if gas_limit > max_gas_limit {
			return Err(Error {
				code: ErrorCode::InvalidParams,
				message: format!(
					"Requested gas limit is greater than maximum allowed: {} > {}",
					gas_limit, max_gas_limit
				),
				data: None,
			});
		}

		let exec_result = api
			.call(&at, origin, dest, value, gas_limit, input_data.to_vec())
			.map_err(runtime_error_into_rpc_err)?;

		Ok(exec_result.into())
	}

	fn get_storage(
		&self,
		address: AccountId,
		key: H256,
		at: Option<<Block as BlockT>::Hash>,
	) -> Result<Option<Bytes>> {
		let api = self.client.runtime_api();
		let at = BlockId::hash(at.unwrap_or_else(||
			// If the block hash is not supplied assume the best block.
			self.client.info().best_hash));

		let result = api
			.get_storage(&at, address, key.into())
			.map_err(runtime_error_into_rpc_err)?
			.map_err(ContractAccessError)?
			.map(Bytes);

		Ok(result)
	}

	fn rent_projection(
		&self,
		address: AccountId,
		at: Option<<Block as BlockT>::Hash>,
	) -> Result<Option<<<Block as BlockT>::Header as HeaderT>::Number>> {
		let api = self.client.runtime_api();
		let at = BlockId::hash(at.unwrap_or_else(||
			// If the block hash is not supplied assume the best block.
			self.client.info().best_hash));

		let result = api
			.rent_projection(&at, address)
			.map_err(runtime_error_into_rpc_err)?
			.map_err(ContractAccessError)?;

		Ok(match result {
			RentProjection::NoEviction => None,
			RentProjection::EvictionAt(block_num) => Some(block_num),
		})
	}
}

/// Converts a runtime trap into an RPC error.
fn runtime_error_into_rpc_err(err: impl std::fmt::Debug) -> Error {
	Error {
		code: ErrorCode::ServerError(RUNTIME_ERROR),
		message: "Runtime trapped".into(),
		data: Some(format!("{:?}", err).into()),
	}
}

#[cfg(test)]
mod tests {
	use super::*;
	use tet_core::U256;

	#[test]
	fn call_request_should_serialize_deserialize_properly() {
		type Req = CallRequest<String>;
		let req: Req = serde_json::from_str(r#"
		{
			"origin": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL",
			"dest": "5DRakbLVnjVrW6niwLfHGW24EeCEvDAFGEXrtaYS5M4ynoom",
			"value": "0x112210f4B16c1cb1",
			"gasLimit": 1000000000000,
			"inputData": "0x8c97db39"
		}
		"#).unwrap();
		assert_eq!(req.gas_limit.into_u256(), U256::from(0xe8d4a51000u64));
		assert_eq!(req.value.into_u256(), U256::from(1234567890987654321u128));
	}

	#[test]
	fn result_should_serialize_deserialize_properly() {
		fn test(expected: &str) {
			let res: RpcContractExecResult = serde_json::from_str(expected).unwrap();
			let actual = serde_json::to_string(&res).unwrap();
			assert_eq!(actual, expected);
		}
		test(r#"{"gasConsumed":5000,"debugMessage":"helpOk","result":{"Ok":{"flags":5,"data":"0x1234"}}}"#);
		test(r#"{"gasConsumed":3400,"debugMessage":"helpErr","result":{"Err":"BadOrigin"}}"#);
	}
}