Skip to content

Commit 74eeac0

Browse files
committed
Add support for overloaded contract functions
Prior to this, ethabi would store just on function definition per function name in the `Contract` struct. This is not sufficient, as functions can be overloaded and there can be multiple definitions with the same name. This applies the same treatment to contract functions that was applied earlier for events: for every name, a vector of function definitions is now stored. ethabi-cli takes either a name or a signature of the form functionName() functionName():(bool) functionName(uint256,string):(uint256,bool) and matches this to the right overloaded version of the function with that name.
1 parent f4ce267 commit 74eeac0

File tree

5 files changed

+154
-20
lines changed

5 files changed

+154
-20
lines changed

cli/src/error.rs

+11
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,16 @@ error_chain! {
2424
description("More than one event found for name, try providing the full signature"),
2525
display("Ambiguous event name `{}`", name),
2626
}
27+
28+
InvalidFunctionSignature(signature: String) {
29+
description("Invalid function signature"),
30+
display("Invalid function signature `{}`", signature),
31+
}
32+
33+
AmbiguousFunctionName(name: String) {
34+
description("More than one function found for name, try providing the full signature"),
35+
display("Ambiguous function name `{}`", name),
36+
}
37+
2738
}
2839
}

cli/src/main.rs

+63-14
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ enum Encode {
2929
/// Load function from JSON ABI file.
3030
Function {
3131
abi_path: String,
32-
function_name: String,
32+
function_name_or_signature: String,
3333
#[structopt(short, number_of_values = 1)]
3434
params: Vec<String>,
3535
/// Allow short representation of input params.
@@ -54,7 +54,7 @@ enum Decode {
5454
/// Load function from JSON ABI file.
5555
Function {
5656
abi_path: String,
57-
function_name: String,
57+
function_name_or_signature: String,
5858
data: String,
5959
},
6060
/// Specify types of input params inline.
@@ -87,25 +87,44 @@ where
8787
let opt = Opt::from_iter(args);
8888

8989
match opt {
90-
Opt::Encode(Encode::Function { abi_path, function_name, params, lenient }) =>
91-
encode_input(&abi_path, &function_name, &params, lenient),
90+
Opt::Encode(Encode::Function { abi_path, function_name_or_signature, params, lenient }) =>
91+
encode_input(&abi_path, &function_name_or_signature, &params, lenient),
9292
Opt::Encode(Encode::Params { params, lenient }) =>
9393
encode_params(&params, lenient),
94-
Opt::Decode(Decode::Function { abi_path, function_name, data }) =>
95-
decode_call_output(&abi_path, &function_name, &data),
94+
Opt::Decode(Decode::Function { abi_path, function_name_or_signature, data }) =>
95+
decode_call_output(&abi_path, &function_name_or_signature, &data),
9696
Opt::Decode(Decode::Params { types, data }) =>
9797
decode_params(&types, &data),
9898
Opt::Decode(Decode::Log { abi_path, event_name_or_signature, topics, data }) =>
9999
decode_log(&abi_path, &event_name_or_signature, &topics, &data),
100100
}
101101
}
102102

103-
fn load_function(path: &str, function: &str) -> Result<Function, Error> {
103+
fn load_function(path: &str, name_or_signature: &str) -> Result<Function, Error> {
104104
let file = File::open(path)?;
105105
let contract = Contract::load(file)?;
106-
let function = contract.function(function)?.clone();
106+
let params_start = name_or_signature.find('(');
107107

108-
Ok(function)
108+
match params_start {
109+
// It's a signature
110+
Some(params_start) => {
111+
let name = &name_or_signature[..params_start];
112+
113+
contract.functions_by_name(name)?.iter().find(|f| {
114+
f.signature() == name_or_signature
115+
}).cloned().ok_or(ErrorKind::InvalidFunctionSignature(name_or_signature.to_owned()).into())
116+
},
117+
118+
// It's a name
119+
None => {
120+
let functions = contract.functions_by_name(name_or_signature)?;
121+
match functions.len() {
122+
0 => unreachable!(),
123+
1 => Ok(functions[0].clone()),
124+
_ => Err(ErrorKind::AmbiguousFunctionName(name_or_signature.to_owned()).into())
125+
}
126+
},
127+
}
109128
}
110129

111130
fn load_event(path: &str, name_or_signature: &str) -> Result<Event, Error> {
@@ -145,8 +164,8 @@ fn parse_tokens(params: &[(ParamType, &str)], lenient: bool) -> Result<Vec<Token
145164
.map_err(From::from)
146165
}
147166

148-
fn encode_input(path: &str, function: &str, values: &[String], lenient: bool) -> Result<String, Error> {
149-
let function = load_function(path, function)?;
167+
fn encode_input(path: &str, name_or_signature: &str, values: &[String], lenient: bool) -> Result<String, Error> {
168+
let function = load_function(path, name_or_signature)?;
150169

151170
let params: Vec<_> = function.inputs.iter()
152171
.map(|param| param.kind.clone())
@@ -174,8 +193,8 @@ fn encode_params(params: &[String], lenient: bool) -> Result<String, Error> {
174193
Ok(result.to_hex())
175194
}
176195

177-
fn decode_call_output(path: &str, function: &str, data: &str) -> Result<String, Error> {
178-
let function = load_function(path, function)?;
196+
fn decode_call_output(path: &str, name_or_signature: &str, data: &str) -> Result<String, Error> {
197+
let function = load_function(path, name_or_signature)?;
179198
let data : Vec<u8> = data.from_hex().chain_err(|| "Expected <data> to be hex")?;
180199
let tokens = function.decode_output(&data)?;
181200
let types = function.outputs;
@@ -271,12 +290,42 @@ mod tests {
271290
}
272291

273292
#[test]
274-
fn abi_encode() {
293+
fn function_encode_by_name() {
275294
let command = "ethabi encode function ../res/test.abi foo -p 1".split(" ");
276295
let expected = "455575780000000000000000000000000000000000000000000000000000000000000001";
277296
assert_eq!(execute(command).unwrap(), expected);
278297
}
279298

299+
#[test]
300+
fn function_encode_by_signature() {
301+
let command = "ethabi encode function ../res/test.abi foo(bool) -p 1".split(" ");
302+
let expected = "455575780000000000000000000000000000000000000000000000000000000000000001";
303+
assert_eq!(execute(command).unwrap(), expected);
304+
}
305+
306+
#[test]
307+
fn overloaded_function_encode_by_name() {
308+
// This should fail because there are two definitions of `bar in the ABI
309+
let command = "ethabi encode function ../res/test.abi bar -p 1".split(" ");
310+
assert!(execute(command).is_err());
311+
}
312+
313+
#[test]
314+
fn overloaded_function_encode_by_first_signature() {
315+
let command = "ethabi encode function ../res/test.abi bar(bool) -p 1".split(" ");
316+
let expected = "6fae94120000000000000000000000000000000000000000000000000000000000000001";
317+
assert_eq!(execute(command).unwrap(), expected);
318+
}
319+
320+
#[test]
321+
fn overloaded_function_encode_by_second_signature() {
322+
let command = "ethabi encode function ../res/test.abi bar(string):(uint256) -p 1".split(" ");
323+
let expected = "d473a8ed0000000000000000000000000000000000000000000000000000000000000020\
324+
000000000000000000000000000000000000000000000000000000000000000131000000\
325+
00000000000000000000000000000000000000000000000000000000";
326+
assert_eq!(execute(command).unwrap(), expected);
327+
}
328+
280329
#[test]
281330
fn simple_decode() {
282331
let command = "ethabi decode params -t bool 0000000000000000000000000000000000000000000000000000000000000001".split(" ");

ethabi/src/contract.rs

+19-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub struct Contract {
1414
/// Contract constructor.
1515
pub constructor: Option<Constructor>,
1616
/// Contract functions.
17-
pub functions: HashMap<String, Function>,
17+
pub functions: HashMap<String, Vec<Function>>,
1818
/// Contract events, maps signature to event.
1919
pub events: HashMap<String, Vec<Event>>,
2020
/// Contract has fallback function.
@@ -50,7 +50,7 @@ impl<'a> Visitor<'a> for ContractVisitor {
5050
result.constructor = Some(constructor);
5151
},
5252
Operation::Function(func) => {
53-
result.functions.insert(func.name.clone(), func);
53+
result.functions.entry(func.name.clone()).or_default().push(func);
5454
},
5555
Operation::Event(event) => {
5656
result.events.entry(event.name.clone()).or_default().push(event);
@@ -76,9 +76,15 @@ impl Contract {
7676
self.constructor.as_ref()
7777
}
7878

79-
/// Creates function call builder.
79+
/// Get the function named `name`, the first if there are overloaded
80+
/// versions of the same function.
8081
pub fn function(&self, name: &str) -> errors::Result<&Function> {
81-
self.functions.get(name).ok_or_else(|| ErrorKind::InvalidName(name.to_owned()).into())
82+
self.functions
83+
.get(name)
84+
.into_iter()
85+
.flatten()
86+
.next()
87+
.ok_or_else(|| ErrorKind::InvalidName(name.to_owned()).into())
8288
}
8389

8490
/// Get the contract event named `name`, the first if there are multiple.
@@ -95,9 +101,16 @@ impl Contract {
95101
.ok_or_else(|| ErrorKind::InvalidName(name.to_owned()).into())
96102
}
97103

104+
/// Get all functions named `name`.
105+
pub fn functions_by_name(&self, name: &str) -> errors::Result<&Vec<Function>> {
106+
self.functions
107+
.get(name)
108+
.ok_or_else(|| ErrorKind::InvalidName(name.to_owned()).into())
109+
}
110+
98111
/// Iterate over all functions of the contract in arbitrary order.
99112
pub fn functions(&self) -> Functions {
100-
Functions(self.functions.values())
113+
Functions(self.functions.values().flatten())
101114
}
102115

103116
/// Iterate over all events of the contract in arbitrary order.
@@ -112,7 +125,7 @@ impl Contract {
112125
}
113126

114127
/// Contract functions interator.
115-
pub struct Functions<'a>(Values<'a, String, Function>);
128+
pub struct Functions<'a>(Flatten<Values<'a, String, Vec<Function>>>);
116129

117130
impl<'a> Iterator for Functions<'a> {
118131
type Item = &'a Function;

ethabi/src/function.rs

+33
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
//! Contract function call builder.
22
3+
use std::string::ToString;
4+
35
use signature::short_signature;
46
use {Param, Token, Result, ErrorKind, Bytes, decode, ParamType, encode};
57

@@ -49,6 +51,37 @@ impl Function {
4951
pub fn decode_output(&self, data: &[u8]) -> Result<Vec<Token>> {
5052
decode(&self.output_param_types(), &data)
5153
}
54+
55+
/// Parses the ABI function input to a list of tokens.
56+
pub fn decode_input(&self, data: &[u8]) -> Result<Vec<Token>> {
57+
decode(&self.input_param_types(), &data)
58+
}
59+
60+
/// Returns a signature that uniquely identifies this function.
61+
///
62+
/// Examples:
63+
/// - `functionName()`
64+
/// - `functionName():(uint256)`
65+
/// - `functionName(bool):(uint256,string)`
66+
/// - `functionName(uint256,bytes32):(string,uint256)`
67+
pub fn signature(&self) -> String {
68+
let inputs = self.inputs
69+
.iter()
70+
.map(|p| p.kind.to_string())
71+
.collect::<Vec<_>>()
72+
.join(",");
73+
74+
let outputs = self.outputs
75+
.iter()
76+
.map(|p| p.kind.to_string())
77+
.collect::<Vec<_>>()
78+
.join(",");
79+
80+
match (inputs.len(), outputs.len()) {
81+
(_, 0) => format!("{}({})", self.name, inputs),
82+
(_, _) => format!("{}({}):({})", self.name, inputs, outputs)
83+
}
84+
}
5285
}
5386

5487
#[cfg(test)]

res/test.abi

+28
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,33 @@
99
"name": "foo",
1010
"outputs": [],
1111
"type": "function"
12+
},
13+
{
14+
"inputs": [
15+
{
16+
"name": "a",
17+
"type": "bool"
18+
}
19+
],
20+
"name": "bar",
21+
"outputs": [
22+
],
23+
"type": "function"
24+
},
25+
{
26+
"inputs": [
27+
{
28+
"name": "a",
29+
"type": "string"
30+
}
31+
],
32+
"name": "bar",
33+
"outputs": [
34+
{
35+
"name": "b",
36+
"type": "uint256"
37+
}
38+
],
39+
"type": "function"
1240
}
1341
]

0 commit comments

Comments
 (0)