Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/fluffy-facts-brake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`Arrays`: Add `splice` function variants with `replacement` parameter for `address[]`, `bytes32[]` and `uint256[]` arrays, enabling in-place array modification with new content.
5 changes: 5 additions & 0 deletions .changeset/forty-ads-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`Bytes`: Add `splice(bytes,uint256,bytes)` function that replaces a portion of a bytes buffer with replacement content, copying only what fits within the original buffer bounds.
102 changes: 102 additions & 0 deletions contracts/utils/Arrays.sol
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,40 @@ library Arrays {
return array;
}

/**
* @dev Replaces the content of `array` with the content of `replacement`. The replacement is truncated to fit within the bounds of the array.
*
* NOTE: This function modifies the provided array in place. If you need to preserve the original array, use {slice} instead
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice[Javascript's `Array.splice`]
*/
function splice(address[] memory array, address[] memory replacement) internal pure returns (address[] memory) {
return splice(array, 0, replacement);
}

/**
* @dev Replaces the content of `array` starting at position `start` with the content of `replacement`. The
* replacement is truncated to fit within the bounds of the array.
*
* NOTE: This function modifies the provided array in place. If you need to preserve the original array, use {slice} instead
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice[Javascript's `Array.splice`]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhat different since deleteCount is implicitly equal to replacement.length

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, it's the same for other functions that claim to replicate the behavior of Array.splice and Array.slice. I think it's ok that they differ slightly.

*/
function splice(
address[] memory array,
uint256 start,
address[] memory replacement
) internal pure returns (address[] memory) {
// sanitize
start = Math.min(start, array.length);
uint256 copyLength = Math.min(replacement.length, array.length - start);

// allocate and copy
assembly ("memory-safe") {
mcopy(add(add(array, 0x20), mul(start, 0x20)), add(replacement, 0x20), mul(copyLength, 0x20))
}

return array;
}

/**
* @dev Moves the content of `array`, from `start` (included) to the end of `array` to the start of that array.
*
Expand Down Expand Up @@ -527,6 +561,40 @@ library Arrays {
return array;
}

/**
* @dev Replaces the content of `array` with the content of `replacement`. The replacement is truncated to fit within the bounds of the array.
*
* NOTE: This function modifies the provided array in place. If you need to preserve the original array, use {slice} instead
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice[Javascript's `Array.splice`]
*/
function splice(bytes32[] memory array, bytes32[] memory replacement) internal pure returns (bytes32[] memory) {
return splice(array, 0, replacement);
}

/**
* @dev Replaces the content of `array` starting at position `start` with the content of `replacement`. The
* replacement is truncated to fit within the bounds of the array.
*
* NOTE: This function modifies the provided array in place. If you need to preserve the original array, use {slice} instead
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice[Javascript's `Array.splice`]
*/
function splice(
bytes32[] memory array,
uint256 start,
bytes32[] memory replacement
) internal pure returns (bytes32[] memory) {
// sanitize
start = Math.min(start, array.length);
uint256 copyLength = Math.min(replacement.length, array.length - start);

// allocate and copy
assembly ("memory-safe") {
mcopy(add(add(array, 0x20), mul(start, 0x20)), add(replacement, 0x20), mul(copyLength, 0x20))
}

return array;
}

/**
* @dev Moves the content of `array`, from `start` (included) to the end of `array` to the start of that array.
*
Expand Down Expand Up @@ -558,6 +626,40 @@ library Arrays {
return array;
}

/**
* @dev Replaces the content of `array` with the content of `replacement`. The replacement is truncated to fit within the bounds of the array.
*
* NOTE: This function modifies the provided array in place. If you need to preserve the original array, use {slice} instead
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice[Javascript's `Array.splice`]
*/
function splice(uint256[] memory array, uint256[] memory replacement) internal pure returns (uint256[] memory) {
return splice(array, 0, replacement);
}

