aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/contracts/compiler.json2
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol24
-rw-r--r--packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol28
-rw-r--r--packages/contracts/src/contracts/current/test/DummyERC721Receiver/DummyERC721Receiver.sol62
-rw-r--r--packages/contracts/src/contracts/current/test/TestLibMem/TestLibMem.sol238
-rw-r--r--packages/contracts/src/contracts/current/utils/LibAssetProxyDecoder/LibAssetProxyDecoder.sol74
-rw-r--r--packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol76
-rw-r--r--packages/contracts/src/contracts/current/utils/LibMem/LibMem.sol104
8 files changed, 581 insertions, 27 deletions
diff --git a/packages/contracts/compiler.json b/packages/contracts/compiler.json
index 48ba4ffcd..a11f2a2c0 100644
--- a/packages/contracts/compiler.json
+++ b/packages/contracts/compiler.json
@@ -22,6 +22,7 @@
"AssetProxyOwner",
"DummyERC20Token",
"DummyERC721Token",
+ "DummyERC721Receiver",
"ERC20Proxy",
"ERC721Proxy",
"Exchange",
@@ -30,6 +31,7 @@
"MultiSigWalletWithTimeLock",
"TestAssetProxyDispatcher",
"TestLibBytes",
+ "TestLibMem",
"TestLibs",
"TestSignatureValidator",
"TokenRegistry",
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol
index 2c321e134..017f94b1a 100644
--- a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC20Proxy.sol
@@ -20,12 +20,14 @@ pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;
import "../../utils/LibBytes/LibBytes.sol";
+import "../../utils/LibAssetProxyDecoder/LibAssetProxyDecoder.sol";
import "./MixinAssetProxy.sol";
import "./MixinAuthorizable.sol";
import "../../tokens/ERC20Token/IERC20Token.sol";
contract ERC20Proxy is
LibBytes,
+ LibAssetProxyDecoder,
MixinAssetProxy,
MixinAuthorizable
{
@@ -34,34 +36,32 @@ contract ERC20Proxy is
uint8 constant PROXY_ID = 1;
/// @dev Internal version of `transferFrom`.
- /// @param assetMetadata Encoded byte array.
+ /// @param proxyData Encoded byte array.
/// @param from Address to transfer asset from.
/// @param to Address to transfer asset to.
/// @param amount Amount of asset to transfer.
function transferFromInternal(
- bytes memory assetMetadata,
+ bytes memory proxyData,
address from,
address to,
uint256 amount
)
internal
{
+ // Decode proxy data.
+ (
+ uint8 proxyId,
+ address token
+ ) = decodeERC20Data(proxyData);
+
// Data must be intended for this proxy.
uint256 length = assetMetadata.length;
require(
- length == 21,
- LENGTH_21_REQUIRED
- );
- // TODO: Is this too inflexible in the future?
- require(
- uint8(assetMetadata[length - 1]) == PROXY_ID,
- ASSET_PROXY_ID_MISMATCH
+ proxyId == PROXY_ID,
+ PROXY_ID_MISMATCH
);
- // Decode metadata.
- address token = readAddress(assetMetadata, 0);
-
// Transfer tokens.
bool success = IERC20Token(token).transferFrom(from, to, amount);
require(
diff --git a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol
index 07e01c774..f35e48eee 100644
--- a/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol
+++ b/packages/contracts/src/contracts/current/protocol/AssetProxy/ERC721Proxy.sol
@@ -20,12 +20,14 @@ pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;
import "../../utils/LibBytes/LibBytes.sol";
+import "../../utils/LibAssetProxyDecoder/LibAssetProxyDecoder.sol";
import "./MixinAssetProxy.sol";
import "./MixinAuthorizable.sol";
import "../../tokens/ERC721Token/ERC721Token.sol";
contract ERC721Proxy is
LibBytes,
+ LibAssetProxyDecoder,
MixinAssetProxy,
MixinAuthorizable
{
@@ -33,19 +35,29 @@ contract ERC721Proxy is
// Id of this proxy.
uint8 constant PROXY_ID = 2;
+ string constant PROXY_ID_MISMATCH = "Proxy id in metadata does not match this proxy id.";
+
/// @dev Internal version of `transferFrom`.
- /// @param assetMetadata Encoded byte array.
+ /// @param proxyData Encoded byte array.
/// @param from Address to transfer asset from.
/// @param to Address to transfer asset to.
/// @param amount Amount of asset to transfer.
function transferFromInternal(
- bytes memory assetMetadata,
+ bytes memory proxyData,
address from,
address to,
uint256 amount
)
internal
{
+ // Decode proxy data.
+ (
+ uint8 proxyId,
+ address token,
+ uint256 tokenId,
+ bytes memory data
+ ) = decodeERC721Data(proxyData);
+
// Data must be intended for this proxy.
uint256 length = assetMetadata.length;
@@ -56,8 +68,8 @@ contract ERC721Proxy is
// TODO: Is this too inflexible in the future?
require(
- uint8(assetMetadata[length - 1]) == PROXY_ID,
- ASSET_PROXY_ID_MISMATCH
+ proxyId == PROXY_ID,
+ PROXY_ID_MISMATCH
);
// There exists only 1 of each token.
@@ -66,15 +78,9 @@ contract ERC721Proxy is
INVALID_AMOUNT
);
- // Decode metadata
- address token = readAddress(assetMetadata, 0);
- uint256 tokenId = readUint256(assetMetadata, 20);
-
// Transfer token.
// Either succeeds or throws.
- // @TODO: Call safeTransferFrom if there is additional
- // data stored in `assetMetadata`.
- ERC721Token(token).transferFrom(from, to, tokenId);
+ ERC721Token(token).safeTransferFrom(from, to, tokenId, data);
}
/// @dev Gets the proxy id associated with the proxy address.
diff --git a/packages/contracts/src/contracts/current/test/DummyERC721Receiver/DummyERC721Receiver.sol b/packages/contracts/src/contracts/current/test/DummyERC721Receiver/DummyERC721Receiver.sol
new file mode 100644
index 000000000..1596f3357
--- /dev/null
+++ b/packages/contracts/src/contracts/current/test/DummyERC721Receiver/DummyERC721Receiver.sol
@@ -0,0 +1,62 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2016 Smart Contract Solutions, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+pragma solidity ^0.4.24;
+
+import "../../tokens/ERC721Token/IERC721Receiver.sol";
+
+contract DummyERC721Receiver is
+ IERC721Receiver
+{
+
+ event TokenReceived(
+ address from,
+ uint256 tokenId,
+ bytes data
+ );
+
+ /**
+ * @notice Handle the receipt of an NFT
+ * @dev The ERC721 smart contract calls this function on the recipient
+ * after a `safetransfer`. This function MAY throw to revert and reject the
+ * transfer. This function MUST use 50,000 gas or less. Return of other
+ * than the magic value MUST result in the transaction being reverted.
+ * Note: the contract address is always the message sender.
+ * @param _from The sending address
+ * @param _tokenId The NFT identifier which is being transfered
+ * @param _data Additional data with no specified format
+ * @return `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`
+ */
+ function onERC721Received(
+ address _from,
+ uint256 _tokenId,
+ bytes _data)
+ public
+ returns (bytes4)
+ {
+ emit TokenReceived(_from, _tokenId, _data);
+ return ERC721_RECEIVED;
+ }
+}
diff --git a/packages/contracts/src/contracts/current/test/TestLibMem/TestLibMem.sol b/packages/contracts/src/contracts/current/test/TestLibMem/TestLibMem.sol
new file mode 100644
index 000000000..4cf62bf3a
--- /dev/null
+++ b/packages/contracts/src/contracts/current/test/TestLibMem/TestLibMem.sol
@@ -0,0 +1,238 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ 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.
+
+*/
+
+pragma solidity ^0.4.24;
+
+import "../../utils/LibMem/LibMem.sol";
+import "../../utils/LibBytes/LibBytes.sol";
+
+contract TestLibMem is
+ LibMem,
+ LibBytes
+{
+
+ function test1()
+ public
+ pure
+ {
+ // Length of array & length to copy
+ uint256 length = 0;
+
+ // Create source array
+ bytes memory sourceArray = new bytes(length);
+
+ // Create dest array with same contents as source array
+ bytes memory destArray = new bytes(length);
+ memcpy(
+ getMemAddress(destArray) + 32, // skip copying array length
+ getMemAddress(sourceArray) + 32, // skip copying array length
+ length
+ );
+
+ // Verify contents of source & dest arrays match
+ require(
+ areBytesEqual(sourceArray, destArray),
+ "Test #1 failed. Array contents are not the same."
+ );
+ }
+
+ function test2()
+ public
+ pure
+ {
+ // Length of array & length to copy
+ uint256 length = 1;
+
+ // Create source array
+ bytes memory sourceArray = new bytes(length);
+ sourceArray[0] = byte(1);
+
+ // Create dest array with same contents as source array
+ bytes memory destArray = new bytes(length);
+ memcpy(
+ getMemAddress(destArray) + 32, // skip copying array length
+ getMemAddress(sourceArray) + 32, // skip copying array length
+ length
+ );
+
+ // Verify contents of source & dest arrays match
+ require(
+ areBytesEqual(sourceArray, destArray),
+ "Test #2 failed. Array contents are not the same."
+ );
+ }
+
+ function test3()
+ public
+ pure
+ {
+ // Length of array & length to copy
+ uint256 length = 11;
+
+ // Create source array
+ bytes memory sourceArray = new bytes(length);
+ for(uint256 i = 0; i < length; ++i) {
+ sourceArray[i] = byte((i % 0xF) + 1); // [1..f]
+ }
+
+ // Create dest array with same contents as source array
+ bytes memory destArray = new bytes(length);
+ memcpy(
+ getMemAddress(destArray) + 32, // skip copying array length
+ getMemAddress(sourceArray) + 32, // skip copying array length
+ length
+ );
+
+ // Verify contents of source & dest arrays match
+ require(
+ areBytesEqual(sourceArray, destArray),
+ "Test #3 failed. Array contents are not the same."
+ );
+ }
+
+ function test4()
+ public
+ pure
+ {
+ // Length of array & length to copy
+ uint256 length = 32;
+
+ // Create source array
+ bytes memory sourceArray = new bytes(length);
+ for(uint256 i = 0; i < length; ++i) {
+ sourceArray[i] = byte((i % 0xF) + 1); // [1..f]
+ }
+
+ // Create dest array with same contents as source array
+ bytes memory destArray = new bytes(length);
+ memcpy(
+ getMemAddress(destArray) + 32, // skip copying array length
+ getMemAddress(sourceArray) + 32, // skip copying array length
+ length
+ );
+
+ // Verify contents of source & dest arrays match
+ require(
+ areBytesEqual(sourceArray, destArray),
+ "Test #4 failed. Array contents are not the same."
+ );
+ }
+
+ function test5()
+ public
+ pure
+ {
+ // Length of array & length to copy
+ uint256 length = 72;
+
+ // Create source array
+ bytes memory sourceArray = new bytes(length);
+ for(uint256 i = 0; i < length; ++i) {
+ sourceArray[i] = byte((i % 0xF) + 1); // [1..f]
+ }
+
+ // Create dest array with same contents as source array
+ bytes memory destArray = new bytes(length);
+ memcpy(
+ getMemAddress(destArray) + 32, // skip copying array length
+ getMemAddress(sourceArray) + 32, // skip copying array length
+ length
+ );
+
+ // Verify contents of source & dest arrays match
+ require(
+ areBytesEqual(sourceArray, destArray),
+ "Test #5 failed. Array contents are not the same."
+ );
+ }
+
+
+ function test6()
+ public
+ pure
+ {
+ // Length of arrays
+ uint256 length1 = 72;
+ uint256 length2 = 100;
+
+ // The full source array is used for comparisons at the end
+ bytes memory fullSourceArray = new bytes(length1 + length2);
+
+ // First source array
+ bytes memory sourceArray1 = new bytes(length1);
+ for(uint256 i = 0; i < length1; ++i) {
+ sourceArray1[i] = byte((i % 0xF) + 1); // [1..f]
+ fullSourceArray[i] = byte((i % 0xF) + 1); // [1..f]
+ }
+
+ // Second source array
+ bytes memory sourceArray2 = new bytes(length2);
+ for(uint256 j = 0; i < length2; ++i) {
+ sourceArray2[j] = byte((j % 0xF) + 1); // [1..f]
+ fullSourceArray[length1+j] = byte((j % 0xF) + 1); // [1..f]
+ }
+
+ // Create dest array with same contents as source arrays
+ bytes memory destArray = new bytes(length1 + length2);
+ memcpy(
+ getMemAddress(destArray) + 32, // skip copying array length
+ getMemAddress(sourceArray1) + 32, // skip copying array length
+ length1
+ );
+ memcpy(
+ getMemAddress(destArray) + 32 + length1, // skip copying array length + sourceArray1 bytes
+ getMemAddress(sourceArray2) + 32, // skip copying array length
+ length2
+ );
+
+ // Verify contents of source & dest arrays match
+ require(
+ areBytesEqual(fullSourceArray, destArray),
+ "Test #6 failed. Array contents are not the same."
+ );
+ }
+
+ function test7()
+ public
+ pure
+ {
+ // Length of array & length to copy
+ uint256 length = 72;
+
+ // Create source array
+ bytes memory sourceArray = new bytes(length);
+ for(uint256 i = 0; i < length; ++i) {
+ sourceArray[i] = byte((i % 0xF) + 1); // [1..f]
+ }
+
+ // Create dest array with same contents as source array
+ bytes memory destArray = new bytes(length);
+ memcpy(
+ getMemAddress(destArray) + 32, // skip copying array length
+ getMemAddress(sourceArray) + 32, // skip copying array length
+ length - 8 // Copy all but last byte.
+ );
+
+ // Verify contents of source & dest arrays match
+ // We expect this to fail
+ require(
+ areBytesEqual(sourceArray, destArray),
+ "Test #7 failed. Array contents are not the same."
+ );
+ }
+}
diff --git a/packages/contracts/src/contracts/current/utils/LibAssetProxyDecoder/LibAssetProxyDecoder.sol b/packages/contracts/src/contracts/current/utils/LibAssetProxyDecoder/LibAssetProxyDecoder.sol
new file mode 100644
index 000000000..ba53f2769
--- /dev/null
+++ b/packages/contracts/src/contracts/current/utils/LibAssetProxyDecoder/LibAssetProxyDecoder.sol
@@ -0,0 +1,74 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ 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.
+
+*/
+
+pragma solidity ^0.4.24;
+pragma experimental ABIEncoderV2;
+
+import "../LibBytes/LibBytes.sol";
+
+contract LibAssetProxyDecoder is
+ LibBytes
+{
+
+ string constant INVALID_ERC20_METADATA_LENGTH = "Metadata must have a length of 21.";
+ string constant INVALID_ERC721_METADATA_LENGTH = "Metadata must have a length of at least 53.";
+
+ /// @dev Decodes ERC721 Asset Proxy data
+ function decodeERC20Data(bytes memory proxyData)
+ internal
+ pure
+ returns (
+ uint8 proxyId,
+ address token
+ )
+ {
+ require(
+ proxyData.length == 21,
+ INVALID_ERC20_METADATA_LENGTH
+ );
+ proxyId = uint8(proxyData[0]);
+ token = readAddress(proxyData, 1);
+
+ return (proxyId, token);
+ }
+
+ /// @dev Decodes ERC721 Asset Proxy data
+ function decodeERC721Data(bytes memory proxyData)
+ internal
+ pure
+ returns (
+ uint8 proxyId,
+ address token,
+ uint256 tokenId,
+ bytes memory data
+ )
+ {
+ require(
+ proxyData.length >= 53,
+ INVALID_ERC721_METADATA_LENGTH
+ );
+ proxyId = uint8(proxyData[0]);
+ token = readAddress(proxyData, 1);
+ tokenId = readUint256(proxyData, 21);
+ if (proxyData.length > 53) {
+ data = readBytes(proxyData, 53);
+ }
+
+ return (proxyId, token, tokenId, data);
+ }
+}
diff --git a/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol
index df2221c93..fb8359462 100644
--- a/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol
+++ b/packages/contracts/src/contracts/current/utils/LibBytes/LibBytes.sol
@@ -18,7 +18,11 @@
pragma solidity ^0.4.24;
-contract LibBytes {
+import "../LibMem/LibMem.sol";
+
+contract LibBytes is
+ LibMem
+{
// Revert reasons
string constant GT_ZERO_LENGTH_REQUIRED = "Length must be greater than 0.";
@@ -42,7 +46,7 @@ contract LibBytes {
// Store last byte.
result = b[b.length - 1];
-
+
assembly {
// Decrement length of byte array.
let newLen := sub(mload(b), 1)
@@ -125,7 +129,7 @@ contract LibBytes {
require(
b.length >= index + 20, // 20 is length of address
GTE_20_LENGTH_REQUIRED
- );
+ );
// Add offset to index:
// 1. Arrays are prefixed by 32-byte length parameter (add 32 to index)
@@ -157,7 +161,7 @@ contract LibBytes {
require(
b.length >= index + 20, // 20 is length of address
GTE_20_LENGTH_REQUIRED
- );
+ );
// Add offset to index:
// 1. Arrays are prefixed by 32-byte length parameter (add 32 to index)
@@ -264,6 +268,7 @@ contract LibBytes {
writeBytes32(b, index, bytes32(input));
}
+=======
/// @dev Reads the first 4 bytes from a byte array of arbitrary length.
/// @param b Byte array to read first 4 bytes from.
/// @return First 4 bytes of data.
@@ -281,4 +286,67 @@ contract LibBytes {
}
return result;
}
+
+ /// @dev Reads a uint256 value from a position in a byte array.
+ /// @param b Byte array containing a uint256 value.
+ /// @param index Index in byte array of uint256 value.
+ /// @return uint256 value from byte array.
+ function readBytes(
+ bytes memory b,
+ uint256 index
+ )
+ internal
+ pure
+ returns (bytes memory result)
+ {
+ // Read length of nested bytes
+ require(
+ b.length >= index + 32,
+ GTE_32_LENGTH_REQUIRED
+ );
+ uint256 nestedBytesLength = readUint256(b, index);
+
+ // Assert length of <b> is valid, given
+ // length of nested bytes
+ require(
+ b.length >= index + 32 + nestedBytesLength,
+ GTE_32_LENGTH_REQUIRED
+ );
+
+ // Allocate memory and copy value to result
+ result = new bytes(nestedBytesLength);
+ memcpy(
+ getMemAddress(result) + 32, // +32 skips array length
+ getMemAddress(b) + index + 32, // +32 skips array length
+ nestedBytesLength
+ );
+
+ return result;
+ }
+
+ /// @dev Writes a uint256 into a specific position in a byte array.
+ /// @param b Byte array to insert <input> into.
+ /// @param index Index in byte array of <input>.
+ /// @param input uint256 to put into byte array.
+ function writeBytes(
+ bytes memory b,
+ uint256 index,
+ bytes memory input
+ )
+ internal
+ pure
+ {
+ // Read length of nested bytes
+ require(
+ b.length >= index + 32 /* 32 bytes to store length */ + input.length,
+ GTE_32_LENGTH_REQUIRED
+ );
+
+ // Copy <input> into <b>
+ memcpy(
+ getMemAddress(b) + index,
+ getMemAddress(input),
+ input.length + 32 /* 32 bytes to store length */
+ );
+ }
}
diff --git a/packages/contracts/src/contracts/current/utils/LibMem/LibMem.sol b/packages/contracts/src/contracts/current/utils/LibMem/LibMem.sol
new file mode 100644
index 000000000..b07a5da54
--- /dev/null
+++ b/packages/contracts/src/contracts/current/utils/LibMem/LibMem.sol
@@ -0,0 +1,104 @@
+/*
+
+ Copyright 2018 ZeroEx Intl.
+
+ 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.
+
+*/
+
+pragma solidity ^0.4.24;
+
+contract LibMem {
+
+ function getMemAddress(bytes memory input)
+ internal
+ pure
+ returns (uint256 address_)
+ {
+ assembly {
+ address_ := input
+ }
+ return address_;
+ }
+
+ /// @dev Writes a uint256 into a specific position in a byte array.
+ /// @param dest memory adress to copy bytes to
+ function memcpy(
+ uint256 dest,
+ uint256 source,
+ uint256 length
+ )
+ internal
+ pure
+ {
+ // Base cases
+ if(length == 0) return;
+ if(source == dest) return;
+
+ // Copy bytes from source to dest
+ assembly {
+ // Compute number of complete words to copy + remaining bytes
+ let lenFullWords := div(add(length, 0x1F), 0x20)
+ let remainder := mod(length, 0x20)
+ if gt(remainder, 0) {
+ lenFullWords := sub(lenFullWords, 1)
+ }
+
+ // Copy full words from source to dest
+ let offset := 0
+ let maxOffset := mul(0x20, lenFullWords)
+ for {offset := 0} lt(offset, maxOffset) {offset := add(offset, 0x20)} {
+ mstore(add(dest, offset), mload(add(source, offset)))
+ }
+
+ // Copy remaining bytes
+ if gt(remainder, 0) {
+ // Read a full word from source, containing X bytes to copy to dest.
+ // We only want to keep the X bytes, zeroing out the remaining bytes.
+ // We accomplish this by a right shift followed by a left shift.
+ // Example:
+ // Suppose a word of 8 bits has all 1's: [11111111]
+ // Let X = 7 (we want to copy the first 7 bits)
+ // Apply a right shift of 1: [01111111]
+ // Apply a left shift of 1: [11111110]
+ let sourceShiftFactor := exp(2, mul(8, sub(0x20, remainder)))
+ let sourceWord := mload(add(source, offset))
+ let sourceBytes := mul(div(sourceWord, sourceShiftFactor), sourceShiftFactor)
+
+ // Read a full word from dest, containing (32-X) bytes to retain.
+ // We need to zero out the remaining bytes to be overwritten by source,
+ // while retaining the (32-X) bytes we don't want to overwrite.
+ // We accomplish this by a left shift followed by a right shift.
+ // Example:
+ // Suppose a word of 8 bits has all 1's: [11111111]
+ // Let X = 7 (we want to free the first 7 bits, and retain the last bit)
+ // Apply a left shift of 1: [11111110]
+ // Apply a right shift of 1: [01111111]
+ let destShiftFactor := exp(2, mul(8, remainder))
+ let destWord := mload(add(dest, offset))
+ let destBytes := div(mul(destWord, destShiftFactor), destShiftFactor)
+
+ // Combine the source and dest bytes. There should be no overlap:
+ // The source bytes run from [0..X-1] and the dest bytes from [X..31].
+ // Example:
+ // Following the example from above, we have [11111110]
+ // from the source word and [01111111] from the dest word.
+ // Combine these words using <or> to get [11111111].
+ let combinedDestWord := or(sourceBytes, destBytes)
+
+ // Store the combined word into dest
+ mstore(add(dest, offset), combinedDestWord)
+ }
+ }
+ }
+}