diff --git a/CHANGELOG.md b/CHANGELOG.md index b1adcef..a0db30e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## Unreleased +- Fixed AbiGen when in presence of functions that was has leading underscores or casing differences. + + This was generating multiple Rust struct with the same name leading to compilation errors. Now, those cases will be de-duped and you will end up with N Rust struct all suffixed from 1 to N, like `TotalSupply1` and `TotalSupply2`. + - Fixed AbiGen generated `Event#NAME` and `Function#Name` static const in presence of multiple overloads. This was previously using the de-duped name but this was wrong as the intention was always to be the ABI's defined named. diff --git a/abigen-tests/abi/erc721.json b/abigen-tests/abi/erc721.json deleted file mode 100644 index 2c5cd49..0000000 --- a/abigen-tests/abi/erc721.json +++ /dev/null @@ -1,388 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "approved", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "indexed": false, - "internalType": "bool", - "name": "approved", - "type": "bool" - } - ], - "name": "ApprovalForAll", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": true, - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "balance", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "getApproved", - "outputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "operator", - "type": "address" - } - ], - "name": "isApprovedForAll", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "ownerOf", - "outputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "safeTransferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "bool", - "name": "_approved", - "type": "bool" - } - ], - "name": "setApprovalForAll", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes4", - "name": "interfaceId", - "type": "bytes4" - } - ], - "name": "supportsInterface", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - } - ], - "name": "tokenByIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "uint256", - "name": "index", - "type": "uint256" - } - ], - "name": "tokenOfOwnerByIndex", - "outputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "tokenURI", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "tokenId", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] \ No newline at end of file diff --git a/abigen-tests/abi/tests.json b/abigen-tests/abi/tests.json index 9de37d7..8ffb83d 100644 --- a/abigen-tests/abi/tests.json +++ b/abigen-tests/abi/tests.json @@ -640,5 +640,33 @@ "inputs": [{ "name": "arg0", "type": "uint256" }], "outputs": [], "gas": 2310 + }, + { + "stateMutability": "view", + "type": "function", + "name": "funWithOverloadsLeadingUnderscore", + "inputs": [{ "name": "arg0", "type": "int128" }], + "outputs": [] + }, + { + "stateMutability": "view", + "type": "function", + "name": "_funWithOverloadsLeadingUnderscore", + "inputs": [{ "name": "arg0", "type": "uint256" }], + "outputs": [] + }, + { + "stateMutability": "view", + "type": "function", + "name": "funWithOverloadsCasing", + "inputs": [{ "name": "arg0", "type": "int128" }], + "outputs": [] + }, + { + "stateMutability": "view", + "type": "function", + "name": "FunWithOverloadsCasing", + "inputs": [{ "name": "arg0", "type": "uint256" }], + "outputs": [] } ] diff --git a/abigen-tests/src/abi/tests.rs b/abigen-tests/src/abi/tests.rs index 2771ca6..399c287 100644 --- a/abigen-tests/src/abi/tests.rs +++ b/abigen-tests/src/abi/tests.rs @@ -4,12 +4,12 @@ const INTERNAL_ERR: &'static str = "`ethabi_derive` internal error"; pub mod functions { use super::INTERNAL_ERR; #[derive(Debug, Clone, PartialEq)] - pub struct FixedArrayAddressArrayUint256ReturnsUint256String { + pub struct FixedArrayAddressArrayAddressReturnsUint256String { pub param0: [Vec; 2usize], - pub param1: Vec, + pub param1: Vec>, } - impl FixedArrayAddressArrayUint256ReturnsUint256String { - const METHOD_ID: [u8; 4] = [136u8, 229u8, 164u8, 109u8]; + impl FixedArrayAddressArrayAddressReturnsUint256String { + const METHOD_ID: [u8; 4] = [222u8, 196u8, 49u8, 26u8]; pub fn decode( call: &substreams_ethereum::pb::eth::v2::Call, ) -> Result { @@ -23,9 +23,7 @@ pub mod functions { Box::new(ethabi::ParamType::Address), 2usize, ), - ethabi::ParamType::Array( - Box::new(ethabi::ParamType::Uint(256usize)), - ), + ethabi::ParamType::Array(Box::new(ethabi::ParamType::Address)), ], maybe_data.unwrap(), ) @@ -51,12 +49,7 @@ pub mod functions { .expect(INTERNAL_ERR) .into_iter() .map(|inner| { - let mut v = [0 as u8; 32]; - inner - .into_uint() - .expect(INTERNAL_ERR) - .to_big_endian(v.as_mut_slice()); - substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + inner.into_address().expect(INTERNAL_ERR).as_bytes().to_vec() }) .collect(), }) @@ -78,17 +71,8 @@ pub mod functions { let v = self .param1 .iter() - .map(|inner| ethabi::Token::Uint( - ethabi::Uint::from_big_endian( - match inner.clone().to_bytes_be() { - (num_bigint::Sign::Plus, bytes) => bytes, - (num_bigint::Sign::NoSign, bytes) => bytes, - (num_bigint::Sign::Minus, _) => { - panic!("negative numbers are not supported") - } - } - .as_slice(), - ), + .map(|inner| ethabi::Token::Address( + ethabi::Address::from_slice(&inner), )) .collect(); ethabi::Token::Array(v) @@ -161,8 +145,8 @@ pub mod functions { } } impl substreams_ethereum::Function - for FixedArrayAddressArrayUint256ReturnsUint256String { - const NAME: &'static str = "FixedArrayAddressArrayUint256ReturnsUint256String"; + for FixedArrayAddressArrayAddressReturnsUint256String { + const NAME: &'static str = "fixedArrayAddressArrayAddressReturnsUint256String"; fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { Self::match_call(call) } @@ -176,18 +160,18 @@ pub mod functions { } } impl substreams_ethereum::rpc::RPCDecodable<(substreams::scalar::BigInt, String)> - for FixedArrayAddressArrayUint256ReturnsUint256String { + for FixedArrayAddressArrayAddressReturnsUint256String { fn output(data: &[u8]) -> Result<(substreams::scalar::BigInt, String), String> { Self::output(data) } } #[derive(Debug, Clone, PartialEq)] - pub struct FixedArrayAddressArrayAddressReturnsUint256String { + pub struct FixedArrayAddressArrayUint256ReturnsUint256String { pub param0: [Vec; 2usize], - pub param1: Vec>, + pub param1: Vec, } - impl FixedArrayAddressArrayAddressReturnsUint256String { - const METHOD_ID: [u8; 4] = [222u8, 196u8, 49u8, 26u8]; + impl FixedArrayAddressArrayUint256ReturnsUint256String { + const METHOD_ID: [u8; 4] = [136u8, 229u8, 164u8, 109u8]; pub fn decode( call: &substreams_ethereum::pb::eth::v2::Call, ) -> Result { @@ -201,7 +185,9 @@ pub mod functions { Box::new(ethabi::ParamType::Address), 2usize, ), - ethabi::ParamType::Array(Box::new(ethabi::ParamType::Address)), + ethabi::ParamType::Array( + Box::new(ethabi::ParamType::Uint(256usize)), + ), ], maybe_data.unwrap(), ) @@ -227,7 +213,12 @@ pub mod functions { .expect(INTERNAL_ERR) .into_iter() .map(|inner| { - inner.into_address().expect(INTERNAL_ERR).as_bytes().to_vec() + let mut v = [0 as u8; 32]; + inner + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) }) .collect(), }) @@ -249,8 +240,17 @@ pub mod functions { let v = self .param1 .iter() - .map(|inner| ethabi::Token::Address( - ethabi::Address::from_slice(&inner), + .map(|inner| ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match inner.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), )) .collect(); ethabi::Token::Array(v) @@ -323,8 +323,8 @@ pub mod functions { } } impl substreams_ethereum::Function - for FixedArrayAddressArrayAddressReturnsUint256String { - const NAME: &'static str = "fixedArrayAddressArrayAddressReturnsUint256String"; + for FixedArrayAddressArrayUint256ReturnsUint256String { + const NAME: &'static str = "FixedArrayAddressArrayUint256ReturnsUint256String"; fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { Self::match_call(call) } @@ -338,7 +338,7 @@ pub mod functions { } } impl substreams_ethereum::rpc::RPCDecodable<(substreams::scalar::BigInt, String)> - for FixedArrayAddressArrayAddressReturnsUint256String { + for FixedArrayAddressArrayUint256ReturnsUint256String { fn output(data: &[u8]) -> Result<(substreams::scalar::BigInt, String), String> { Self::output(data) } @@ -1803,6 +1803,320 @@ pub mod functions { self.encode() } } + #[derive(Debug, Clone, PartialEq)] + pub struct FunWithOverloadsCasing1 { + pub arg0: substreams::scalar::BigInt, + } + impl FunWithOverloadsCasing1 { + const METHOD_ID: [u8; 4] = [207u8, 112u8, 174u8, 135u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + arg0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.arg0.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for FunWithOverloadsCasing1 { + const NAME: &'static str = "FunWithOverloadsCasing"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct FunWithOverloadsCasing2 { + pub arg0: substreams::scalar::BigInt, + } + impl FunWithOverloadsCasing2 { + const METHOD_ID: [u8; 4] = [46u8, 173u8, 55u8, 142u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Int(128usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + arg0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + { + let non_full_signed_bytes = self.arg0.to_signed_bytes_be(); + let full_signed_bytes_init = if non_full_signed_bytes[0] & 0x80 + == 0x80 + { + 0xff + } else { + 0x00 + }; + let mut full_signed_bytes = [full_signed_bytes_init as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int( + ethabi::Int::from_big_endian(full_signed_bytes.as_ref()), + ) + }, + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for FunWithOverloadsCasing2 { + const NAME: &'static str = "funWithOverloadsCasing"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct FunWithOverloadsLeadingUnderscore1 { + pub arg0: substreams::scalar::BigInt, + } + impl FunWithOverloadsLeadingUnderscore1 { + const METHOD_ID: [u8; 4] = [140u8, 162u8, 116u8, 137u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + arg0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.arg0.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for FunWithOverloadsLeadingUnderscore1 { + const NAME: &'static str = "_funWithOverloadsLeadingUnderscore"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct FunWithOverloadsLeadingUnderscore2 { + pub arg0: substreams::scalar::BigInt, + } + impl FunWithOverloadsLeadingUnderscore2 { + const METHOD_ID: [u8; 4] = [106u8, 55u8, 235u8, 170u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Int(128usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + arg0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + { + let non_full_signed_bytes = self.arg0.to_signed_bytes_be(); + let full_signed_bytes_init = if non_full_signed_bytes[0] & 0x80 + == 0x80 + { + 0xff + } else { + 0x00 + }; + let mut full_signed_bytes = [full_signed_bytes_init as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int( + ethabi::Int::from_big_endian(full_signed_bytes.as_ref()), + ) + }, + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for FunWithOverloadsLeadingUnderscore2 { + const NAME: &'static str = "funWithOverloadsLeadingUnderscore"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } } /// Contract's events. #[allow(dead_code, unused_imports, unused_variables)] diff --git a/abigen-tests/src/lib.rs b/abigen-tests/src/lib.rs index ac401bd..d0ede06 100644 --- a/abigen-tests/src/lib.rs +++ b/abigen-tests/src/lib.rs @@ -898,4 +898,22 @@ mod tests { assert!(Fun1::NAME == "funWithOverloads"); assert!(Fun2::NAME == "funWithOverloads"); } + + #[test] + fn it_dedup_function_with_leading_underscore_difference() { + use tests::functions::FunWithOverloadsLeadingUnderscore1 as Fun1; + use tests::functions::FunWithOverloadsLeadingUnderscore2 as Fun2; + + assert!(Fun1::NAME == "_funWithOverloadsLeadingUnderscore"); + assert!(Fun2::NAME == "funWithOverloadsLeadingUnderscore"); + } + + #[test] + fn it_dedup_function_with_casing_difference() { + use tests::functions::FunWithOverloadsCasing1 as Fun1; + use tests::functions::FunWithOverloadsCasing2 as Fun2; + + assert!(Fun1::NAME == "FunWithOverloadsCasing"); + assert!(Fun2::NAME == "funWithOverloadsCasing"); + } } diff --git a/abigen/src/contract.rs b/abigen/src/contract.rs index 67a60e0..13c50ba 100644 --- a/abigen/src/contract.rs +++ b/abigen/src/contract.rs @@ -6,6 +6,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use std::collections::BTreeMap; + +use heck::ToUpperCamelCase; use proc_macro2::TokenStream; use quote::quote; @@ -40,17 +43,29 @@ impl<'a> From<&'a ethabi::Contract> for Contract { // Since some people will actually commit this code, we use a "stable" generation order events.sort_by(|left: &Event, right: &Event| left.name.cmp(&right.name)); - let mut functions: Vec<_> = c - .functions - .values() - .flat_map(|functions| { + let mut function_by_rust_struct_name = BTreeMap::>::new(); + for (_, functions) in c.functions.iter() { + for function in functions { + let sanitized_name = function.name.to_upper_camel_case(); + + if let Some(existing) = function_by_rust_struct_name.get_mut(&sanitized_name) { + existing.push(function); + } else { + function_by_rust_struct_name.insert(sanitized_name, vec![function]); + } + } + } + + let mut functions: Vec<_> = function_by_rust_struct_name + .iter() + .flat_map(|(sanitized_name, functions)| { let count = functions.len(); functions.iter().enumerate().map(move |(index, function)| { if count <= 1 { - (&function.name, function).into() + (sanitized_name.clone(), *function).into() } else { - (&format!("{}{}", function.name, index + 1), function).into() + (format!("{}{}", sanitized_name, index + 1), *function).into() } }) }) diff --git a/abigen/src/function.rs b/abigen/src/function.rs index 120425a..5619c8f 100644 --- a/abigen/src/function.rs +++ b/abigen/src/function.rs @@ -44,8 +44,8 @@ pub struct Function { outputs: Outputs, } -impl<'a> From<(&'a String, &'a ethabi::Function)> for Function { - fn from((name, f): (&'a String, &'a ethabi::Function)) -> Self { +impl<'a> From<(String, &'a ethabi::Function)> for Function { + fn from((name, f): (String, &'a ethabi::Function)) -> Self { // [param0, hello_world, param2] let input_names = param_names(&f.inputs); @@ -180,7 +180,7 @@ impl<'a> From<(&'a String, &'a ethabi::Function)> for Function { // it must go on the entire struct #[allow(deprecated)] Function { - name: name.clone(), + name, original_name: f.name.clone(), short_signature: f.short_signature(), inputs: Inputs {