/**
* @dev Replaces the content of `array` starting at position `start` with the content of `replacement`. The
* replacement is truncated to fit within the bounds of the array.
*
* NOTE: This function modifies the provided array in place. If you need to preserve the original array, use {slice} instead
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice[Javascript's `Array.splice`]
*/
function splice(
uint256[] memory array,
uint256 start,
uint256[] memory replacement
) internal pure returns (uint256[] memory) {
// sanitize
start = Math.min(start, array.length);
uint256 copyLength = Math.min(replacement.length, array.length - start);

// allocate and copy
assembly ("memory-safe") {
mcopy(add(add(array, 0x20), mul(start, 0x20)), add(replacement, 0x20), mul(copyLength, 0x20))
}

return array;
}

/**
* @dev Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check.
*
Expand Down
30 changes: 30 additions & 0 deletions contracts/utils/Bytes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,36 @@ library Bytes {
return buffer;
}

/**
* @dev Replaces the content of `buffer` with the content of `replacement`. The replacement is truncated to fit within the bounds of the buffer.
*
* NOTE: This function modifies the provided buffer in place. If you need to preserve the original buffer, use {slice} instead
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice[Javascript's `Array.splice`]
*/
function splice(bytes memory buffer, bytes memory replacement) internal pure returns (bytes memory) {
return splice(buffer, 0, replacement);
}

/**
* @dev Replaces the content of `buffer` starting at position `start` with the content of `replacement`. The
* replacement is truncated to fit within the bounds of the buffer.
*
* NOTE: This function modifies the provided buffer in place. If you need to preserve the original buffer, use {slice} instead
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice[Javascript's `Array.splice`]
*/
function splice(bytes memory buffer, uint256 start, bytes memory replacement) internal pure returns (bytes memory) {
// sanitize
start = Math.min(start, buffer.length);
uint256 copyLength = Math.min(replacement.length, buffer.length - start);

// allocate and copy
assembly ("memory-safe") {
mcopy(add(add(buffer, 0x20), start), add(replacement, 0x20), copyLength)
}

return buffer;
}

/**
* @dev Concatenate an array of bytes into a single bytes object.
*
Expand Down
34 changes: 34 additions & 0 deletions scripts/generate/templates/Arrays.js
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,40 @@ function splice(${type.name}[] memory array, uint256 start, uint256 end) interna

return array;
}

/**
* @dev Replaces the content of \`array\` with the content of \`replacement\`. The replacement is truncated to fit within the bounds of the array.
*
* NOTE: This function modifies the provided array in place. If you need to preserve the original array, use {slice} instead
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice[Javascript's \`Array.splice\`]
*/
function splice(${type.name}[] memory array, ${type.name}[] memory replacement) internal pure returns (${type.name}[] memory) {
return splice(array, 0, replacement);
}

