Of course you can.
A better question is : should you ?
Probably not, because to do so you will have to write low level assembly code that's error-prone, difficult to debug / read and possibly make assumptions about the solidity compiler that might not hold over time.
Your input data will be :
- function identifier : 4 bytes
- address[] : N * 20 bytes
- uint256 : 32 bytes
The assumption about the compiler will be the following :
A function can receive more calldata that its signature suggest.
The following function :
function do_sth() public view {}
Should only receive 4 bytes of calldata : the function identifier. It cannot receive less, but it can receive more. There is no saying if this assumption will hold on every subsequent versions of solidity...
Your parameters will therefore be embedded in the calldata, and not accessible from solidity, only from assembly reading directly from calldata.
This can be achieved with the following implementation that just reads from calldata, decodes the embedded input and returns them following the standard ABI specifications :
function do_sth() public view returns (address[] memory, uint256) {
address[] memory ads;
uint256 input;
assembly {
let tmp := 0
// Skip : function selector : 0x4 bytes
let offset := 0x4
// Compute the number of addresses :
// ((array length - 0x04) - 0x20) / 0x14
// ((array length - sizeof(function Selector)) - sizeof(uint256)) / sizeof(address)
let adsCount := div(sub(sub(calldatasize(), 0x04), 0x20), 0x14)
// Allocate memory for the address array
ads := mload(0x40)
mstore(0x40, add(ads, add(0x20, mul(adsCount, 0x20))))
// Set the size of the array
mstore(ads, adsCount)
// Get an address from calldata on each iteration :
// loads 0x20 bytes from calldata starting at offset : calldata[offset: offset + 0x20)
// shift value by 96 bits (12 bytes) to the right to keep only the relevant portion (first 20 bytes)
// store that value at ads[i]
// increments calldata offset by 0x14 (20 bytes)
for {let i := 0} lt(i, adsCount) {i := add(i, 1)} {
tmp := calldataload(offset)
tmp := shr(96, tmp)
mstore(add(add(ads, 0x20), mul(i, 0x20)), tmp)
offset := add(offset, 0x14)
}
// Get the remaining parameter : uint256 (32 bytes)
input := calldataload(offset)
}
return (ads, input);
}
If you were to call this function with the following calldata (you can add as many addresses as you want before the uint256 value / function identifier was omitted):
0xe9e7cea3dedca5984780bafc599bd69add087d56b387c824cafe52b25fbbe793d7b6800b1ddbfe7d00000000000000000000000000000000000000000000000783c5395de63b678d
With this web3 code for example :
const Web3 = require("Web3");
const ENDPOINT = "http://localhost:8545";
const CONTRACT_ADDRESS = "CONTRACT-ADDRESS_HERE";
const YOUR_DATA =
"0xe9e7cea3dedca5984780bafc599bd69add087d56b387c824cafe52b25fbbe793d7b6800b1ddbfe7d00000000000000000000000000000000000000000000000783c5395de63b678d";
let web3 = new Web3(ENDPOINT);
async function main() {
let encodedData =
web3.eth.abi.encodeFunctionSignature({
name: "do_sth",
type: "function",
inputs: [],
}) + YOUR_DATA.replace("0x", "");
let callObject = {
from: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
to: CONTRACT_ADDRESS,
data: encodedData,
};
console.log(
"This call will consume : " +
((await web3.eth.estimateGas(callObject)) + " gas")
);
let output = await web3.eth.call(callObject);
console.log(output);
let decodedData = web3.eth.abi.decodeParameters(
["address[]", "uint256"],
output
);
console.log(decodedData);
}
main();
You get your addresses and your uint value extracted from the calldata :
Result {
'0': [
'0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56',
'0xb387C824CAfE52B25FBbE793d7b6800b1DdBfE7D'
],
'1': '138622266980804814733',
__length__: 2
}
Which cost 23593 gas on default settings, a slight improvement over the 24331 gas cost of this solidity version on the same settings :
function do_sth2(address[] calldata ads, uint256 input) public view returns (address[] memory, uint256) {
return (ads, input);
}
Now you shouldn't do that, and probably don't even need to. You should stick to solidity.
So, it's possible but clearly not recommended.
I hope that answers your question.