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

Handling data and input fields in TransactionRequest and CallRequest #1096

Merged
102 changes: 99 additions & 3 deletions client/rpc-core/src/types/call_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@

use std::collections::BTreeMap;

use crate::types::{deserialize_data_or_input, Bytes};
use ethereum::AccessListItem;
use ethereum_types::{H160, H256, U256};
use serde::Deserialize;

Comment on lines +21 to 25
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
use crate::types::{deserialize_data_or_input, Bytes};
use ethereum::AccessListItem;
use ethereum_types::{H160, H256, U256};
use serde::Deserialize;
use ethereum::AccessListItem;
use ethereum_types::{H160, H256, U256};
use serde::Deserialize;
use crate::types::{deserialize_data_or_input, Bytes};

use crate::types::Bytes;

/// Call request
#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)]
#[serde(deny_unknown_fields)]
Expand All @@ -44,7 +43,7 @@ pub struct CallRequest {
/// Value
pub value: Option<U256>,
/// Data
#[serde(alias = "input")]
#[serde(deserialize_with = "deserialize_data_or_input", flatten)]
pub data: Option<Bytes>,
/// Nonce
pub nonce: Option<U256>,
Expand Down Expand Up @@ -73,3 +72,100 @@ pub struct CallStateOverride {
/// executing the call.
pub state_diff: Option<BTreeMap<H256, H256>>,
}

#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;

#[test]
fn test_deserialize_with_only_input() {
let data = json!({
"from": "0x60be2d1d3665660d22ff9624b7be0551ee1ac91b",
"to": "0x13fe2d1d3665660d22ff9624b7be0551ee1ac91b",
"gasPrice": "0x10",
"maxFeePerGas": "0x20",
"maxPriorityFeePerGas": "0x30",
"gas": "0x40",
"value": "0x50",
"input": "0x123abc",
"nonce": "0x60",
"accessList": [{"address": "0x60be2d1d3665660d22ff9624b7be0551ee1ac91b", "storageKeys": []}],
"type": "0x70"
});

let request: Result<CallRequest, _> = serde_json::from_value(data);
assert!(request.is_ok());

let request = request.unwrap();
assert_eq!(request.data, Some(Bytes::from(vec![0x12, 0x3a, 0xbc])));
}

#[test]
fn test_deserialize_with_only_data() {
let data = json!({
"from": "0x60be2d1d3665660d22ff9624b7be0551ee1ac91b",
"to": "0x13fe2d1d3665660d22ff9624b7be0551ee1ac91b",
"gasPrice": "0x10",
"maxFeePerGas": "0x20",
"maxPriorityFeePerGas": "0x30",
"gas": "0x40",
"value": "0x50",
"data": "0x123abc",
"nonce": "0x60",
"accessList": [{"address": "0x60be2d1d3665660d22ff9624b7be0551ee1ac91b", "storageKeys": []}],
"type": "0x70"
});

let request: Result<CallRequest, _> = serde_json::from_value(data);
assert!(request.is_ok());

let request = request.unwrap();
assert_eq!(request.data, Some(Bytes::from(vec![0x12, 0x3a, 0xbc])));
}

#[test]
fn test_deserialize_with_data_and_input_mismatch() {
let data = json!({
"from": "0x60be2d1d3665660d22ff9624b7be0551ee1ac91b",
"to": "0x13fe2d1d3665660d22ff9624b7be0551ee1ac91b",
"gasPrice": "0x10",
"maxFeePerGas": "0x20",
"maxPriorityFeePerGas": "0x30",
"gas": "0x40",
"value": "0x50",
"data": "0x123abc",
"input": "0x456def",
"nonce": "0x60",
"accessList": [{"address": "0x60be2d1d3665660d22ff9624b7be0551ee1ac91b", "storageKeys": []}],
"type": "0x70"
});

let request: Result<CallRequest, _> = serde_json::from_value(data);
assert!(request.is_err());
}

#[test]
fn test_deserialize_with_data_and_input_equal() {
let data = json!({
"from": "0x60be2d1d3665660d22ff9624b7be0551ee1ac91b",
"to": "0x13fe2d1d3665660d22ff9624b7be0551ee1ac91b",
"gasPrice": "0x10",
"maxFeePerGas": "0x20",
"maxPriorityFeePerGas": "0x30",
"gas": "0x40",
"value": "0x50",
"data": "0x123abc",
"input": "0x123abc",
"nonce": "0x60",
"accessList": [{"address": "0x60be2d1d3665660d22ff9624b7be0551ee1ac91b", "storageKeys": []}],
"type": "0x70"
});

let request: Result<CallRequest, _> = serde_json::from_value(data);
assert!(request.is_ok());

let request = request.unwrap();
assert_eq!(request.data, Some(Bytes::from(vec![0x12, 0x3a, 0xbc])));
}
}
28 changes: 28 additions & 0 deletions client/rpc-core/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ mod work;