/**
* @dev Replaces the content of \`array\` starting at position \`start\` with the content of \`replacement\`. The
* replacement is truncated to fit within the bounds of the array.
*
* NOTE: This function modifies the provided array in place. If you need to preserve the original array, use {slice} instead
* NOTE: replicates the behavior of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice[Javascript's \`Array.splice\`]
*/
function splice(
${type.name}[] memory array,
uint256 start,
${type.name}[] memory replacement
) internal pure returns (${type.name}[] memory) {
// sanitize
start = Math.min(start, array.length);
uint256 copyLength = Math.min(replacement.length, array.length - start);

// allocate and copy
assembly ("memory-safe") {
mcopy(add(add(array, 0x20), mul(start, 0x20)), add(replacement, 0x20), mul(copyLength, 0x20))
}

return array;
}
`;

// GENERATE
Expand Down
143 changes: 143 additions & 0 deletions test/utils/Arrays.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,149 @@ contract ArraysTest is Test, SymTest {
_assertSliceOf(result, originalValues, sanitizedStart, expectedLength);
}

function testSpliceAddressWithReplacementFromStart(
address[] memory values,
address[] memory replacement
) public pure {
address[] memory originalValues = _copyArray(values);
address[] memory result = Arrays.splice(values, replacement);

bytes32[] memory valuesBytes;
bytes32[] memory originalValuesBytes;
bytes32[] memory replacementBytes;
bytes32[] memory resultBytes;

assembly {
valuesBytes := values
originalValuesBytes := originalValues
replacementBytes := replacement
resultBytes := result
}

_validateSplice(valuesBytes, originalValuesBytes, 0, replacementBytes, resultBytes);
}

function testSpliceAddressWithReplacement(
address[] memory values,
uint256 start,
address[] memory replacement
) public pure {
address[] memory originalValues = _copyArray(values);
address[] memory result = Arrays.splice(values, start, replacement);

bytes32[] memory valuesBytes;
bytes32[] memory originalValuesBytes;
bytes32[] memory replacementBytes;
bytes32[] memory resultBytes;

assembly {
valuesBytes := values
originalValuesBytes := originalValues
replacementBytes := replacement
resultBytes := result
}

_validateSplice(valuesBytes, originalValuesBytes, start, replacementBytes, resultBytes);
}

function testSpliceBytes32WithReplacementFromStart(
bytes32[] memory values,
bytes32[] memory replacement
) public pure {
bytes32[] memory originalValues = _copyArray(values);
bytes32[] memory result = Arrays.splice(values, replacement);

_validateSplice(values, originalValues, 0, replacement, result);
}

function testSpliceBytes32WithReplacement(
bytes32[] memory values,
uint256 start,
bytes32[] memory replacement
) public pure {
bytes32[] memory originalValues = _copyArray(values);
bytes32[] memory result = Arrays.splice(values, start, replacement);

_validateSplice(values, originalValues, start, replacement, result);
}

function testSpliceUint256WithReplacementFromStart(
uint256[] memory values,
uint256[] memory replacement
) public pure {
uint256[] memory originalValues = _copyArray(values);
uint256[] memory result = Arrays.splice(values, replacement);

bytes32[] memory valuesBytes;
bytes32[] memory originalValuesBytes;
bytes32[] memory replacementBytes;
bytes32[] memory resultBytes;

assembly {
valuesBytes := values
originalValuesBytes := originalValues
replacementBytes := replacement
resultBytes := result
}

_validateSplice(valuesBytes, originalValuesBytes, 0, replacementBytes, resultBytes);
}

function testSpliceUint256WithReplacement(
uint256[] memory values,
uint256 start,
uint256[] memory replacement
) public pure {
uint256[] memory originalValues = _copyArray(values);
uint256[] memory result = Arrays.splice(values, start, replacement);

bytes32[] memory valuesBytes;
bytes32[] memory originalValuesBytes;
bytes32[] memory replacementBytes;
bytes32[] memory resultBytes;
assembly {
valuesBytes := values
originalValuesBytes := originalValues
replacementBytes := replacement
resultBytes := result
}

_validateSplice(valuesBytes, originalValuesBytes, start, replacementBytes, resultBytes);
}

function _validateSplice(
bytes32[] memory values,
bytes32[] memory originalValues,
uint256 start,
bytes32[] memory replacement,
bytes32[] memory result
) internal pure {
// Result should be the same object as input (modified in place)
assertEq(result, values);

// Array length should remain unchanged
assertEq(result.length, originalValues.length);

// Calculate expected bounds after sanitization
uint256 sanitizedStart = Math.min(start, originalValues.length);
uint256 copyLength = Math.min(replacement.length, originalValues.length - sanitizedStart);

// Verify content before start position is unchanged
for (uint256 i = 0; i < sanitizedStart; ++i) {
assertEq(result[i], originalValues[i]);
}

// Verify replacement content was copied correctly
for (uint256 i = 0; i < copyLength; ++i) {
assertEq(result[sanitizedStart + i], replacement[i]);
}

// Verify content after replacement is unchanged
for (uint256 i = sanitizedStart + copyLength; i < result.length; ++i) {
assertEq(result[i], originalValues[i]);
}
}

/// Asserts

function _assertSort(uint256[] memory values) internal pure {
Expand Down
Loading