diff --git a/contracts/asset-proxy/contracts/src/ERC1155Proxy.sol b/contracts/asset-proxy/contracts/src/ERC1155Proxy.sol index fc587cfd61..024e1cba06 100644 --- a/contracts/asset-proxy/contracts/src/ERC1155Proxy.sol +++ b/contracts/asset-proxy/contracts/src/ERC1155Proxy.sol @@ -163,6 +163,10 @@ contract ERC1155Proxy is // Load length in bytes of `assetData` let assetDataLength := calldataload(assetDataOffset) + // End of asset data in calldata + // +32 for length field + let assetDataEnd := add(assetDataOffset, add(assetDataLength, 32)) + // Load offset to parameters section in asset data let paramsInAssetDataOffset := add(assetDataOffset, 36) @@ -181,6 +185,16 @@ contract ERC1155Proxy is let idsOffset := add(paramsInAssetDataOffset, calldataload(add(assetDataOffset, 68))) let idsLength := calldataload(idsOffset) let idsLengthInBytes := mul(idsLength, 32) + let idsBegin := add(idsOffset, 32) + let idsEnd := add(idsBegin, idsLengthInBytes) + if gt(idsEnd, assetDataEnd) { + // Revert with `Error("INVALID_IDS_OFFSET")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x00000012494e56414c49445f4944535f4f464653455400000000000000000000) + mstore(96, 0) + revert(0, 100) + } calldatacopy( dataAreaEndOffset, idsOffset, @@ -196,14 +210,22 @@ contract ERC1155Proxy is let valuesOffset := add(paramsInAssetDataOffset, calldataload(add(assetDataOffset, 100))) let valuesLength := calldataload(valuesOffset) let valuesLengthInBytes := mul(valuesLength, 32) + let valuesBegin := add(valuesOffset, 32) + let valuesEnd := add(valuesBegin, valuesLengthInBytes) + if gt(valuesEnd, assetDataEnd) { + // Revert with `Error("INVALID_VALUES_OFFSET")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x00000015494e56414c49445f56414c5545535f4f464653455400000000000000) + mstore(96, 0) + revert(0, 100) + } // Store length of `values` mstore(dataAreaEndOffset, valuesLength) dataAreaEndOffset := add(dataAreaEndOffset, 32) // Scale and store elements of `values` - let valuesBegin := add(valuesOffset, 32) - let valuesEnd := add(valuesBegin, valuesLengthInBytes) for { let currentValueOffset := valuesBegin } lt(currentValueOffset, valuesEnd) { currentValueOffset := add(currentValueOffset, 32) } @@ -237,13 +259,25 @@ contract ERC1155Proxy is // Copy `data` from `assetData` (Table #2) to memory (Table #3) let dataOffset := add(paramsInAssetDataOffset, calldataload(add(assetDataOffset, 132))) let dataLengthInBytes := calldataload(dataOffset) - let dataLengthInWords := div(add(dataLengthInBytes, 31), 32) - let dataLengthInBytesWordAligned := mul(dataLengthInWords, 32) + let dataBegin := add(dataOffset, 32) + let dataEnd := add(dataBegin, dataLengthInBytes) + if gt(dataEnd, assetDataEnd) { + // Revert with `Error("INVALID_DATA_OFFSET")` + mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) + mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) + mstore(64, 0x00000013494e56414c49445f444154415f4f4646534554000000000000000000) + mstore(96, 0) + revert(0, 100) + } calldatacopy( dataAreaEndOffset, dataOffset, - add(dataLengthInBytesWordAligned, 32) + add(dataLengthInBytes, 32) ) + + // Update the end of data offset to be word-aligned + let dataLengthInWords := div(add(dataLengthInBytes, 31), 32) + let dataLengthInBytesWordAligned := mul(dataLengthInWords, 32) dataAreaEndOffset := add(dataAreaEndOffset, add(dataLengthInBytesWordAligned, 32)) ////////// STEP 3/3 - Execute Transfer ////////// diff --git a/contracts/asset-proxy/test/erc1155_proxy.ts b/contracts/asset-proxy/test/erc1155_proxy.ts index 52bd158cc3..59152a944b 100644 --- a/contracts/asset-proxy/test/erc1155_proxy.ts +++ b/contracts/asset-proxy/test/erc1155_proxy.ts @@ -1052,6 +1052,306 @@ describe('ERC1155Proxy', () => { ]; expect(finalBalances).to.be.deep.equal(expectedFinalBalances); }); + it('should revert if token ids resolves to outside the bounds of calldata', async () => { + // setup test parameters + const tokensToTransfer = fungibleTokens.slice(0, 1); + const valuesToTransfer = [fungibleValueToTransferLarge]; + const valueMultiplier = valueMultiplierSmall; + const erc1155ContractAddress = erc1155Wrapper.getContract().address; + const assetData = assetDataUtils.encodeERC1155AssetData( + erc1155ContractAddress, + tokensToTransfer, + valuesToTransfer, + receiverCallbackData, + ); + // The asset data we just generated will look like this: + // a7cb5fb7 + // 0x 0000000000000000000000000b1ba0af832d7c05fd64161e0db78e85978e8082 + // 0x20 0000000000000000000000000000000000000000000000000000000000000080 // offset to token ids + // 0x40 00000000000000000000000000000000000000000000000000000000000000c0 + // 0x60 0000000000000000000000000000000000000000000000000000000000000100 + // 0x80 0000000000000000000000000000000000000000000000000000000000000001 + // 0xA0 0000000000000000000000000000000100000000000000000000000000000000 + // 0xC0 0000000000000000000000000000000000000000000000000000000000000001 + // 0xE0 0000000000000000000000000000000000000000000000878678326eac900000 + // 0x100 0000000000000000000000000000000000000000000000000000000000000004 + // 0x120 0102030400000000000000000000000000000000000000000000000000000000 + // + // We want to chan ge the offset to token ids to point outside the calldata. + const encodedOffsetToTokenIds = '0000000000000000000000000000000000000000000000000000000000000080'; + const badEncodedOffsetToTokenIds = '0000000000000000000000000000000000000000000000000000000000000180'; + const assetDataWithBadTokenIdsOffset = assetData.replace( + encodedOffsetToTokenIds, + badEncodedOffsetToTokenIds, + ); + // execute transfer + await expectTransactionFailedAsync( + erc1155ProxyWrapper.transferFromAsync( + spender, + receiverContract, + erc1155Contract.address, + tokensToTransfer, + valuesToTransfer, + valueMultiplier, + receiverCallbackData, + authorized, + assetDataWithBadTokenIdsOffset, + ), + RevertReason.InvalidIdsOffset, + ); + }); + it('should revert if an element of token ids lies to outside the bounds of calldata', async () => { + // setup test parameters + const tokensToTransfer = fungibleTokens.slice(0, 1); + const valuesToTransfer = [fungibleValueToTransferLarge]; + const valueMultiplier = valueMultiplierSmall; + const erc1155ContractAddress = erc1155Wrapper.getContract().address; + const assetData = assetDataUtils.encodeERC1155AssetData( + erc1155ContractAddress, + tokensToTransfer, + valuesToTransfer, + receiverCallbackData, + ); + // The asset data we just generated will look like this: + // a7cb5fb7 + // 0x 0000000000000000000000000b1ba0af832d7c05fd64161e0db78e85978e8082 + // 0x20 0000000000000000000000000000000000000000000000000000000000000080 // offset to token ids + // 0x40 00000000000000000000000000000000000000000000000000000000000000c0 + // 0x60 0000000000000000000000000000000000000000000000000000000000000100 + // 0x80 0000000000000000000000000000000000000000000000000000000000000001 + // 0xA0 0000000000000000000000000000000100000000000000000000000000000000 + // 0xC0 0000000000000000000000000000000000000000000000000000000000000001 + // 0xE0 0000000000000000000000000000000000000000000000878678326eac900000 + // 0x100 0000000000000000000000000000000000000000000000000000000000000004 + // 0x120 0102030400000000000000000000000000000000000000000000000000000000 + // + // We want to chan ge the offset to token ids to the end of calldata. + // Then we'll add an invalid length: we encode length of 2 but only add 1 element. + const encodedOffsetToTokenIds = '0000000000000000000000000000000000000000000000000000000000000080'; + const newEcodedOffsetToTokenIds = '0000000000000000000000000000000000000000000000000000000000000140'; + const assetDataWithNewTokenIdsOffset = assetData.replace( + encodedOffsetToTokenIds, + newEcodedOffsetToTokenIds, + ); + const encodedTokenIdsLength = '0000000000000000000000000000000000000000000000000000000000000002'; + const encodedTokenIdValues = '0000000000000000000000000000000000000000000000000000000000000001'; + const assetDataWithBadTokenIds = `${assetDataWithNewTokenIdsOffset}${encodedTokenIdsLength}${encodedTokenIdValues}`; + // execute transfer + await expectTransactionFailedAsync( + erc1155ProxyWrapper.transferFromAsync( + spender, + receiverContract, + erc1155Contract.address, + tokensToTransfer, + valuesToTransfer, + valueMultiplier, + receiverCallbackData, + authorized, + assetDataWithBadTokenIds, + ), + RevertReason.InvalidIdsOffset, + ); + }); + it('should revert if token values resolves to outside the bounds of calldata', async () => { + // setup test parameters + const tokensToTransfer = fungibleTokens.slice(0, 1); + const valuesToTransfer = [fungibleValueToTransferLarge]; + const valueMultiplier = valueMultiplierSmall; + const erc1155ContractAddress = erc1155Wrapper.getContract().address; + const assetData = assetDataUtils.encodeERC1155AssetData( + erc1155ContractAddress, + tokensToTransfer, + valuesToTransfer, + receiverCallbackData, + ); + // The asset data we just generated will look like this: + // a7cb5fb7 + // 0x 0000000000000000000000000b1ba0af832d7c05fd64161e0db78e85978e8082 + // 0x20 0000000000000000000000000000000000000000000000000000000000000080 + // 0x40 00000000000000000000000000000000000000000000000000000000000000c0 // offset to token values + // 0x60 0000000000000000000000000000000000000000000000000000000000000100 + // 0x80 0000000000000000000000000000000000000000000000000000000000000001 + // 0xA0 0000000000000000000000000000000100000000000000000000000000000000 + // 0xC0 0000000000000000000000000000000000000000000000000000000000000001 + // 0xE0 0000000000000000000000000000000000000000000000878678326eac900000 + // 0x100 0000000000000000000000000000000000000000000000000000000000000004 + // 0x120 0102030400000000000000000000000000000000000000000000000000000000 + // + // We want to chan ge the offset to token values to point outside the calldata. + const encodedOffsetToTokenValues = '00000000000000000000000000000000000000000000000000000000000000c0'; + const badEncodedOffsetToTokenValues = '00000000000000000000000000000000000000000000000000000000000001c0'; + const assetDataWithBadTokenIdsOffset = assetData.replace( + encodedOffsetToTokenValues, + badEncodedOffsetToTokenValues, + ); + // execute transfer + await expectTransactionFailedAsync( + erc1155ProxyWrapper.transferFromAsync( + spender, + receiverContract, + erc1155Contract.address, + tokensToTransfer, + valuesToTransfer, + valueMultiplier, + receiverCallbackData, + authorized, + assetDataWithBadTokenIdsOffset, + ), + RevertReason.InvalidValuesOffset, + ); + }); + it('should revert if an element of token values lies to outside the bounds of calldata', async () => { + // setup test parameters + const tokensToTransfer = fungibleTokens.slice(0, 1); + const valuesToTransfer = [fungibleValueToTransferLarge]; + const valueMultiplier = valueMultiplierSmall; + const erc1155ContractAddress = erc1155Wrapper.getContract().address; + const assetData = assetDataUtils.encodeERC1155AssetData( + erc1155ContractAddress, + tokensToTransfer, + valuesToTransfer, + receiverCallbackData, + ); + // The asset data we just generated will look like this: + // a7cb5fb7 + // 0x 0000000000000000000000000b1ba0af832d7c05fd64161e0db78e85978e8082 + // 0x20 0000000000000000000000000000000000000000000000000000000000000080 + // 0x40 00000000000000000000000000000000000000000000000000000000000000c0 // offset to token values + // 0x60 0000000000000000000000000000000000000000000000000000000000000100 + // 0x80 0000000000000000000000000000000000000000000000000000000000000001 + // 0xA0 0000000000000000000000000000000100000000000000000000000000000000 + // 0xC0 0000000000000000000000000000000000000000000000000000000000000001 + // 0xE0 0000000000000000000000000000000000000000000000878678326eac900000 + // 0x100 0000000000000000000000000000000000000000000000000000000000000004 + // 0x120 0102030400000000000000000000000000000000000000000000000000000000 + // + // We want to chan ge the offset to token values to the end of calldata. + // Then we'll add an invalid length: we encode length of 2 but only add 1 element. + const encodedOffsetToTokenValues = '00000000000000000000000000000000000000000000000000000000000000c0'; + const newEcodedOffsetToTokenValues = '0000000000000000000000000000000000000000000000000000000000000140'; + const assetDataWithNewTokenValuesOffset = assetData.replace( + encodedOffsetToTokenValues, + newEcodedOffsetToTokenValues, + ); + const encodedTokenValuesLength = '0000000000000000000000000000000000000000000000000000000000000002'; + const encodedTokenValuesElements = '0000000000000000000000000000000000000000000000000000000000000001'; + const assetDataWithBadTokenIds = `${assetDataWithNewTokenValuesOffset}${encodedTokenValuesLength}${encodedTokenValuesElements}`; + // execute transfer + await expectTransactionFailedAsync( + erc1155ProxyWrapper.transferFromAsync( + spender, + receiverContract, + erc1155Contract.address, + tokensToTransfer, + valuesToTransfer, + valueMultiplier, + receiverCallbackData, + authorized, + assetDataWithBadTokenIds, + ), + RevertReason.InvalidValuesOffset, + ); + }); + it('should revert if token data resolves to outside the bounds of calldata', async () => { + // setup test parameters + const tokensToTransfer = fungibleTokens.slice(0, 1); + const valuesToTransfer = [fungibleValueToTransferLarge]; + const valueMultiplier = valueMultiplierSmall; + const erc1155ContractAddress = erc1155Wrapper.getContract().address; + const assetData = assetDataUtils.encodeERC1155AssetData( + erc1155ContractAddress, + tokensToTransfer, + valuesToTransfer, + receiverCallbackData, + ); + // The asset data we just generated will look like this: + // a7cb5fb7 + // 0x 0000000000000000000000000b1ba0af832d7c05fd64161e0db78e85978e8082 + // 0x20 0000000000000000000000000000000000000000000000000000000000000080 + // 0x40 00000000000000000000000000000000000000000000000000000000000000c0 + // 0x60 0000000000000000000000000000000000000000000000000000000000000100 // offset to token data + // 0x80 0000000000000000000000000000000000000000000000000000000000000001 + // 0xA0 0000000000000000000000000000000100000000000000000000000000000000 + // 0xC0 0000000000000000000000000000000000000000000000000000000000000001 + // 0xE0 0000000000000000000000000000000000000000000000878678326eac900000 + // 0x100 0000000000000000000000000000000000000000000000000000000000000004 + // 0x120 0102030400000000000000000000000000000000000000000000000000000000 + // + // We want to chan ge the offset to token data to point outside the calldata. + const encodedOffsetToTokenData = '0000000000000000000000000000000000000000000000000000000000000100'; + const badEncodedOffsetToTokenData = '00000000000000000000000000000000000000000000000000000000000001c0'; + const assetDataWithBadTokenDataOffset = assetData.replace( + encodedOffsetToTokenData, + badEncodedOffsetToTokenData, + ); + // execute transfer + await expectTransactionFailedAsync( + erc1155ProxyWrapper.transferFromAsync( + spender, + receiverContract, + erc1155Contract.address, + tokensToTransfer, + valuesToTransfer, + valueMultiplier, + receiverCallbackData, + authorized, + assetDataWithBadTokenDataOffset, + ), + RevertReason.InvalidDataOffset, + ); + }); + it('should revert if an element of token data lies to outside the bounds of calldata', async () => { + // setup test parameters + const tokensToTransfer = fungibleTokens.slice(0, 1); + const valuesToTransfer = [fungibleValueToTransferLarge]; + const valueMultiplier = valueMultiplierSmall; + const erc1155ContractAddress = erc1155Wrapper.getContract().address; + const assetData = assetDataUtils.encodeERC1155AssetData( + erc1155ContractAddress, + tokensToTransfer, + valuesToTransfer, + receiverCallbackData, + ); + // The asset data we just generated will look like this: + // a7cb5fb7 + // 0x 0000000000000000000000000b1ba0af832d7c05fd64161e0db78e85978e8082 + // 0x20 0000000000000000000000000000000000000000000000000000000000000080 + // 0x40 00000000000000000000000000000000000000000000000000000000000000c0 + // 0x60 0000000000000000000000000000000000000000000000000000000000000100 // offset to token data + // 0x80 0000000000000000000000000000000000000000000000000000000000000001 + // 0xA0 0000000000000000000000000000000100000000000000000000000000000000 + // 0xC0 0000000000000000000000000000000000000000000000000000000000000001 + // 0xE0 0000000000000000000000000000000000000000000000878678326eac900000 + // 0x100 0000000000000000000000000000000000000000000000000000000000000004 + // 0x120 0102030400000000000000000000000000000000000000000000000000000000 + // + // We want to chan ge the offset to token data to the end of calldata. + // Then we'll add an invalid length: we encode length of 33 but only add 32 elements. + const encodedOffsetToTokenData = '0000000000000000000000000000000000000000000000000000000000000100'; + const newEcodedOffsetToTokenData = '0000000000000000000000000000000000000000000000000000000000000140'; + const assetDataWithNewTokenDataOffset = assetData.replace( + encodedOffsetToTokenData, + newEcodedOffsetToTokenData, + ); + const encodedTokenDataLength = '0000000000000000000000000000000000000000000000000000000000000021'; + const encodedTokenDataElements = '0000000000000000000000000000000000000000000000000000000000000001'; + const assetDataWithBadTokenData = `${assetDataWithNewTokenDataOffset}${encodedTokenDataLength}${encodedTokenDataElements}`; + // execute transfer + await expectTransactionFailedAsync( + erc1155ProxyWrapper.transferFromAsync( + spender, + receiverContract, + erc1155Contract.address, + tokensToTransfer, + valuesToTransfer, + valueMultiplier, + receiverCallbackData, + authorized, + assetDataWithBadTokenData, + ), + RevertReason.InvalidDataOffset, + ); + }); it('should transfer nothing if value is zero', async () => { // setup test parameters const tokenHolders = [spender, receiver]; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index b440490d20..d501f859a2 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -312,6 +312,9 @@ export enum RevertReason { TriedToMintNonFungibleForFungibleToken = 'TRIED_TO_MINT_NON_FUNGIBLE_FOR_FUNGIBLE_TOKEN', TransferRejected = 'TRANSFER_REJECTED', Uint256Underflow = 'UINT256_UNDERFLOW', + InvalidIdsOffset = 'INVALID_IDS_OFFSET', + InvalidValuesOffset = 'INVALID_VALUES_OFFSET', + InvalidDataOffset = 'INVALID_DATA_OFFSET', } export enum StatusCodes {