Found the answer. It seems that my contract address was wrong in my .env file (whoops). After I corrected that I used the code below.
from eth_abi import encode_abi
test_encode = encode_abi(['uint', 'uint', 'address'], [1, 2, '0xAD5ce863aE3E4E9394Ab43d4ba0D80f419F61789'],) # Basically map the types to the values
pool = contract.functions.sendBytes(test_encode).call()
print(pool)
outputs
['0xAD5ce863aE3E4E9394Ab43d4ba0D80f419F61789']
If you want to use arrays you can do
test_encode = encode_abi(['uint[]', 'uint[]', 'address'], [[1,2], [3,4], '0xAD5ce863aE3E4E9394Ab43d4ba0D80f419F61789'],)
I could not use
contract.encodeABI(fn_name='sendBytes', args=[test_encode], data=test_encode)
I believe I was using it wrong, and the data it outputs is a string. When you input the value into remix it works. I can only assume remix handles things differently. Maybe someone can explain how to use this properly in the comments.
If your data was in calldata (data location) you could simply use slices.
Unfortunately, slices are not available on memory arrays.
how would I be able to retrieve the string "hello"? There is no
abi.decodeWithSelector, or is there? For my use-case it would even be
sufficient to just shift the entire data by 8 bytes to the left, but
then shl(n,data) (assembly) doesn't work for reference types.
shl
doesn't care about reference or value types, it just takes a value and shifts it. Your issue is that you didn't gave it the right value : data
is a memory pointer, while you want to shift the actual data contained there.
Now you will need to do a bit more than just shifting if your data is more than 32 bytes long (which is always true with dynamic types such as string
due to the way abi encoding works).
To do so, you could use the following function, which works regardless of length, type or number of arguments :
function extractCalldata(bytes memory calldataWithSelector) internal pure returns (bytes memory) {
bytes memory calldataWithoutSelector;
require(calldataWithSelector.length >= 4);
assembly {
let totalLength := mload(calldataWithSelector)
let targetLength := sub(totalLength, 4)
calldataWithoutSelector := mload(0x40)
// Set the length of callDataWithoutSelector (initial length - 4)
mstore(calldataWithoutSelector, targetLength)
// Mark the memory space taken for callDataWithoutSelector as allocated
mstore(0x40, add(calldataWithoutSelector, add(0x20, targetLength)))
// Process first 32 bytes (we only take the last 28 bytes)
mstore(add(calldataWithoutSelector, 0x20), shl(0x20, mload(add(calldataWithSelector, 0x20))))
// Process all other data by chunks of 32 bytes
for { let i := 0x1C } lt(i, targetLength) { i := add(i, 0x20) } {
mstore(add(add(calldataWithoutSelector, 0x20), i), mload(add(add(calldataWithSelector, 0x20), add(i, 0x04))))
}
}
return calldataWithoutSelector;
}
And call it to extract the part of interest :
function example() public pure returns (string memory) {
string memory helloString = "hello";
bytes memory dataWithSelector = abi.encodeWithSelector(0x12345678, helloString);
return abi.decode(extractCalldata(dataWithSelector), (string));
}
Which outputs hello
as expected.
I hope this answers your question.
Best Answer
You have to remove the first four bytes first. The first four bytes of a contract call is the function selector, which specifies which function you want to call, in this case
balanceOf(address)
. I wrote a more detailed explanation about transaction data, which you can find here.Assuming you're using Solidity 0.6.0 or newer, you can use
data[4:]
to slice off the first four bytes of the input, or additionally just remove them from the input if you're hardcoding the data.