chainlink
chainlink copied to clipboard
[NODE] base64decode task failing to decode abi.encodePacked(_address) from decode_cbor task
Description
Hi, I'm currently testing the new base64decode
task and it errors with:
failed to decode base64 string: illegal base64 data at input byte 0
input: $(decode_cbor.address)
I'd like to know whether I'm misusing this new task or it is a bug. Thanks
Basic Information
The Solidity contract sends the address base64 encoded (as a map), via:
function request(
bytes32 _specId,
uint256 _payment,
address _address
) external {
Chainlink.Request memory req = buildOperatorRequest(_specId, this.fulfill.selector);
req.addBytes("address", abi.encodePacked(_address));
sendOperatorRequest(req, _payment);
}
The base64decode
task input is vEyg7adkeoq3wgYcLhGKGKk28T0=
(although after certain node version it is not visible anymore in the JSON taskRun):
"taskRuns": [
{
"__typename": "TaskRun",
"id": "ff223dc5-ab89-4566-b94d-40e22977986d",
"createdAt": "2022-08-10T12:29:39.568966Z",
"dotID": "decode_log",
"error": null,
"finishedAt": "2022-08-10T12:29:39.569245Z",
"output": "{"callbackAddr":"0x8F496457cA77f2Fa7c68A6F1B2127F87c0EE453d","callbackFunctionId":"0x5508ff94","cancelExpiration":1660134868,"data":"0x676164647265737354bc4ca0eda7647a8ab7c2061c2e118a18a936f13d","dataVersion":2,"payment":100000000000000000,"requestId":"0x21b4aea459a1148203fdd41f23bb3fcd318b8c19bbb042761f6d634fd809ac46","requester":"0x8F496457cA77f2Fa7c68A6F1B2127F87c0EE453d","specId":"0x3437366462353666313238623466613862306430313538393430393662666536"}",
"type": "ethabidecodelog"
},
{
"__typename": "TaskRun",
"id": "e744f249-c385-456f-a8ea-8fc8e6a979fa",
"createdAt": "2022-08-10T12:29:39.569362Z",
"dotID": "decode_cbor",
"error": null,
"finishedAt": "2022-08-10T12:29:39.569445Z",
"output": "{"address":"0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"}",
"type": "cborparse"
},
{
"__typename": "TaskRun",
"id": "f48a0900-9b97-4e12-9a80-9e6b65dc43b6",
"createdAt": "2022-08-10T12:29:39.569492Z",
"dotID": "base64_decoder",
"error": "failed to decode base64 string: illegal base64 data at input byte 0",
"finishedAt": "2022-08-10T12:29:39.569621Z",
"output": "null",
"type": "base64decode"
}
]
This is how I'd decode it using node.js (for instance in a base64decode
external adapter):
b64addr = "vEyg7adkeoq3wgYcLhGKGKk28T0="
`0x${Buffer.from(b64addr, "base64").toString('hex')}` // '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d'
- Network: Kovan
- Node version: v1.7.0
Environment Variables N/A
Steps to Reproduce
Basic TOML spec:
type = "directrequest"
schemaVersion = 1
name = "Test base64decode task"
externalJobID = "476db56f-128b-4fa8-b0d0-15894096bfe6"
maxTaskDuration = "0s"
contractAddress = "0x878541888a928a31F9EAb4cB61DfD4e381EC2f00"
minContractPaymentLinkJuels = "10000000000000000"
observationSource = """
decode_log [type="ethabidecodelog"
abi="OracleRequest(bytes32 indexed specId, address requester, bytes32 requestId, uint256 payment, address callbackAddr, bytes4 callbackFunctionId, uint256 cancelExpiration, uint256 dataVersion, bytes data)"
data="$(jobRun.logData)"
topics="$(jobRun.logTopics)"]
decode_cbor [type="cborparse" data="$(decode_log.data)"]
base64_decoder [type="base64decode" input="$(decode_cbor.address)"]
decode_log -> decode_cbor -> base64_decoder
"""
This picture shows how the base64 string was shown on old node versions:
Additional Information Related issues: https://github.com/smartcontractkit/chainlink/issues/6555 https://github.com/smartcontractkit/documentation/issues/255
thanks @vnavascues - will take a look.
@vnavascues , thanks to @aelmanaa , I am able to offer you some suggestions. If it wasn't for his excellent help and careful explanations to me I would have struggled with this too!
-
req.addBytes("address", abi.encodePacked(_address))
wont do the trick. It returns bytes (not a base64 encoded string). Your code is still sending bytes, though without the padding. Instead,.encodePacked()
returns ABI ETH spec 32 bytes. Bytes (binary) is different from base64 which is technically a binary-to-string encoding and therefore different from ABI (bytes) encoding. -
Furthermore,
.encodePacked
is unpadded. Instead we need to useencode()
[footnote A] which pads with extra zeroes to achieve 32 bytes, and is what works on the CL Node's ETH ABI Decode Task. Turns out that anything that is not 32 bytes is problematic for the CBOR decoding [footnote B].
So if you use abi.encode()
and change your TOML to use an ethabidecode
task type instead of a base64decode
task type, it will work. The docs for addBytes
method on Chainlink.Request also recommends that we can pass an address using abi.encode [footnote C]
It would look like this:
function request( ) external {
Chainlink.Request memory req = buildOperatorRequest('###JOB_SPEC_ID_HERE', this.fulfill.selector);
req.addBytes("address", abi.encode(address(this)));
sendOperatorRequest(req, (1 * LINK_DIVISIBILITY) / 10);
}
The relevant Decode Task for the TOML job looks like this when you abi.encode() the address.
abidecode [type="ethabidecode"
abi="address address"
data="$(decode_cbor.address)"]
- to play with the base 64 encode task, please use this gist that @aelmanaa has produced. He has used the Open Zeppelin library to convert the address to a base64 String and send that in the request. Note the use of
req.add
instead of.addBytes
.
add
is used for strings, which is what the base 64 encoding produces.
The relevant part of the job's TOML with the tasks looks like this:
observationSource = """
decode_log [type="ethabidecodelog"
abi="OracleRequest(bytes32 indexed specId, address requester, bytes32 requestId, uint256 payment, address callbackAddr, bytes4 callbackFunctionId, uint256 cancelExpiration, uint256 dataVersion, bytes data)"
data="$(jobRun.logData)"
topics="$(jobRun.logTopics)"]
decode_cbor [type="cborparse" data="$(decode_log.data)"]
base64_decoder [type="base64decode" input="$(decode_cbor.address)"]
decode_log -> decode_cbor -> base64_decoder
Once again, a big thanks to @aelmanaa who helped a lot in understanding this.
[A] As per @aelmanaa's comment in Issue #6555, [B] As per @aelmanaa's comment in smartcontractkit/documentation#255 [C] https://docs.chain.link/docs/chainlink-framework/#addbytes
@zeuslawyer @aelmanaa thank you so much for this thorough explanation and the time spent on it, amazing.
- Wrt
req.addBytes()
: I thought that it was sending the data as a base64 encoded string. - Wrt
ethabidecode
: it is the only classic tasks I've barely used. In fact my first time was trying to move away from passing an address asstring
(this is where I found out the aelmanaa issues). Somehow, I misusedethabidecode
(probably due toabi.encodePacked
) and eventually found another way.
I've just successfully tested sending address
and address[]
via addBytes()
and decoding them with ethabidecode
. And I get now the usecase of base64decode
and how send the data from the consumer contract.
Thank you!
no problems @vnavascues - glad this helped.
so did you encode address
an address[]
using encode() before sending them? i.e. it worked when you encoded them to bytes32 ?
no problems @vnavascues - glad this helped.
so did you encode
address
anaddress[]
using encode() before sending them? i.e. it worked when you encoded them to bytes32 ?
Yep, with abi.encode()
it worked:
function request(
bytes32 _specId,
uint256 _payment,
address _address
) external {
Chainlink.Request memory req = buildOperatorRequest(_specId, this.fulfill.selector);
req.addBytes("address", abi.encode(_address));
sendOperatorRequest(req, _payment);
}
function requestArray(
bytes32 _specId,
uint256 _payment,
address[] memory _addresses
) external {
Chainlink.Request memory req = buildOperatorRequest(_specId, this.fulfill.selector);
req.addBytes("addresses", abi.encode(_addresses));
sendOperatorRequest(req, _payment);
}
And the TOML you shared:
abidecode [type="ethabidecode" abi="address address" data="$(decode_cbor.address)"]
abidecode [type="ethabidecode" abi="address[] addresses" data="$(decode_cbor.addresses)"]
Feel free to close the issue
Great, thanks for confirming.