Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions contracts/utils/RLP.sol
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ library RLP {
}

/**
* @dev Encode an address as RLP.
* @dev Encode an address as an RLP array of fixed size (20 bytes).
*
* The address is encoded with its leading zeros (if it has any). If someone wants to encode the address as a scalar,
* they can cast it to an uint256 and then call the corresponding {encode} function.
Expand All @@ -165,7 +165,11 @@ library RLP {
}
}

/// @dev Encode a uint256 as RLP.
/**
* @dev Encode a uint256 as an RLP scalar.
*
* Unlike {encode-bytes32-}, this function uses scalar encoding that removes the prefix zeros.
*/
function encode(uint256 input) internal pure returns (bytes memory result) {
if (input < SHORT_OFFSET) {
assembly ("memory-safe") {
Expand All @@ -186,9 +190,19 @@ library RLP {
}
}

/// @dev Encode a bytes32 as RLP. Type alias for {encode-uint256-}.
function encode(bytes32 input) internal pure returns (bytes memory) {
return encode(uint256(input));
/**
* @dev Encode a bytes32 as an RLP array of sized size (32 bytes).
*
* Unlike {encode-uint256-}, this function uses array encoding that preserves the prefix zeros.
*/
function encode(bytes32 input) internal pure returns (bytes memory result) {
assembly ("memory-safe") {
result := mload(0x40)
mstore(result, 0x21) // length of the encoded data: 1 (prefix) + 0x20
mstore8(add(result, 0x20), 0xa0) // prefix: SHORT_OFFSET + 0x20
mstore(add(result, 0x21), input)
mstore(0x40, add(result, 0x41)) // reserve memory
}
}

/// @dev Encode a bytes buffer as RLP.
Expand Down
37 changes: 17 additions & 20 deletions test/utils/RLP.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,36 +91,33 @@ describe('RLP', function () {
});

it('encode/decode bytes32', async function () {
for (const { input, expected } of [
{ input: '0x0000000000000000000000000000000000000000000000000000000000000000', expected: '0x80' },
{ input: '0x0000000000000000000000000000000000000000000000000000000000000001', expected: '0x01' },
{
input: '0x1000000000000000000000000000000000000000000000000000000000000000',
expected: '0xa01000000000000000000000000000000000000000000000000000000000000000',
},
for (const input of [
'0x0000000000000000000000000000000000000000000000000000000000000000',
'0x0000000000000000000000000000000000000000000000000000000000000001',
'0x1000000000000000000000000000000000000000000000000000000000000000',
generators.bytes32(),
]) {
await expect(this.mock.$encode_bytes32(input)).to.eventually.equal(expected);
await expect(this.mock.$decodeBytes32(expected)).to.eventually.equal(input);
const encoded = ethers.encodeRlp(input);
await expect(this.mock.$encode_bytes32(input)).to.eventually.equal(encoded);
await expect(this.mock.$decodeBytes32(encoded)).to.eventually.equal(input);
}

// Compact encoding for 1234
await expect(this.mock.$decodeBytes32('0x8204d2')).to.eventually.equal(
'0x00000000000000000000000000000000000000000000000000000000000004d2',
); // Canonical encoding for 1234
);
// Encoding with one leading zero
await expect(this.mock.$decodeBytes32('0x830004d2')).to.eventually.equal(
'0x00000000000000000000000000000000000000000000000000000000000004d2',
); // Non-canonical encoding with leading zero
);
// Encoding with two leading zeros
await expect(this.mock.$decodeBytes32('0x84000004d2')).to.eventually.equal(
'0x00000000000000000000000000000000000000000000000000000000000004d2',
); // Non-canonical encoding with two leading zeros

// Canonical encoding for zero and non-canonical encodings with leading zeros
await expect(this.mock.$decodeBytes32('0x80')).to.eventually.equal(
'0x0000000000000000000000000000000000000000000000000000000000000000',
);
// 1 leading zero is not allowed for single bytes less than 0x80, they must be encoded as themselves
await expect(this.mock.$decodeBytes32('0x820000')).to.eventually.equal(
'0x0000000000000000000000000000000000000000000000000000000000000000',
); // Non-canonical encoding with two leading zeros
// Encoding for the value
await expect(this.mock.$decodeBytes32('0x80')).to.eventually.equal(ethers.ZeroHash);
// Encoding for two zeros (and nothing else)
await expect(this.mock.$decodeBytes32('0x820000')).to.eventually.equal(ethers.ZeroHash);
});

it('encode/decode empty byte', async function () {
Expand Down
Loading