pub mod pubsub;

use serde::{de::Error, Deserialize, Deserializer};

pub use self::{
account_info::{AccountInfo, EthAccount, ExtAccountInfo, RecoveredAccount, StorageProof},
block::{Block, BlockTransactions, Header, Rich, RichBlock, RichHeader},
Expand All @@ -57,3 +59,29 @@ pub use self::{
transaction_request::{TransactionMessage, TransactionRequest},
work::Work,
};

#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize)]
pub(crate) struct CallOrInputData {
data: Option<Bytes>,
input: Option<Bytes>,
}

/// Function to deserialize `data` and `input` within `TransactionRequest` and `CallRequest`.
/// It verifies that if both `data` and `input` are provided, they must be identical.
pub(crate) fn deserialize_data_or_input<'d, D: Deserializer<'d>>(
d: D,
) -> Result<Option<Bytes>, D::Error> {
let CallOrInputData { data, input } = CallOrInputData::deserialize(d)?;
match (&data, &input) {
(Some(data), Some(input)) => {
if data == input {
Ok(Some(data.clone()))
} else {
Err(D::Error::custom(
"Ambiguous value for `data` and `input`".to_string(),
))
}
}
(_, _) => Ok(data.or(input)),
}
}
101 changes: 99 additions & 2 deletions client/rpc-core/src/types/transaction_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use ethereum::{
use ethereum_types::{H160, U256};
use serde::{Deserialize, Serialize};

use crate::types::Bytes;
use crate::types::{deserialize_data_or_input, Bytes};

pub enum TransactionMessage {
Legacy(LegacyTransactionMessage),
Expand Down Expand Up @@ -55,7 +55,7 @@ pub struct TransactionRequest {
/// Value of transaction in wei
pub value: Option<U256>,
/// Additional data sent with transaction
#[serde(alias = "input")]
#[serde(deserialize_with = "deserialize_data_or_input", flatten)]
pub data: Option<Bytes>,
/// Transaction's nonce
pub nonce: Option<U256>,
Expand Down Expand Up @@ -119,3 +119,100 @@ impl From<TransactionRequest> for Option<TransactionMessage> {
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;

#[test]
fn test_deserialize_with_only_input() {
let data = json!({
"from": "0x60be2d1d3665660d22ff9624b7be0551ee1ac91b",
"to": "0x13fe2d1d3665660d22ff9624b7be0551ee1ac91b",
"gasPrice": "0x10",
"maxFeePerGas": "0x20",
"maxPriorityFeePerGas": "0x30",
"gas": "0x40",
"value": "0x50",
"input": "0x123abc",
"nonce": "0x60",
"accessList": [{"address": "0x60be2d1d3665660d22ff9624b7be0551ee1ac91b", "storageKeys": []}],
"type": "0x70"
});

let request: Result<TransactionRequest, _> = serde_json::from_value(data);
assert!(request.is_ok());

let request = request.unwrap();
assert_eq!(request.data, Some(Bytes::from(vec![0x12, 0x3a, 0xbc])));
}

#[test]
fn test_deserialize_with_only_data() {
let data = json!({
"from": "0x60be2d1d3665660d22ff9624b7be0551ee1ac91b",
"to": "0x13fe2d1d3665660d22ff9624b7be0551ee1ac91b",
"gasPrice": "0x10",
"maxFeePerGas": "0x20",
"maxPriorityFeePerGas": "0x30",
"gas": "0x40",
"value": "0x50",
"data": "0x123abc",
"nonce": "0x60",
"accessList": [{"address": "0x60be2d1d3665660d22ff9624b7be0551ee1ac91b", "storageKeys": []}],
"type": "0x70"
});

let request: Result<TransactionRequest, _> = serde_json::from_value(data);
assert!(request.is_ok());

let request = request.unwrap();
assert_eq!(request.data, Some(Bytes::from(vec![0x12, 0x3a, 0xbc])));
}

#[test]
fn test_deserialize_with_data_and_input_mismatch() {
let data = json!({
"from": "0x60be2d1d3665660d22ff9624b7be0551ee1ac91b",
"to": "0x13fe2d1d3665660d22ff9624b7be0551ee1ac91b",
"gasPrice": "0x10",
"maxFeePerGas": "0x20",
"maxPriorityFeePerGas": "0x30",
"gas": "0x40",
"value": "0x50",
"data": "0x123abc",
"input": "0x456def",
"nonce": "0x60",
"accessList": [{"address": "0x60be2d1d3665660d22ff9624b7be0551ee1ac91b", "storageKeys": []}],
"type": "0x70"
});

let request: Result<TransactionRequest, _> = serde_json::from_value(data);
assert!(request.is_err());
}

#[test]
fn test_deserialize_with_data_and_input_equal() {
let data = json!({
"from": "0x60be2d1d3665660d22ff9624b7be0551ee1ac91b",
"to": "0x13fe2d1d3665660d22ff9624b7be0551ee1ac91b",
"gasPrice": "0x10",
"maxFeePerGas": "0x20",
"maxPriorityFeePerGas": "0x30",
"gas": "0x40",
"value": "0x50",
"data": "0x123abc",
"input": "0x123abc",
"nonce": "0x60",
"accessList": [{"address": "0x60be2d1d3665660d22ff9624b7be0551ee1ac91b", "storageKeys": []}],
"type": "0x70"
});

let request: Result<TransactionRequest, _> = serde_json::from_value(data);
assert!(request.is_ok());

let request = request.unwrap();
assert_eq!(request.data, Some(Bytes::from(vec![0x12, 0x3a, 0xbc])));
}
}
25 changes: 25 additions & 0 deletions ts-tests/tests/test-execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,4 +238,29 @@ describeWithFrontier("Frontier RPC (RPC execution)", (context) => {

expect(result.result).to.be.equal(TEST_CONTRACT_DEPLOYED_BYTECODE);
});

step("Deserializes correctly when data and input are equal", async function () {
const result = await customRequest(context.web3, "eth_call", [
{
from: GENESIS_ACCOUNT,
gas: `0x${(ETH_BLOCK_GAS_LIMIT - 1).toString(16)}`,
data: TEST_CONTRACT_BYTECODE,
input: TEST_CONTRACT_BYTECODE,
},
]);

expect(result.result).to.be.equal(TEST_CONTRACT_DEPLOYED_BYTECODE);
});

step("Throws error when data and input are both present and not equal", async function () {
const result = await customRequest(context.web3, "eth_call", [
{
from: GENESIS_ACCOUNT,
gas: `0x${(ETH_BLOCK_GAS_LIMIT - 1).toString(16)}`,
data: TEST_CONTRACT_BYTECODE,
input: "0x12345678",
},
]);
expect((result as any).error.message).to.match(/^Ambiguous value for `data` and `input`/);
});
});