/*******************************************************************************
* (c) 2022 Zondax AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
//
// THIS CODE WAS SECURITY REVIEWED BY KUDELSKI SECURITY, BUT NOT FORMALLY AUDITED
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.17;
import "./types/AccountTypes.sol";
import "./cbor/AccountCbor.sol";
import "./cbor/BytesCbor.sol";
import "./utils/Misc.sol";
import "./utils/Actor.sol";
/// @title This library is a proxy to the Account actor. Calling one of its methods will result in a cross-actor call being performed.
/// @author Zondax AG
library AccountAPI {
using AccountCBOR for *;
using BytesCBOR for bytes;
/// @notice Authenticates whether the provided signature is valid for the provided message.
/// @dev Should be called with the raw bytes of a signature, NOT a serialized Signature object that includes a SignatureType.
/// @dev Errors if the authentication is invalid.
/// @param target The account actor id you want to interact with
/// @param params message to be authenticated
function authenticateMessage(CommonTypes.FilActorId target, AccountTypes.AuthenticateMessageParams memory params) internal {
bytes memory raw_request = params.serializeAuthenticateMessageParams();
bytes memory data = Actor.callNonSingletonByID(target, AccountTypes.AuthenticateMessageMethodNum, Misc.CBOR_CODEC, raw_request, 0, true);
require(data.deserializeBool());
}
}
/*******************************************************************************
* (c) 2022 Zondax AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
//
// THIS CODE WAS SECURITY REVIEWED BY KUDELSKI SECURITY, BUT NOT FORMALLY AUDITED
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.17;
import "./CommonTypes.sol";
/// @title Filecoin account actor types for Solidity.
/// @author Zondax AG
library AccountTypes {
uint constant AuthenticateMessageMethodNum = 2643134072;
/// @param it should be a raw byte of signature, NOT a serialized signature object with a signatureType.
/// @param message The message which is signed by the corresponding account address.
struct AuthenticateMessageParams {
bytes signature;
bytes message;
}
}
/*******************************************************************************
* (c) 2022 Zondax AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
//
// THIS CODE WAS SECURITY REVIEWED BY KUDELSKI SECURITY, BUT NOT FORMALLY AUDITED
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.17;
import "solidity-cborutils/contracts/CBOR.sol";
import "../types/AccountTypes.sol";
import "../utils/CborDecode.sol";
import "../utils/Misc.sol";
/// @title This library is a set of functions meant to handle CBOR parameters serialization and return values deserialization for Account actor exported methods.
/// @author Zondax AG
library AccountCBOR {
using CBOR for CBOR.CBORBuffer;
using CBORDecoder for bytes;
/// @notice serialize AuthenticateMessageParams struct to cbor in order to pass as arguments to an account actor
/// @param params AuthenticateMessageParams to serialize as cbor
/// @return cbor serialized data as bytes
function serializeAuthenticateMessageParams(AccountTypes.AuthenticateMessageParams memory params) internal pure returns (bytes memory) {
uint256 capacity = 0;
capacity += Misc.getPrefixSize(2);
capacity += Misc.getBytesSize(params.signature);
capacity += Misc.getBytesSize(params.message);
CBOR.CBORBuffer memory buf = CBOR.create(capacity);
buf.startFixedArray(2);
buf.writeBytes(params.signature);
buf.writeBytes(params.message);
return buf.data();
}
/// @notice deserialize AuthenticateMessageParams struct from cbor encoded bytes coming from an account actor call
/// @param rawResp cbor encoded response
/// @return ret new instance of AuthenticateMessageParams created based on parsed data
function deserializeAuthenticateMessageParams(bytes memory rawResp) internal pure returns (AccountTypes.AuthenticateMessageParams memory ret) {
uint byteIdx = 0;
uint len;
(len, byteIdx) = rawResp.readFixedArray(byteIdx);
assert(len == 2);
(ret.signature, byteIdx) = rawResp.readBytes(byteIdx);
(ret.message, byteIdx) = rawResp.readBytes(byteIdx);
return ret;
}
}
/*******************************************************************************
* (c) 2022 Zondax AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
//
// THIS CODE WAS SECURITY REVIEWED BY KUDELSKI SECURITY, BUT NOT FORMALLY AUDITED
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.17;
import "solidity-cborutils/contracts/CBOR.sol";
import "../utils/CborDecode.sol";
import "../utils/Misc.sol";
import "../types/CommonTypes.sol";
import "./BigIntCbor.sol";
/// @title This library is a set of functions meant to handle CBOR serialization and deserialization for bytes
/// @author Zondax AG
library BytesCBOR {
using CBOR for CBOR.CBORBuffer;
using CBORDecoder for bytes;
using BigIntCBOR for bytes;
/// @notice serialize raw bytes as cbor bytes string encoded
/// @param data raw data in bytes
/// @return encoded cbor bytes
function serializeBytes(bytes memory data) internal pure returns (bytes memory) {
uint256 capacity = Misc.getBytesSize(data);
CBOR.CBORBuffer memory buf = CBOR.create(capacity);
buf.writeBytes(data);
return buf.data();
}
/// @notice serialize raw address (in bytes) as cbor bytes string encoded (how an address is passed to filecoin actors)
/// @param addr raw address in bytes
/// @return encoded address as cbor bytes
function serializeAddress(bytes memory addr) internal pure returns (bytes memory) {
return serializeBytes(addr);
}
/// @notice encoded null value as cbor
/// @return cbor encoded null
function serializeNull() internal pure returns (bytes memory) {
CBOR.CBORBuffer memory buf = CBOR.create(1);
buf.writeNull();
return buf.data();
}
/// @notice deserialize cbor encoded filecoin address to bytes
/// @param ret cbor encoded filecoin address
/// @return raw bytes representing a filecoin address
function deserializeAddress(bytes memory ret) internal pure returns (bytes memory) {
bytes memory addr;
uint byteIdx = 0;
(addr, byteIdx) = ret.readBytes(byteIdx);
return addr;
}
/// @notice deserialize cbor encoded string
/// @param ret cbor encoded string (in bytes)
/// @return decoded string
function deserializeString(bytes memory ret) internal pure returns (string memory) {
string memory response;
uint byteIdx = 0;
(response, byteIdx) = ret.readString(byteIdx);
return response;
}
/// @notice deserialize cbor encoded bool
/// @param ret cbor encoded bool (in bytes)
/// @return decoded bool
function deserializeBool(bytes memory ret) internal pure returns (bool) {
bool response;
uint byteIdx = 0;
(response, byteIdx) = ret.readBool(byteIdx);
return response;
}
/// @notice deserialize cbor encoded BigInt
/// @param ret cbor encoded BigInt (in bytes)
/// @return decoded BigInt
/// @dev BigInts are cbor encoded as bytes string first. That is why it unwraps the cbor encoded bytes first, and then parse the result into BigInt
function deserializeBytesBigInt(bytes memory ret) internal pure returns (CommonTypes.BigInt memory) {
bytes memory tmp;
uint byteIdx = 0;
if (ret.length > 0) {
(tmp, byteIdx) = ret.readBytes(byteIdx);
if (tmp.length > 0) {
return tmp.deserializeBigInt();
}
}
return CommonTypes.BigInt(new bytes(0), false);
}
/// @notice deserialize cbor encoded uint64
/// @param rawResp cbor encoded uint64 (in bytes)
/// @return decoded uint64
function deserializeUint64(bytes memory rawResp) internal pure returns (uint64) {
uint byteIdx = 0;
uint64 value;
(value, byteIdx) = rawResp.readUInt64(byteIdx);
return value;
}
/// @notice deserialize cbor encoded int64
/// @param rawResp cbor encoded int64 (in bytes)
/// @return decoded int64
function deserializeInt64(bytes memory rawResp) internal pure returns (int64) {
uint byteIdx = 0;
int64 value;
(value, byteIdx) = rawResp.readInt64(byteIdx);
return value;
}
}
/*******************************************************************************
* (c) 2022 Zondax AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
// THIS CODE WAS SECURITY REVIEWED BY KUDELSKI SECURITY, BUT NOT FORMALLY AUDITED
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.17;
import "../types/CommonTypes.sol";
/// @title Library containing miscellaneous functions used on the project
/// @author Zondax AG
library Misc {
uint64 constant DAG_CBOR_CODEC = 0x71;
uint64 constant CBOR_CODEC = 0x51;
uint64 constant NONE_CODEC = 0x00;
// Code taken from Openzeppelin repo
// Link: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/0320a718e8e07b1d932f5acb8ad9cec9d9eed99b/contracts/utils/math/SignedMath.sol#L37-L42
/// @notice get the abs from a signed number
/// @param n number to get abs from
/// @return unsigned number
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
/// @notice validate if an address exists or not
/// @dev read this article for more information https://blog.finxter.com/how-to-find-out-if-an-ethereum-address-is-a-contract/
/// @param addr address to check
/// @return whether the address exists or not
function addressExists(address addr) internal view returns (bool) {
bytes32 codehash;
assembly {
codehash := extcodehash(addr)
}
return codehash != 0x0;
}
/// Returns the data size required by CBOR.writeFixedNumeric
function getPrefixSize(uint256 data_size) internal pure returns (uint256) {
if (data_size <= 23) {
return 1;
} else if (data_size <= 0xFF) {
return 2;
} else if (data_size <= 0xFFFF) {
return 3;
} else if (data_size <= 0xFFFFFFFF) {
return 5;
}
return 9;
}
function getBytesSize(bytes memory value) internal pure returns (uint256) {
return getPrefixSize(value.length) + value.length;
}
function getCidSize(bytes memory value) internal pure returns (uint256) {
return getPrefixSize(2) + value.length;
}
function getFilActorIdSize(CommonTypes.FilActorId value) internal pure returns (uint256) {
uint64 val = CommonTypes.FilActorId.unwrap(value);
return getPrefixSize(uint256(val));
}
function getChainEpochSize(CommonTypes.ChainEpoch value) internal pure returns (uint256) {
int64 val = CommonTypes.ChainEpoch.unwrap(value);
if (val >= 0) {
return getPrefixSize(uint256(uint64(val)));
} else {
return getPrefixSize(uint256(uint64(-1 - val)));
}
}
function getBoolSize() internal pure returns (uint256) {
return getPrefixSize(1);
}
}
/*******************************************************************************
* (c) 2022 Zondax AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
// THIS CODE WAS SECURITY REVIEWED BY KUDELSKI SECURITY, BUT NOT FORMALLY AUDITED
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.17;
import "./Misc.sol";
import "../types/CommonTypes.sol";
/// @title Call actors utilities library, meant to interact with Filecoin builtin actors
/// @author Zondax AG
library Actor {
/// @notice precompile address for the call_actor precompile
address constant CALL_ACTOR_ADDRESS = 0xfe00000000000000000000000000000000000003;
/// @notice precompile address for the call_actor_id precompile
address constant CALL_ACTOR_ID = 0xfe00000000000000000000000000000000000005;
/// @notice flag used to indicate that the call_actor or call_actor_id should perform a static_call to the desired actor
uint64 constant READ_ONLY_FLAG = 0x00000001;
/// @notice flag used to indicate that the call_actor or call_actor_id should perform a delegate_call to the desired actor
uint64 constant DEFAULT_FLAG = 0x00000000;
/// @notice the provided address is not valid
error InvalidAddress(bytes addr);
/// @notice the smart contract has no enough balance to transfer
error NotEnoughBalance(uint256 balance, uint256 value);
/// @notice the provided actor id is not valid
error InvalidActorID(CommonTypes.FilActorId actorId);
/// @notice an error happened trying to call the actor
error FailToCallActor();
/// @notice the response received is not correct. In some case no response is expected and we received one, or a response was indeed expected and we received none.
error InvalidResponseLength();
/// @notice the codec received is not valid
error InvalidCodec(uint64);
/// @notice the called actor returned an error as part of its expected behaviour
error ActorError(int256 errorCode);
/// @notice the actor is not found
error ActorNotFound();
/// @notice allows to interact with an specific actor by its address (bytes format)
/// @param actor_address actor address (bytes format) to interact with
/// @param method_num id of the method from the actor to call
/// @param codec how the request data passed as argument is encoded
/// @param raw_request encoded arguments to be passed in the call
/// @param value tokens to be transferred to the called actor
/// @param static_call indicates if the call will be allowed to change the actor state or not (just read the state)
/// @return payload (in bytes) with the actual response data (without codec or response code)
function callByAddress(
bytes memory actor_address,
uint256 method_num,
uint64 codec,
bytes memory raw_request,
uint256 value,
bool static_call
) internal returns (bytes memory) {
if (actor_address.length < 2) {
revert InvalidAddress(actor_address);
}
validatePrecompileCall(CALL_ACTOR_ADDRESS, value);
// We have to delegate-call the call-actor precompile because the call-actor precompile will
// call the target actor on our behalf. This will _not_ delegate to the target `actor_address`.
//
// Specifically:
//
// - `static_call == false`: `CALLER (you) --(DELEGATECALL)-> CALL_ACTOR_PRECOMPILE --(CALL)-> actor_address
// - `static_call == true`: `CALLER (you) --(DELEGATECALL)-> CALL_ACTOR_PRECOMPILE --(STATICCALL)-> actor_address
(bool success, bytes memory data) = address(CALL_ACTOR_ADDRESS).delegatecall(
abi.encode(uint64(method_num), value, static_call ? READ_ONLY_FLAG : DEFAULT_FLAG, codec, raw_request, actor_address)
);
if (!success) {
revert FailToCallActor();
}
return readRespData(data);
}
/// @notice allows to interact with an specific actor by its id (uint64)
/// @param target actor id (uint64) to interact with
/// @param method_num id of the method from the actor to call
/// @param codec how the request data passed as argument is encoded
/// @param raw_request encoded arguments to be passed in the call
/// @param value tokens to be transferred to the called actor
/// @param static_call indicates if the call will be allowed to change the actor state or not (just read the state)
/// @return payload (in bytes) with the actual response data (without codec or response code)
function callByID(
CommonTypes.FilActorId target,
uint256 method_num,
uint64 codec,
bytes memory raw_request,
uint256 value,
bool static_call
) internal returns (bytes memory) {
validatePrecompileCall(CALL_ACTOR_ID, value);
(bool success, bytes memory data) = address(CALL_ACTOR_ID).delegatecall(
abi.encode(uint64(method_num), value, static_call ? READ_ONLY_FLAG : DEFAULT_FLAG, codec, raw_request, target)
);
if (!success) {
revert FailToCallActor();
}
return readRespData(data);
}
/// @notice allows to run some generic validations before calling the precompile actor
/// @param addr precompile actor address to run check to
/// @param value tokens to be transferred to the called actor
function validatePrecompileCall(address addr, uint256 value) internal view {
uint balance = address(this).balance;
if (balance < value) {
revert NotEnoughBalance(balance, value);
}
bool actorExists = Misc.addressExists(addr);
if (!actorExists) {
revert ActorNotFound();
}
}
/// @notice allows to interact with an non-singleton actors by its id (uint64)
/// @param target actor id (uint64) to interact with
/// @param method_num id of the method from the actor to call
/// @param codec how the request data passed as argument is encoded
/// @param raw_request encoded arguments to be passed in the call
/// @param value tokens to be transfered to the called actor
/// @param static_call indicates if the call will be allowed to change the actor state or not (just read the state)
/// @dev it requires the id to be bigger than 99, as singleton actors are smaller than that
function callNonSingletonByID(
CommonTypes.FilActorId target,
uint256 method_num,
uint64 codec,
bytes memory raw_request,
uint256 value,
bool static_call
) internal returns (bytes memory) {
if (CommonTypes.FilActorId.unwrap(target) < 100) {
revert InvalidActorID(target);
}
return callByID(target, method_num, codec, raw_request, value, static_call);
}
/// @notice parse the response an actor returned
/// @notice it will validate the return code (success) and the codec (valid one)
/// @param raw_response raw data (bytes) the actor returned
/// @return the actual raw data (payload, in bytes) to be parsed according to the actor and method called
function readRespData(bytes memory raw_response) internal pure returns (bytes memory) {
(int256 exit, uint64 return_codec, bytes memory return_value) = abi.decode(raw_response, (int256, uint64, bytes));
if (return_codec == Misc.NONE_CODEC) {
if (return_value.length != 0) {
revert InvalidResponseLength();
}
} else if (return_codec == Misc.CBOR_CODEC || return_codec == Misc.DAG_CBOR_CODEC) {
if (return_value.length == 0) {
revert InvalidResponseLength();
}
} else {
revert InvalidCodec(return_codec);
}
if (exit != 0) {
revert ActorError(exit);
}
return return_value;
}
}
/*******************************************************************************
* (c) 2022 Zondax AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
//
// THIS CODE WAS SECURITY REVIEWED BY KUDELSKI SECURITY, BUT NOT FORMALLY AUDITED
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.17;
import "../cbor/BigIntCbor.sol";
/// @title Filecoin actors' common types for Solidity.
/// @author Zondax AG
library CommonTypes {
uint constant UniversalReceiverHookMethodNum = 3726118371;
/// @param idx index for the failure in batch
/// @param code failure code
struct FailCode {
uint32 idx;
uint32 code;
}
/// @param success_count total successes in batch
/// @param fail_codes list of failures code and index for each failure in batch
struct BatchReturn {
uint32 success_count;
FailCode[] fail_codes;
}
/// @param type_ asset type
/// @param payload payload corresponding to asset type
struct UniversalReceiverParams {
uint32 type_;
bytes payload;
}
/// @param val contains the actual arbitrary number written as binary
/// @param neg indicates if val is negative or not
struct BigInt {
bytes val;
bool neg;
}
/// @param data filecoin address in bytes format
struct FilAddress {
bytes data;
}
/// @param data cid in bytes format
struct Cid {
bytes data;
}
/// @param data deal proposal label in bytes format (it can be utf8 string or arbitrary bytes string).
/// @param isString indicates if the data is string or raw bytes
struct DealLabel {
bytes data;
bool isString;
}
type FilActorId is uint64;
type ChainEpoch is int64;
}
/*******************************************************************************
* (c) 2022 Zondax AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
//
// THIS CODE WAS SECURITY REVIEWED BY KUDELSKI SECURITY, BUT NOT FORMALLY AUDITED
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.17;
import "../types/CommonTypes.sol";
/// @title This library is a set of functions meant to handle CBOR serialization and deserialization for BigInt type
/// @author Zondax AG
library BigIntCBOR {
/// @notice serialize BigInt instance to bytes
/// @param num BigInt instance to serialize
/// @return serialized BigInt as bytes
function serializeBigInt(CommonTypes.BigInt memory num) internal pure returns (bytes memory) {
bytes memory raw = new bytes(num.val.length + 1);
raw[0] = num.neg == true ? bytes1(0x01) : bytes1(0x00);
uint index = 1;
for (uint i = 0; i < num.val.length; i++) {
raw[index] = num.val[i];
index++;
}
return raw;
}
/// @notice deserialize big int (encoded as bytes) to BigInt instance
/// @param raw as bytes to parse
/// @return parsed BigInt instance
function deserializeBigInt(bytes memory raw) internal pure returns (CommonTypes.BigInt memory) {
if (raw.length == 0) {
return CommonTypes.BigInt(hex"00", false);
}
bytes memory val = new bytes(raw.length - 1);
bool neg = false;
if (raw[0] == 0x01) {
neg = true;
}
for (uint i = 1; i < raw.length; i++) {
val[i - 1] = raw[i];
}
return CommonTypes.BigInt(val, neg);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@ensdomains/buffer/contracts/Buffer.sol";
/**
* @dev A library for populating CBOR encoded payload in Solidity.
*
* https://datatracker.ietf.org/doc/html/rfc7049
*
* The library offers various write* and start* methods to encode values of different types.
* The resulted buffer can be obtained with data() method.
* Encoding of primitive types is staightforward, whereas encoding of sequences can result
* in an invalid CBOR if start/write/end flow is violated.
* For the purpose of gas saving, the library does not verify start/write/end flow internally,
* except for nested start/end pairs.
*/
library CBOR {
using Buffer for Buffer.buffer;
struct CBORBuffer {
Buffer.buffer buf;
uint256 depth;
}
uint8 private constant MAJOR_TYPE_INT = 0;
uint8 private constant MAJOR_TYPE_NEGATIVE_INT = 1;
uint8 private constant MAJOR_TYPE_BYTES = 2;
uint8 private constant MAJOR_TYPE_STRING = 3;
uint8 private constant MAJOR_TYPE_ARRAY = 4;
uint8 private constant MAJOR_TYPE_MAP = 5;
uint8 private constant MAJOR_TYPE_TAG = 6;
uint8 private constant MAJOR_TYPE_CONTENT_FREE = 7;
uint8 private constant TAG_TYPE_BIGNUM = 2;
uint8 private constant TAG_TYPE_NEGATIVE_BIGNUM = 3;
uint8 private constant CBOR_FALSE = 20;
uint8 private constant CBOR_TRUE = 21;
uint8 private constant CBOR_NULL = 22;
uint8 private constant CBOR_UNDEFINED = 23;
function create(uint256 capacity) internal pure returns(CBORBuffer memory cbor) {
Buffer.init(cbor.buf, capacity);
cbor.depth = 0;
return cbor;
}
function data(CBORBuffer memory buf) internal pure returns(bytes memory) {
require(buf.depth == 0, "Invalid CBOR");
return buf.buf.buf;
}
function writeUInt256(CBORBuffer memory buf, uint256 value) internal pure {
buf.buf.appendUint8(uint8((MAJOR_TYPE_TAG << 5) | TAG_TYPE_BIGNUM));
writeBytes(buf, abi.encode(value));
}
function writeInt256(CBORBuffer memory buf, int256 value) internal pure {
if (value < 0) {
buf.buf.appendUint8(
uint8((MAJOR_TYPE_TAG << 5) | TAG_TYPE_NEGATIVE_BIGNUM)
);
writeBytes(buf, abi.encode(uint256(-1 - value)));
} else {
writeUInt256(buf, uint256(value));
}
}
function writeUInt64(CBORBuffer memory buf, uint64 value) internal pure {
writeFixedNumeric(buf, MAJOR_TYPE_INT, value);
}
function writeInt64(CBORBuffer memory buf, int64 value) internal pure {
if(value >= 0) {
writeFixedNumeric(buf, MAJOR_TYPE_INT, uint64(value));
} else{
writeFixedNumeric(buf, MAJOR_TYPE_NEGATIVE_INT, uint64(-1 - value));
}
}
function writeBytes(CBORBuffer memory buf, bytes memory value) internal pure {
writeFixedNumeric(buf, MAJOR_TYPE_BYTES, uint64(value.length));
buf.buf.append(value);
}
function writeString(CBORBuffer memory buf, string memory value) internal pure {
writeFixedNumeric(buf, MAJOR_TYPE_STRING, uint64(bytes(value).length));
buf.buf.append(bytes(value));
}
function writeBool(CBORBuffer memory buf, bool value) internal pure {
writeContentFree(buf, value ? CBOR_TRUE : CBOR_FALSE);
}
function writeNull(CBORBuffer memory buf) internal pure {
writeContentFree(buf, CBOR_NULL);
}
function writeUndefined(CBORBuffer memory buf) internal pure {
writeContentFree(buf, CBOR_UNDEFINED);
}
function startArray(CBORBuffer memory buf) internal pure {
writeIndefiniteLengthType(buf, MAJOR_TYPE_ARRAY);
buf.depth += 1;
}
function startFixedArray(CBORBuffer memory buf, uint64 length) internal pure {
writeDefiniteLengthType(buf, MAJOR_TYPE_ARRAY, length);
}
function startMap(CBORBuffer memory buf) internal pure {
writeIndefiniteLengthType(buf, MAJOR_TYPE_MAP);
buf.depth += 1;
}
function startFixedMap(CBORBuffer memory buf, uint64 length) internal pure {
writeDefiniteLengthType(buf, MAJOR_TYPE_MAP, length);
}
function endSequence(CBORBuffer memory buf) internal pure {
writeIndefiniteLengthType(buf, MAJOR_TYPE_CONTENT_FREE);
buf.depth -= 1;
}
function writeKVString(CBORBuffer memory buf, string memory key, string memory value) internal pure {
writeString(buf, key);
writeString(buf, value);
}
function writeKVBytes(CBORBuffer memory buf, string memory key, bytes memory value) internal pure {
writeString(buf, key);
writeBytes(buf, value);
}
function writeKVUInt256(CBORBuffer memory buf, string memory key, uint256 value) internal pure {
writeString(buf, key);
writeUInt256(buf, value);
}
function writeKVInt256(CBORBuffer memory buf, string memory key, int256 value) internal pure {
writeString(buf, key);
writeInt256(buf, value);
}
function writeKVUInt64(CBORBuffer memory buf, string memory key, uint64 value) internal pure {
writeString(buf, key);
writeUInt64(buf, value);
}
function writeKVInt64(CBORBuffer memory buf, string memory key, int64 value) internal pure {
writeString(buf, key);
writeInt64(buf, value);
}
function writeKVBool(CBORBuffer memory buf, string memory key, bool value) internal pure {
writeString(buf, key);
writeBool(buf, value);
}
function writeKVNull(CBORBuffer memory buf, string memory key) internal pure {
writeString(buf, key);
writeNull(buf);
}
function writeKVUndefined(CBORBuffer memory buf, string memory key) internal pure {
writeString(buf, key);
writeUndefined(buf);
}
function writeKVMap(CBORBuffer memory buf, string memory key) internal pure {
writeString(buf, key);
startMap(buf);
}
function writeKVArray(CBORBuffer memory buf, string memory key) internal pure {
writeString(buf, key);
startArray(buf);
}
function writeFixedNumeric(
CBORBuffer memory buf,
uint8 major,
uint64 value
) private pure {
if (value <= 23) {
buf.buf.appendUint8(uint8((major << 5) | value));
} else if (value <= 0xFF) {
buf.buf.appendUint8(uint8((major << 5) | 24));
buf.buf.appendInt(value, 1);
} else if (value <= 0xFFFF) {
buf.buf.appendUint8(uint8((major << 5) | 25));
buf.buf.appendInt(value, 2);
} else if (value <= 0xFFFFFFFF) {
buf.buf.appendUint8(uint8((major << 5) | 26));
buf.buf.appendInt(value, 4);
} else {
buf.buf.appendUint8(uint8((major << 5) | 27));
buf.buf.appendInt(value, 8);
}
}
function writeIndefiniteLengthType(CBORBuffer memory buf, uint8 major)
private
pure
{
buf.buf.appendUint8(uint8((major << 5) | 31));
}
function writeDefiniteLengthType(CBORBuffer memory buf, uint8 major, uint64 length)
private
pure
{
writeFixedNumeric(buf, major, length);
}
function writeContentFree(CBORBuffer memory buf, uint8 value) private pure {
buf.buf.appendUint8(uint8((MAJOR_TYPE_CONTENT_FREE << 5) | value));
}
}
/*******************************************************************************
* (c) 2022 Zondax AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************/
// THIS CODE WAS SECURITY REVIEWED BY KUDELSKI SECURITY, BUT NOT FORMALLY AUDITED
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.17;
// MajUnsignedInt = 0
// MajSignedInt = 1
// MajByteString = 2
// MajTextString = 3
// MajArray = 4
// MajMap = 5
// MajTag = 6
// MajOther = 7
uint8 constant MajUnsignedInt = 0;
uint8 constant MajSignedInt = 1;
uint8 constant MajByteString = 2;
uint8 constant MajTextString = 3;
uint8 constant MajArray = 4;
uint8 constant MajMap = 5;
uint8 constant MajTag = 6;
uint8 constant MajOther = 7;
uint8 constant TagTypeBigNum = 2;
uint8 constant TagTypeNegativeBigNum = 3;
uint8 constant True_Type = 21;
uint8 constant False_Type = 20;
/// @notice This library is a set a functions that allows anyone to decode cbor encoded bytes
/// @dev methods in this library try to read the data type indicated from cbor encoded data stored in bytes at a specific index
/// @dev if it successes, methods will return the read value and the new index (intial index plus read bytes)
/// @author Zondax AG
library CBORDecoder {
/// @notice check if next value on the cbor encoded data is null
/// @param cborData cbor encoded bytes to parse from
/// @param byteIdx current position to read on the cbor encoded bytes
function isNullNext(bytes memory cborData, uint byteIdx) internal pure returns (bool) {
return cborData[byteIdx] == hex"f6";
}
/// @notice attempt to read a bool value
/// @param cborData cbor encoded bytes to parse from
/// @param byteIdx current position to read on the cbor encoded bytes
/// @return a bool decoded from input bytes and the byte index after moving past the value
function readBool(bytes memory cborData, uint byteIdx) internal pure returns (bool, uint) {
uint8 maj;
uint value;
(maj, value, byteIdx) = parseCborHeader(cborData, byteIdx);
require(maj == MajOther, "invalid maj (expected MajOther)");
assert(value == True_Type || value == False_Type);
return (value != False_Type, byteIdx);
}
/// @notice attempt to read the length of a fixed array
/// @param cborData cbor encoded bytes to parse from
/// @param byteIdx current position to read on the cbor encoded bytes
/// @return length of the fixed array decoded from input bytes and the byte index after moving past the value
function readFixedArray(bytes memory cborData, uint byteIdx) internal pure returns (uint, uint) {
uint8 maj;
uint len;
(maj, len, byteIdx) = parseCborHeader(cborData, byteIdx);
require(maj == MajArray, "invalid maj (expected MajArray)");
return (len, byteIdx);
}
/// @notice attempt to read an arbitrary length string value
/// @param cborData cbor encoded bytes to parse from
/// @param byteIdx current position to read on the cbor encoded bytes
/// @return arbitrary length string decoded from input bytes and the byte index after moving past the value
function readString(bytes memory cborData, uint byteIdx) internal pure returns (string memory, uint) {
uint8 maj;
uint len;
(maj, len, byteIdx) = parseCborHeader(cborData, byteIdx);
require(maj == MajTextString, "invalid maj (expected MajTextString)");
uint max_len = byteIdx + len;
bytes memory slice = new bytes(len);
uint slice_index = 0;
for (uint256 i = byteIdx; i < max_len; i++) {
slice[slice_index] = cborData[i];
slice_index++;
}
return (string(slice), byteIdx + len);
}
/// @notice attempt to read an arbitrary byte string value
/// @param cborData cbor encoded bytes to parse from
/// @param byteIdx current position to read on the cbor encoded bytes
/// @return arbitrary byte string decoded from input bytes and the byte index after moving past the value
function readBytes(bytes memory cborData, uint byteIdx) internal pure returns (bytes memory, uint) {
uint8 maj;
uint len;
(maj, len, byteIdx) = parseCborHeader(cborData, byteIdx);
require(maj == MajTag || maj == MajByteString, "invalid maj (expected MajTag or MajByteString)");
if (maj == MajTag) {
(maj, len, byteIdx) = parseCborHeader(cborData, byteIdx);
assert(maj == MajByteString);
}
uint max_len = byteIdx + len;
bytes memory slice = new bytes(len);
uint slice_index = 0;
for (uint256 i = byteIdx; i < max_len; i++) {
slice[slice_index] = cborData[i];
slice_index++;
}
return (slice, byteIdx + len);
}
/// @notice attempt to read a bytes32 value
/// @param cborData cbor encoded bytes to parse from
/// @param byteIdx current position to read on the cbor encoded bytes
/// @return a bytes32 decoded from input bytes and the byte index after moving past the value
function readBytes32(bytes memory cborData, uint byteIdx) internal pure returns (bytes32, uint) {
uint8 maj;
uint len;
(maj, len, byteIdx) = parseCborHeader(cborData, byteIdx);
require(maj == MajByteString, "invalid maj (expected MajByteString)");
uint max_len = byteIdx + len;
bytes memory slice = new bytes(32);
uint slice_index = 32 - len;
for (uint256 i = byteIdx; i < max_len; i++) {
slice[slice_index] = cborData[i];
slice_index++;
}
return (bytes32(slice), byteIdx + len);
}
/// @notice attempt to read a uint256 value encoded per cbor specification
/// @param cborData cbor encoded bytes to parse from
/// @param byteIdx current position to read on the cbor encoded bytes
/// @return an uint256 decoded from input bytes and the byte index after moving past the value
function readUInt256(bytes memory cborData, uint byteIdx) internal pure returns (uint256, uint) {
uint8 maj;
uint256 value;
(maj, value, byteIdx) = parseCborHeader(cborData, byteIdx);
require(maj == MajTag || maj == MajUnsignedInt, "invalid maj (expected MajTag or MajUnsignedInt)");
if (maj == MajTag) {
require(value == TagTypeBigNum, "invalid tag (expected TagTypeBigNum)");
uint len;
(maj, len, byteIdx) = parseCborHeader(cborData, byteIdx);
require(maj == MajByteString, "invalid maj (expected MajByteString)");
require(cborData.length >= byteIdx + len, "slicing out of range");
assembly {
value := mload(add(cborData, add(len, byteIdx)))
}
return (value, byteIdx + len);
}
return (value, byteIdx);
}
/// @notice attempt to read a int256 value encoded per cbor specification
/// @param cborData cbor encoded bytes to parse from
/// @param byteIdx current position to read on the cbor encoded bytes
/// @return an int256 decoded from input bytes and the byte index after moving past the value
function readInt256(bytes memory cborData, uint byteIdx) internal pure returns (int256, uint) {
uint8 maj;
uint value;
(maj, value, byteIdx) = parseCborHeader(cborData, byteIdx);
require(maj == MajTag || maj == MajSignedInt, "invalid maj (expected MajTag or MajSignedInt)");
if (maj == MajTag) {
assert(value == TagTypeNegativeBigNum);
uint len;
(maj, len, byteIdx) = parseCborHeader(cborData, byteIdx);
require(maj == MajByteString, "invalid maj (expected MajByteString)");
require(cborData.length >= byteIdx + len, "slicing out of range");
assembly {
value := mload(add(cborData, add(len, byteIdx)))
}
return (int256(value), byteIdx + len);
}
return (int256(value), byteIdx);
}
/// @notice attempt to read a uint64 value
/// @param cborData cbor encoded bytes to parse from
/// @param byteIdx current position to read on the cbor encoded bytes
/// @return an uint64 decoded from input bytes and the byte index after moving past the value
function readUInt64(bytes memory cborData, uint byteIdx) internal pure returns (uint64, uint) {
uint8 maj;
uint value;
(maj, value, byteIdx) = parseCborHeader(cborData, byteIdx);
require(maj == MajUnsignedInt, "invalid maj (expected MajUnsignedInt)");
return (uint64(value), byteIdx);
}
/// @notice attempt to read a uint32 value
/// @param cborData cbor encoded bytes to parse from
/// @param byteIdx current position to read on the cbor encoded bytes
/// @return an uint32 decoded from input bytes and the byte index after moving past the value
function readUInt32(bytes memory cborData, uint byteIdx) internal pure returns (uint32, uint) {
uint8 maj;
uint value;
(maj, value, byteIdx) = parseCborHeader(cborData, byteIdx);
require(maj == MajUnsignedInt, "invalid maj (expected MajUnsignedInt)");
return (uint32(value), byteIdx);
}
/// @notice attempt to read a uint16 value
/// @param cborData cbor encoded bytes to parse from
/// @param byteIdx current position to read on the cbor encoded bytes
/// @return an uint16 decoded from input bytes and the byte index after moving past the value
function readUInt16(bytes memory cborData, uint byteIdx) internal pure returns (uint16, uint) {
uint8 maj;
uint value;
(maj, value, byteIdx) = parseCborHeader(cborData, byteIdx);
require(maj == MajUnsignedInt, "invalid maj (expected MajUnsignedInt)");
return (uint16(value), byteIdx);
}
/// @notice attempt to read a uint8 value
/// @param cborData cbor encoded bytes to parse from
/// @param byteIdx current position to read on the cbor encoded bytes
/// @return an uint8 decoded from input bytes and the byte index after moving past the value
function readUInt8(bytes memory cborData, uint byteIdx) internal pure returns (uint8, uint) {
uint8 maj;
uint value;
(maj, value, byteIdx) = parseCborHeader(cborData, byteIdx);
require(maj == MajUnsignedInt, "invalid maj (expected MajUnsignedInt)");
return (uint8(value), byteIdx);
}
/// @notice attempt to read a int64 value
/// @param cborData cbor encoded bytes to parse from
/// @param byteIdx current position to read on the cbor encoded bytes
/// @return an int64 decoded from input bytes and the byte index after moving past the value
function readInt64(bytes memory cborData, uint byteIdx) internal pure returns (int64, uint) {
uint8 maj;
uint value;
(maj, value, byteIdx) = parseCborHeader(cborData, byteIdx);
require(maj == MajSignedInt || maj == MajUnsignedInt, "invalid maj (expected MajSignedInt or MajUnsignedInt)");
return (int64(uint64(value)), byteIdx);
}
/// @notice attempt to read a int32 value
/// @param cborData cbor encoded bytes to parse from
/// @param byteIdx current position to read on the cbor encoded bytes
/// @return an int32 decoded from input bytes and the byte index after moving past the value
function readInt32(bytes memory cborData, uint byteIdx) internal pure returns (int32, uint) {
uint8 maj;
uint value;
(maj, value, byteIdx) = parseCborHeader(cborData, byteIdx);
require(maj == MajSignedInt || maj == MajUnsignedInt, "invalid maj (expected MajSignedInt or MajUnsignedInt)");
return (int32(uint32(value)), byteIdx);
}
/// @notice attempt to read a int16 value
/// @param cborData cbor encoded bytes to parse from
/// @param byteIdx current position to read on the cbor encoded bytes
/// @return an int16 decoded from input bytes and the byte index after moving past the value
function readInt16(bytes memory cborData, uint byteIdx) internal pure returns (int16, uint) {
uint8 maj;
uint value;
(maj, value, byteIdx) = parseCborHeader(cborData, byteIdx);
require(maj == MajSignedInt || maj == MajUnsignedInt, "invalid maj (expected MajSignedInt or MajUnsignedInt)");
return (int16(uint16(value)), byteIdx);
}
/// @notice attempt to read a int8 value
/// @param cborData cbor encoded bytes to parse from
/// @param byteIdx current position to read on the cbor encoded bytes
/// @return an int8 decoded from input bytes and the byte index after moving past the value
function readInt8(bytes memory cborData, uint byteIdx) internal pure returns (int8, uint) {
uint8 maj;
uint value;
(maj, value, byteIdx) = parseCborHeader(cborData, byteIdx);
require(maj == MajSignedInt || maj == MajUnsignedInt, "invalid maj (expected MajSignedInt or MajUnsignedInt)");
return (int8(uint8(value)), byteIdx);
}
/// @notice slice uint8 from bytes starting at a given index
/// @param bs bytes to slice from
/// @param start current position to slice from bytes
/// @return uint8 sliced from bytes
function sliceUInt8(bytes memory bs, uint start) internal pure returns (uint8) {
require(bs.length >= start + 1, "slicing out of range");
return uint8(bs[start]);
}
/// @notice slice uint16 from bytes starting at a given index
/// @param bs bytes to slice from
/// @param start current position to slice from bytes
/// @return uint16 sliced from bytes
function sliceUInt16(bytes memory bs, uint start) internal pure returns (uint16) {
require(bs.length >= start + 2, "slicing out of range");
bytes2 x;
assembly {
x := mload(add(bs, add(0x20, start)))
}
return uint16(x);
}
/// @notice slice uint32 from bytes starting at a given index
/// @param bs bytes to slice from
/// @param start current position to slice from bytes
/// @return uint32 sliced from bytes
function sliceUInt32(bytes memory bs, uint start) internal pure returns (uint32) {
require(bs.length >= start + 4, "slicing out of range");
bytes4 x;
assembly {
x := mload(add(bs, add(0x20, start)))
}
return uint32(x);
}
/// @notice slice uint64 from bytes starting at a given index
/// @param bs bytes to slice from
/// @param start current position to slice from bytes
/// @return uint64 sliced from bytes
function sliceUInt64(bytes memory bs, uint start) internal pure returns (uint64) {
require(bs.length >= start + 8, "slicing out of range");
bytes8 x;
assembly {
x := mload(add(bs, add(0x20, start)))
}
return uint64(x);
}
/// @notice Parse cbor header for major type and extra info.
/// @param cbor cbor encoded bytes to parse from
/// @param byteIndex current position to read on the cbor encoded bytes
/// @return major type, extra info and the byte index after moving past header bytes
function parseCborHeader(bytes memory cbor, uint byteIndex) internal pure returns (uint8, uint64, uint) {
uint8 first = sliceUInt8(cbor, byteIndex);
byteIndex += 1;
uint8 maj = (first & 0xe0) >> 5;
uint8 low = first & 0x1f;
// We don't handle CBOR headers with extra > 27, i.e. no indefinite lengths
require(low < 28, "cannot handle headers with extra > 27");
// extra is lower bits
if (low < 24) {
return (maj, low, byteIndex);
}
// extra in next byte
if (low == 24) {
uint8 next = sliceUInt8(cbor, byteIndex);
byteIndex += 1;
require(next >= 24, "invalid cbor"); // otherwise this is invalid cbor
return (maj, next, byteIndex);
}
// extra in next 2 bytes
if (low == 25) {
uint16 extra16 = sliceUInt16(cbor, byteIndex);
byteIndex += 2;
return (maj, extra16, byteIndex);
}
// extra in next 4 bytes
if (low == 26) {
uint32 extra32 = sliceUInt32(cbor, byteIndex);
byteIndex += 4;
return (maj, extra32, byteIndex);
}
// extra in next 8 bytes
assert(low == 27);
uint64 extra64 = sliceUInt64(cbor, byteIndex);
byteIndex += 8;
return (maj, extra64, byteIndex);
}
}
// SPDX-License-Identifier: BSD-2-Clause
pragma solidity ^0.8.4;
/**
* @dev A library for working with mutable byte buffers in Solidity.
*
* Byte buffers are mutable and expandable, and provide a variety of primitives
* for appending to them. At any time you can fetch a bytes object containing the
* current contents of the buffer. The bytes object should not be stored between
* operations, as it may change due to resizing of the buffer.
*/
library Buffer {
/**
* @dev Represents a mutable buffer. Buffers have a current value (buf) and
* a capacity. The capacity may be longer than the current value, in
* which case it can be extended without the need to allocate more memory.
*/
struct buffer {
bytes buf;
uint capacity;
}
/**
* @dev Initializes a buffer with an initial capacity.
* @param buf The buffer to initialize.
* @param capacity The number of bytes of space to allocate the buffer.
* @return The buffer, for chaining.
*/
function init(buffer memory buf, uint capacity) internal pure returns(buffer memory) {
if (capacity % 32 != 0) {
capacity += 32 - (capacity % 32);
}
// Allocate space for the buffer data
buf.capacity = capacity;
assembly {
let ptr := mload(0x40)
mstore(buf, ptr)
mstore(ptr, 0)
let fpm := add(32, add(ptr, capacity))
if lt(fpm, ptr) {
revert(0, 0)
}
mstore(0x40, fpm)
}
return buf;
}
/**
* @dev Initializes a new buffer from an existing bytes object.
* Changes to the buffer may mutate the original value.
* @param b The bytes object to initialize the buffer with.
* @return A new buffer.
*/
function fromBytes(bytes memory b) internal pure returns(buffer memory) {
buffer memory buf;
buf.buf = b;
buf.capacity = b.length;
return buf;
}
function resize(buffer memory buf, uint capacity) private pure {
bytes memory oldbuf = buf.buf;
init(buf, capacity);
append(buf, oldbuf);
}
/**
* @dev Sets buffer length to 0.
* @param buf The buffer to truncate.
* @return The original buffer, for chaining..
*/
function truncate(buffer memory buf) internal pure returns (buffer memory) {
assembly {
let bufptr := mload(buf)
mstore(bufptr, 0)
}
return buf;
}
/**
* @dev Appends len bytes of a byte string to a buffer. Resizes if doing so would exceed
* the capacity of the buffer.
* @param buf The buffer to append to.
* @param data The data to append.
* @param len The number of bytes to copy.
* @return The original buffer, for chaining.
*/
function append(buffer memory buf, bytes memory data, uint len) internal pure returns(buffer memory) {
require(len <= data.length);
uint off = buf.buf.length;
uint newCapacity = off + len;
if (newCapacity > buf.capacity) {
resize(buf, newCapacity * 2);
}
uint dest;
uint src;
assembly {
// Memory address of the buffer data
let bufptr := mload(buf)
// Length of existing buffer data
let buflen := mload(bufptr)
// Start address = buffer address + offset + sizeof(buffer length)
dest := add(add(bufptr, 32), off)
// Update buffer length if we're extending it
if gt(newCapacity, buflen) {
mstore(bufptr, newCapacity)
}
src := add(data, 32)
}
// Copy word-length chunks while possible
for (; len >= 32; len -= 32) {
assembly {
mstore(dest, mload(src))
}
dest += 32;
src += 32;
}
// Copy remaining bytes
unchecked {
uint mask = (256 ** (32 - len)) - 1;
assembly {
let srcpart := and(mload(src), not(mask))
let destpart := and(mload(dest), mask)
mstore(dest, or(destpart, srcpart))
}
}
return buf;
}
/**
* @dev Appends a byte string to a buffer. Resizes if doing so would exceed
* the capacity of the buffer.
* @param buf The buffer to append to.
* @param data The data to append.
* @return The original buffer, for chaining.
*/
function append(buffer memory buf, bytes memory data) internal pure returns (buffer memory) {
return append(buf, data, data.length);
}
/**
* @dev Appends a byte to the buffer. Resizes if doing so would exceed the
* capacity of the buffer.
* @param buf The buffer to append to.
* @param data The data to append.
* @return The original buffer, for chaining.
*/
function appendUint8(buffer memory buf, uint8 data) internal pure returns(buffer memory) {
uint off = buf.buf.length;
uint offPlusOne = off + 1;
if (off >= buf.capacity) {
resize(buf, offPlusOne * 2);
}
assembly {
// Memory address of the buffer data
let bufptr := mload(buf)
// Address = buffer address + sizeof(buffer length) + off
let dest := add(add(bufptr, off), 32)
mstore8(dest, data)
// Update buffer length if we extended it
if gt(offPlusOne, mload(bufptr)) {
mstore(bufptr, offPlusOne)
}
}
return buf;
}
/**
* @dev Appends len bytes of bytes32 to a buffer. Resizes if doing so would
* exceed the capacity of the buffer.
* @param buf The buffer to append to.
* @param data The data to append.
* @param len The number of bytes to write (left-aligned).
* @return The original buffer, for chaining.
*/
function append(buffer memory buf, bytes32 data, uint len) private pure returns(buffer memory) {
uint off = buf.buf.length;
uint newCapacity = len + off;
if (newCapacity > buf.capacity) {
resize(buf, newCapacity * 2);
}
unchecked {
uint mask = (256 ** len) - 1;
// Right-align data
data = data >> (8 * (32 - len));
assembly {
// Memory address of the buffer data
let bufptr := mload(buf)
// Address = buffer address + sizeof(buffer length) + newCapacity
let dest := add(bufptr, newCapacity)
mstore(dest, or(and(mload(dest), not(mask)), data))
// Update buffer length if we extended it
if gt(newCapacity, mload(bufptr)) {
mstore(bufptr, newCapacity)
}
}
}
return buf;
}
/**
* @dev Appends a bytes20 to the buffer. Resizes if doing so would exceed
* the capacity of the buffer.
* @param buf The buffer to append to.
* @param data The data to append.
* @return The original buffer, for chhaining.
*/
function appendBytes20(buffer memory buf, bytes20 data) internal pure returns (buffer memory) {
return append(buf, bytes32(data), 20);
}
/**
* @dev Appends a bytes32 to the buffer. Resizes if doing so would exceed
* the capacity of the buffer.
* @param buf The buffer to append to.
* @param data The data to append.
* @return The original buffer, for chaining.
*/
function appendBytes32(buffer memory buf, bytes32 data) internal pure returns (buffer memory) {
return append(buf, data, 32);
}
/**
* @dev Appends a byte to the end of the buffer. Resizes if doing so would
* exceed the capacity of the buffer.
* @param buf The buffer to append to.
* @param data The data to append.
* @param len The number of bytes to write (right-aligned).
* @return The original buffer.
*/
function appendInt(buffer memory buf, uint data, uint len) internal pure returns(buffer memory) {
uint off = buf.buf.length;
uint newCapacity = len + off;
if (newCapacity > buf.capacity) {
resize(buf, newCapacity * 2);
}
uint mask = (256 ** len) - 1;
assembly {
// Memory address of the buffer data
let bufptr := mload(buf)
// Address = buffer address + sizeof(buffer length) + newCapacity
let dest := add(bufptr, newCapacity)
mstore(dest, or(and(mload(dest), not(mask)), data))
// Update buffer length if we extended it
if gt(newCapacity, mload(bufptr)) {
mstore(bufptr, newCapacity)
}
}
return buf;
}
}