From a79ce8c26c7bcc1b3c27bc2ceab23f2fd5f80840 Mon Sep 17 00:00:00 2001 From: Michael Standen Date: Mon, 29 May 2023 10:58:58 +1200 Subject: [PATCH] Use storage slot for upgradeable contracts --- .../mocks/ERC1155MetadataUpgradeableMock.sol | 2 +- src/contracts/tokens/ERC1155/ERC1155Meta.sol | 7 +- .../ERC1155MetaPackedBalanceUpgradeable.sol | 36 +- ...RC1155MintBurnPackedBalanceUpgradeable.sol | 8 +- .../ERC1155PackedBalanceUpgradeable.sol | 53 +- .../ERC1155MetaUpgradeable.sol | 41 +- .../ERC1155MetadataUpgradeable.sol | 35 +- .../ERC1155MintBurnUpgradeable.sol | 8 +- .../ERC1155Upgradeable/ERC1155Upgradeable.sol | 55 +- src/contracts/utils/ContextUpgradeable.sol | 7 - src/contracts/utils/StorageSlot.sol | 13 + tests/ERC1155.spec.ts | 20 +- tests/ERC1155Meta.spec.ts | 3489 ++++++++-------- tests/ERC1155MetaPackedBalance.spec.ts | 3505 +++++++++-------- tests/ERC1155Metadata.spec.ts | 9 +- tests/ERC1155MintBurn.spec.ts | 722 ++-- tests/ERC1155MintBurnPackedBalance.spec.ts | 914 ++--- tests/ERC1155PackedBalance.spec.ts | 1073 ++--- tests/utils/helpers.ts | 10 +- 19 files changed, 5112 insertions(+), 4895 deletions(-) diff --git a/src/contracts/mocks/ERC1155MetadataUpgradeableMock.sol b/src/contracts/mocks/ERC1155MetadataUpgradeableMock.sol index e35c1c3..ef79f61 100644 --- a/src/contracts/mocks/ERC1155MetadataUpgradeableMock.sol +++ b/src/contracts/mocks/ERC1155MetadataUpgradeableMock.sol @@ -72,6 +72,6 @@ contract ERC1155MetadataUpgradeableMockV2 is ERC1155MetadataUpgradeableMock { } function uri(uint256 _id) public override view returns (string memory) { - return string(abi.encodePacked(baseURI, _uint2str(idMapping[_id]))); // Removes .json extension, swaps ids + return string(abi.encodePacked(baseURI(), _uint2str(idMapping[_id]))); // Removes .json extension, swaps ids } } diff --git a/src/contracts/tokens/ERC1155/ERC1155Meta.sol b/src/contracts/tokens/ERC1155/ERC1155Meta.sol index 439fd09..3e86cc5 100644 --- a/src/contracts/tokens/ERC1155/ERC1155Meta.sol +++ b/src/contracts/tokens/ERC1155/ERC1155Meta.sol @@ -294,12 +294,13 @@ contract ERC1155Meta is ERC1155, SignatureValidator { // Complete data to pass to signer verifier bytes memory fullData = abi.encodePacked(_encMembers, nonce, signedData); - //Update signature nonce + // Verify if _from is the signer + require(isValidSignature(_signer, hash, fullData, sig), "ERC1155Meta#_signatureValidation: INVALID_SIGNATURE"); + + // Update signature nonce nonces[_signer] = nonce + 1; emit NonceChange(_signer, nonce + 1); - // Verify if _from is the signer - require(isValidSignature(_signer, hash, fullData, sig), "ERC1155Meta#_signatureValidation: INVALID_SIGNATURE"); return signedData; } diff --git a/src/contracts/tokens/ERC1155PackedBalanceUpgradeable/ERC1155MetaPackedBalanceUpgradeable.sol b/src/contracts/tokens/ERC1155PackedBalanceUpgradeable/ERC1155MetaPackedBalanceUpgradeable.sol index f55e13d..17b40a7 100644 --- a/src/contracts/tokens/ERC1155PackedBalanceUpgradeable/ERC1155MetaPackedBalanceUpgradeable.sol +++ b/src/contracts/tokens/ERC1155PackedBalanceUpgradeable/ERC1155MetaPackedBalanceUpgradeable.sol @@ -6,7 +6,7 @@ import "../../interfaces/IERC20.sol"; import "../../interfaces/IERC1155.sol"; import "../../utils/LibBytes.sol"; import "../../utils/SignatureValidator.sol"; - +import '../../utils/StorageSlot.sol'; /** * @dev ERC-1155 with native metatransaction methods. These additional functions allow users @@ -45,7 +45,7 @@ contract ERC1155MetaPackedBalanceUpgradeable is ERC1155PackedBalanceUpgradeable, } // Signature nonce per address - mapping (address => uint256) internal nonces; + bytes32 constant private _NONCES_SLOT_KEY = keccak256("0xsequence.ERC1155MetaPackedBalanceUpgradeable.nonces"); /***********************************| @@ -227,7 +227,7 @@ contract ERC1155MetaPackedBalanceUpgradeable is ERC1155PackedBalanceUpgradeable, ); // Update operator status - operators[_owner][_operator] = _approved; + _setOperator(_owner, _operator, _approved); // Emit event emit ApprovalForAll(_owner, _operator, _approved); @@ -284,8 +284,8 @@ contract ERC1155MetaPackedBalanceUpgradeable is ERC1155PackedBalanceUpgradeable, (sig, signedData) = abi.decode(_sigData, (bytes, bytes)); // Get current nonce and nonce used for signature - uint256 currentNonce = nonces[_signer]; // Lowest valid nonce for signer - uint256 nonce = uint256(sig.readBytes32(65)); // Nonce passed in the signature object + uint256 currentNonce = _getNonce(_signer); // Lowest valid nonce for signer + uint256 nonce = uint256(sig.readBytes32(65)); // Nonce passed in the signature object // Verify if nonce is valid require( @@ -300,8 +300,9 @@ contract ERC1155MetaPackedBalanceUpgradeable is ERC1155PackedBalanceUpgradeable, bytes memory fullData = abi.encodePacked(_encMembers, nonce, signedData); //Update signature nonce - nonces[_signer] = nonce + 1; - emit NonceChange(_signer, nonce + 1); + nonce++; + _setNonce(_signer, nonce); + emit NonceChange(_signer, nonce); // Verify if _from is the signer require(isValidSignature(_signer, hash, fullData, sig), "ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE"); @@ -315,7 +316,26 @@ contract ERC1155MetaPackedBalanceUpgradeable is ERC1155PackedBalanceUpgradeable, function getNonce(address _signer) public view returns (uint256 nonce) { - return nonces[_signer]; + return _getNonce(_signer); + } + + /** + * @notice Returns the current nonce associated with a given address + * @param _signer Address to query signature nonce for + */ + function _getNonce(address _signer) + internal view returns (uint256 nonce) + { + return StorageSlot.getUint256Slot(keccak256(abi.encodePacked(_NONCES_SLOT_KEY, _signer))).value; + } + + /** + * @notice Sets the nonce associated with a given address + * @param _signer Address to set signature nonce for + * @param _nonce Nonce value to set + */ + function _setNonce(address _signer, uint256 _nonce) internal { + StorageSlot.getUint256Slot(keccak256(abi.encodePacked(_NONCES_SLOT_KEY, _signer))).value = _nonce; } diff --git a/src/contracts/tokens/ERC1155PackedBalanceUpgradeable/ERC1155MintBurnPackedBalanceUpgradeable.sol b/src/contracts/tokens/ERC1155PackedBalanceUpgradeable/ERC1155MintBurnPackedBalanceUpgradeable.sol index de041f4..12769c6 100644 --- a/src/contracts/tokens/ERC1155PackedBalanceUpgradeable/ERC1155MintBurnPackedBalanceUpgradeable.sol +++ b/src/contracts/tokens/ERC1155PackedBalanceUpgradeable/ERC1155MintBurnPackedBalanceUpgradeable.sol @@ -51,7 +51,7 @@ contract ERC1155MintBurnPackedBalanceUpgradeable is ERC1155PackedBalanceUpgradea (uint256 bin, uint256 index) = getIDBinIndex(_ids[0]); // Balance for current bin in memory (initialized with first transfer) - uint256 balTo = _viewUpdateBinValue(balances[_to][bin], index, _amounts[0], Operations.Add); + uint256 balTo = _viewUpdateBinValue(_getBalance(_to, bin), index, _amounts[0], Operations.Add); // Number of transfer to execute uint256 nTransfer = _ids.length; @@ -65,8 +65,8 @@ contract ERC1155MintBurnPackedBalanceUpgradeable is ERC1155PackedBalanceUpgradea // If new bin if (bin != lastBin) { // Update storage balance of previous bin - balances[_to][lastBin] = balTo; - balTo = balances[_to][bin]; + _setBalance(_to, lastBin, balTo); + balTo = _getBalance(_to, bin); // Bin will be the most recent bin lastBin = bin; @@ -77,7 +77,7 @@ contract ERC1155MintBurnPackedBalanceUpgradeable is ERC1155PackedBalanceUpgradea } // Update storage of the last bin visited - balances[_to][bin] = balTo; + _setBalance(_to, bin, balTo); } // //Emit event diff --git a/src/contracts/tokens/ERC1155PackedBalanceUpgradeable/ERC1155PackedBalanceUpgradeable.sol b/src/contracts/tokens/ERC1155PackedBalanceUpgradeable/ERC1155PackedBalanceUpgradeable.sol index 19e83b1..04f78e7 100644 --- a/src/contracts/tokens/ERC1155PackedBalanceUpgradeable/ERC1155PackedBalanceUpgradeable.sol +++ b/src/contracts/tokens/ERC1155PackedBalanceUpgradeable/ERC1155PackedBalanceUpgradeable.sol @@ -6,6 +6,7 @@ import "../../interfaces/IERC1155.sol"; import "../../utils/Address.sol"; import "../../utils/ContextUpgradeable.sol"; import "../../utils/ERC165.sol"; +import '../../utils/StorageSlot.sol'; /** * @dev Implementation of Multi-Token Standard contract. This implementation of the ERC-1155 standard @@ -35,10 +36,10 @@ contract ERC1155PackedBalanceUpgradeable is ContextUpgradeable, IERC1155, ERC165 enum Operations { Add, Sub } // Token IDs balances ; balances[address][id] => balance (using array instead of mapping for efficiency) - mapping (address => mapping(uint256 => uint256)) internal balances; + bytes32 constant private _BALANCES_SLOT_KEY = keccak256("0xsequence.ERC1155PackedBalanceUpgradeable.balances"); // Operators - mapping (address => mapping(address => bool)) internal operators; + bytes32 constant private _OPERATORS_SLOT_KEY = keccak256("0xsequence.ERC1155PackedBalanceUpgradeable.operators"); /***********************************| @@ -140,8 +141,8 @@ contract ERC1155PackedBalanceUpgradeable is ContextUpgradeable, IERC1155, ERC165 (uint256 bin, uint256 index) = getIDBinIndex(_ids[0]); // Balance for current bin in memory (initialized with first transfer) - uint256 balFrom = _viewUpdateBinValue(balances[_from][bin], index, _amounts[0], Operations.Sub); - uint256 balTo = _viewUpdateBinValue(balances[_to][bin], index, _amounts[0], Operations.Add); + uint256 balFrom = _viewUpdateBinValue(_getBalance(_from, bin), index, _amounts[0], Operations.Sub); + uint256 balTo = _viewUpdateBinValue(_getBalance(_to, bin), index, _amounts[0], Operations.Add); // Last bin updated uint256 lastBin = bin; @@ -152,11 +153,11 @@ contract ERC1155PackedBalanceUpgradeable is ContextUpgradeable, IERC1155, ERC165 // If new bin if (bin != lastBin) { // Update storage balance of previous bin - balances[_from][lastBin] = balFrom; - balances[_to][lastBin] = balTo; + _setBalance(_from, lastBin, balFrom); + _setBalance(_to, lastBin, balTo); - balFrom = balances[_from][bin]; - balTo = balances[_to][bin]; + balFrom = _getBalance(_from, bin); + balTo = _getBalance(_to, bin); // Bin will be the most recent bin lastBin = bin; @@ -168,8 +169,8 @@ contract ERC1155PackedBalanceUpgradeable is ContextUpgradeable, IERC1155, ERC165 } // Update storage of the last bin visited - balances[_from][bin] = balFrom; - balances[_to][bin] = balTo; + _setBalance(_from, bin, balFrom); + _setBalance(_to, bin, balTo); // If transfer to self, just make sure all amounts are valid } else { @@ -209,7 +210,7 @@ contract ERC1155PackedBalanceUpgradeable is ContextUpgradeable, IERC1155, ERC165 external override { // Update operator status - operators[_msgSender()][_operator] = _approved; + _setOperator(_msgSender(), _operator, _approved); emit ApprovalForAll(_msgSender(), _operator, _approved); } @@ -222,7 +223,7 @@ contract ERC1155PackedBalanceUpgradeable is ContextUpgradeable, IERC1155, ERC165 function isApprovedForAll(address _owner, address _operator) public override view returns (bool isOperator) { - return operators[_owner][_operator]; + return _getOperator(_owner, _operator); } @@ -244,7 +245,7 @@ contract ERC1155PackedBalanceUpgradeable is ContextUpgradeable, IERC1155, ERC165 //Get bin and index of _id (bin, index) = getIDBinIndex(_id); - return getValueInBin(balances[_owner][bin], index); + return getValueInBin(_getBalance(_owner, bin), index); } /** @@ -261,7 +262,7 @@ contract ERC1155PackedBalanceUpgradeable is ContextUpgradeable, IERC1155, ERC165 // First values (uint256 bin, uint256 index) = getIDBinIndex(_ids[0]); - uint256 balance_bin = balances[_owners[0]][bin]; + uint256 balance_bin = _getBalance(_owners[0], bin); uint256 last_bin = bin; // Initialization @@ -274,7 +275,7 @@ contract ERC1155PackedBalanceUpgradeable is ContextUpgradeable, IERC1155, ERC165 // SLOAD if bin changed for the same owner or if owner changed if (bin != last_bin || _owners[i-1] != _owners[i]) { - balance_bin = balances[_owners[i]][bin]; + balance_bin = _getBalance(_owners[i], bin); last_bin = bin; } @@ -308,7 +309,7 @@ contract ERC1155PackedBalanceUpgradeable is ContextUpgradeable, IERC1155, ERC165 (bin, index) = getIDBinIndex(_id); // Update balance - balances[_address][bin] = _viewUpdateBinValue(balances[_address][bin], index, _amount, _operation); + _setBalance(_address, bin, _viewUpdateBinValue(_getBalance(_address, bin), index, _amount, _operation)); } /** @@ -379,6 +380,26 @@ contract ERC1155PackedBalanceUpgradeable is ContextUpgradeable, IERC1155, ERC165 return (_binValues >> rightShift) & mask; } + /***********************************| + | Storage Functions | + |__________________________________*/ + + function _getBalance(address _owner, uint256 _id) internal view returns (uint256) { + return StorageSlot.getUint256Slot(keccak256(abi.encodePacked(_BALANCES_SLOT_KEY, _owner, _id))).value; + } + + function _setBalance(address _owner, uint256 _id, uint256 _balance) internal { + StorageSlot.getUint256Slot(keccak256(abi.encodePacked(_BALANCES_SLOT_KEY, _owner, _id))).value = _balance; + } + + function _getOperator(address _owner, address _operator) internal view returns (bool) { + return StorageSlot.getBooleanSlot(keccak256(abi.encodePacked(_OPERATORS_SLOT_KEY, _owner, _operator))).value; + } + + function _setOperator(address _owner, address _operator, bool _approved) internal { + StorageSlot.getBooleanSlot(keccak256(abi.encodePacked(_OPERATORS_SLOT_KEY, _owner, _operator))).value = _approved; + } + /***********************************| | ERC165 Functions | diff --git a/src/contracts/tokens/ERC1155Upgradeable/ERC1155MetaUpgradeable.sol b/src/contracts/tokens/ERC1155Upgradeable/ERC1155MetaUpgradeable.sol index b4ff694..8344a63 100644 --- a/src/contracts/tokens/ERC1155Upgradeable/ERC1155MetaUpgradeable.sol +++ b/src/contracts/tokens/ERC1155Upgradeable/ERC1155MetaUpgradeable.sol @@ -6,6 +6,7 @@ import "../../interfaces/IERC20.sol"; import "../../interfaces/IERC1155.sol"; import "../../utils/LibBytes.sol"; import "../../utils/SignatureValidator.sol"; +import '../../utils/StorageSlot.sol'; /** * @dev ERC-1155 with native metatransaction methods. These additional functions allow users @@ -41,8 +42,7 @@ contract ERC1155MetaUpgradeable is ERC1155Upgradeable, SignatureValidator { } // Signature nonce per address - mapping (address => uint256) internal nonces; - + bytes32 constant private _NONCES_SLOT_KEY = keccak256("0xsequence.ERC1155MetaUpgradeable.nonces"); /***********************************| | Events | @@ -223,7 +223,7 @@ contract ERC1155MetaUpgradeable is ERC1155Upgradeable, SignatureValidator { ); // Update operator status - operators[_owner][_operator] = _approved; + _setOperator(_owner, _operator, _approved); // Emit event emit ApprovalForAll(_owner, _operator, _approved); @@ -279,8 +279,8 @@ contract ERC1155MetaUpgradeable is ERC1155Upgradeable, SignatureValidator { (sig, signedData) = abi.decode(_sigData, (bytes, bytes)); // Get current nonce and nonce used for signature - uint256 currentNonce = nonces[_signer]; // Lowest valid nonce for signer - uint256 nonce = uint256(sig.readBytes32(65)); // Nonce passed in the signature object + uint256 currentNonce = _getNonce(_signer); // Lowest valid nonce for signer + uint256 nonce = uint256(sig.readBytes32(65)); // Nonce passed in the signature object // Verify if nonce is valid require( @@ -294,12 +294,14 @@ contract ERC1155MetaUpgradeable is ERC1155Upgradeable, SignatureValidator { // Complete data to pass to signer verifier bytes memory fullData = abi.encodePacked(_encMembers, nonce, signedData); - //Update signature nonce - nonces[_signer] = nonce + 1; - emit NonceChange(_signer, nonce + 1); - // Verify if _from is the signer require(isValidSignature(_signer, hash, fullData, sig), "ERC1155Meta#_signatureValidation: INVALID_SIGNATURE"); + + // Update signature nonce + nonce++; + _setNonce(_signer, nonce); + emit NonceChange(_signer, nonce); + return signedData; } @@ -310,7 +312,26 @@ contract ERC1155MetaUpgradeable is ERC1155Upgradeable, SignatureValidator { function getNonce(address _signer) public view returns (uint256 nonce) { - return nonces[_signer]; + return _getNonce(_signer); + } + + /** + * @notice Returns the current nonce associated with a given address + * @param _signer Address to query signature nonce for + */ + function _getNonce(address _signer) + internal view returns (uint256 nonce) + { + return StorageSlot.getUint256Slot(keccak256(abi.encodePacked(_NONCES_SLOT_KEY, _signer))).value; + } + + /** + * @notice Sets the nonce associated with a given address + * @param _signer Address to set signature nonce for + * @param _nonce Nonce value to set + */ + function _setNonce(address _signer, uint256 _nonce) internal { + StorageSlot.getUint256Slot(keccak256(abi.encodePacked(_NONCES_SLOT_KEY, _signer))).value = _nonce; } diff --git a/src/contracts/tokens/ERC1155Upgradeable/ERC1155MetadataUpgradeable.sol b/src/contracts/tokens/ERC1155Upgradeable/ERC1155MetadataUpgradeable.sol index 6db96ce..6b7579d 100644 --- a/src/contracts/tokens/ERC1155Upgradeable/ERC1155MetadataUpgradeable.sol +++ b/src/contracts/tokens/ERC1155Upgradeable/ERC1155MetadataUpgradeable.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import '../../interfaces/IERC1155Metadata.sol'; import '../../utils/ERC165.sol'; import '../../utils/Initializable.sol'; +import '../../utils/StorageSlot.sol'; /** * @notice Contract that handles metadata related methods. @@ -12,8 +13,8 @@ import '../../utils/Initializable.sol'; */ contract ERC1155MetadataUpgradeable is Initializable, IERC1155Metadata, ERC165 { // URI's default URI prefix - string public baseURI; - string public name; + bytes32 private constant _BASEURI_SLOT = keccak256("0xsequence.ERC1155MetadataUpgradeable.baseURI"); + bytes32 private constant _NAME_SLOT = keccak256("0xsequence.ERC1155MetadataUpgradeable.name"); constructor() initializer {} @@ -22,14 +23,22 @@ contract ERC1155MetadataUpgradeable is Initializable, IERC1155Metadata, ERC165 { * @dev This function should be called once immediately after deployment. */ function initialize(string memory _name, string memory _baseURI) public virtual initializer { - name = _name; - baseURI = _baseURI; + _setContractName(_name); + _setBaseMetadataURI(_baseURI); } /***********************************| - | Metadata Public Functions | + | Public Functions | |__________________________________*/ + function name() public view virtual returns (string memory) { + return StorageSlot.getStringSlot(_NAME_SLOT).value; + } + + function baseURI() public view virtual returns (string memory) { + return StorageSlot.getStringSlot(_BASEURI_SLOT).value; + } + /** * @notice A distinct Uniform Resource Identifier (URI) for a given token. * @dev URIs are defined in RFC 3986. @@ -37,7 +46,7 @@ contract ERC1155MetadataUpgradeable is Initializable, IERC1155Metadata, ERC165 { * @return URI string */ function uri(uint256 _id) public virtual override view returns (string memory) { - return string(abi.encodePacked(baseURI, _uint2str(_id), ".json")); + return string(abi.encodePacked(baseURI(), _uint2str(_id), ".json")); } @@ -50,7 +59,7 @@ contract ERC1155MetadataUpgradeable is Initializable, IERC1155Metadata, ERC165 { * @param _tokenIDs Array of IDs of tokens to log default URI */ function _logURIs(uint256[] memory _tokenIDs) internal { - string memory baseURL = baseURI; + string memory baseURL = baseURI(); string memory tokenURI; for (uint256 i = 0; i < _tokenIDs.length; i++) { @@ -61,18 +70,18 @@ contract ERC1155MetadataUpgradeable is Initializable, IERC1155Metadata, ERC165 { /** * @notice Will update the base URL of token's URI - * @param _newBaseMetadataURI New base URL of token's URI + * @param _baseURI New base URL of token's URI */ - function _setBaseMetadataURI(string memory _newBaseMetadataURI) internal { - baseURI = _newBaseMetadataURI; + function _setBaseMetadataURI(string memory _baseURI) internal { + StorageSlot.getStringSlot(_BASEURI_SLOT).value = _baseURI; } /** * @notice Will update the name of the contract - * @param _newName New contract name + * @param _name New contract name */ - function _setContractName(string memory _newName) internal { - name = _newName; + function _setContractName(string memory _name) internal { + StorageSlot.getStringSlot(_NAME_SLOT).value = _name; } /** diff --git a/src/contracts/tokens/ERC1155Upgradeable/ERC1155MintBurnUpgradeable.sol b/src/contracts/tokens/ERC1155Upgradeable/ERC1155MintBurnUpgradeable.sol index 33d8352..8d146b3 100644 --- a/src/contracts/tokens/ERC1155Upgradeable/ERC1155MintBurnUpgradeable.sol +++ b/src/contracts/tokens/ERC1155Upgradeable/ERC1155MintBurnUpgradeable.sol @@ -25,7 +25,7 @@ contract ERC1155MintBurnUpgradeable is ERC1155Upgradeable { internal { // Add _amount - balances[_to][_id] += _amount; + _updateBalance(_to, _id, _amount, Operations.Add); // Emit event emit TransferSingle(_msgSender(), address(0x0), _to, _id, _amount); @@ -52,7 +52,7 @@ contract ERC1155MintBurnUpgradeable is ERC1155Upgradeable { // Executing all minting for (uint256 i = 0; i < nMint; i++) { // Update storage balance - balances[_to][_ids[i]] += _amounts[i]; + _updateBalance(_to, _ids[i], _amounts[i], Operations.Add); } // Emit batch mint event @@ -77,7 +77,7 @@ contract ERC1155MintBurnUpgradeable is ERC1155Upgradeable { internal { //Substract _amount - balances[_from][_id] -= _amount; + _updateBalance(_from, _id, _amount, Operations.Sub); // Emit event emit TransferSingle(_msgSender(), _from, address(0x0), _id, _amount); @@ -99,7 +99,7 @@ contract ERC1155MintBurnUpgradeable is ERC1155Upgradeable { // Executing all minting for (uint256 i = 0; i < nBurn; i++) { // Update storage balance - balances[_from][_ids[i]] -= _amounts[i]; + _updateBalance(_from, _ids[i], _amounts[i], Operations.Sub); } // Emit batch mint event diff --git a/src/contracts/tokens/ERC1155Upgradeable/ERC1155Upgradeable.sol b/src/contracts/tokens/ERC1155Upgradeable/ERC1155Upgradeable.sol index d720299..799b4d1 100644 --- a/src/contracts/tokens/ERC1155Upgradeable/ERC1155Upgradeable.sol +++ b/src/contracts/tokens/ERC1155Upgradeable/ERC1155Upgradeable.sol @@ -6,6 +6,7 @@ import "../../interfaces/IERC1155.sol"; import "../../utils/Address.sol"; import "../../utils/ContextUpgradeable.sol"; import "../../utils/ERC165.sol"; +import '../../utils/StorageSlot.sol'; /** * @dev Implementation of Multi-Token Standard contract. @@ -22,11 +23,14 @@ contract ERC1155Upgradeable is ContextUpgradeable, IERC1155, ERC165 { bytes4 constant internal ERC1155_RECEIVED_VALUE = 0xf23a6e61; bytes4 constant internal ERC1155_BATCH_RECEIVED_VALUE = 0xbc197c81; + // Math operations + enum Operations { Add, Sub } + // Objects balances - mapping (address => mapping(uint256 => uint256)) internal balances; + bytes32 constant private _BALANCES_SLOT_KEY = keccak256("0xsequence.ERC1155Upgradeable.balances"); // Operator Functions - mapping (address => mapping(address => bool)) internal operators; + bytes32 constant private _OPERATORS_SLOT_KEY = keccak256("0xsequence.ERC1155Upgradeable.operators"); /***********************************| @@ -86,8 +90,8 @@ contract ERC1155Upgradeable is ContextUpgradeable, IERC1155, ERC165 { internal { // Update balances - balances[_from][_id] -= _amount; - balances[_to][_id] += _amount; + _updateBalance(_from, _id, _amount, Operations.Sub); + _updateBalance(_to, _id, _amount, Operations.Add); // Emit event emit TransferSingle(_msgSender(), _from, _to, _id, _amount); @@ -124,8 +128,8 @@ contract ERC1155Upgradeable is ContextUpgradeable, IERC1155, ERC165 { // Executing all transfers for (uint256 i = 0; i < nTransfer; i++) { // Update storage balance of previous bin - balances[_from][_ids[i]] -= _amounts[i]; - balances[_to][_ids[i]] += _amounts[i]; + _updateBalance(_from, _ids[i], _amounts[i], Operations.Sub); + _updateBalance(_to, _ids[i], _amounts[i], Operations.Add); } // Emit event @@ -159,7 +163,7 @@ contract ERC1155Upgradeable is ContextUpgradeable, IERC1155, ERC165 { external override { // Update operator status - operators[_msgSender()][_operator] = _approved; + _setOperator(_msgSender(), _operator, _approved); emit ApprovalForAll(_msgSender(), _operator, _approved); } @@ -172,7 +176,7 @@ contract ERC1155Upgradeable is ContextUpgradeable, IERC1155, ERC165 { function isApprovedForAll(address _owner, address _operator) public override view returns (bool isOperator) { - return operators[_owner][_operator]; + return _getOperator(_owner, _operator); } @@ -189,7 +193,7 @@ contract ERC1155Upgradeable is ContextUpgradeable, IERC1155, ERC165 { function balanceOf(address _owner, uint256 _id) public override view returns (uint256) { - return balances[_owner][_id]; + return _getBalance(_owner, _id); } /** @@ -208,12 +212,43 @@ contract ERC1155Upgradeable is ContextUpgradeable, IERC1155, ERC165 { // Iterate over each owner and token ID for (uint256 i = 0; i < _owners.length; i++) { - batchBalances[i] = balances[_owners[i]][_ids[i]]; + batchBalances[i] = _getBalance(_owners[i], _ids[i]); } return batchBalances; } + /***********************************| + | Storage Functions | + |__________________________________*/ + + function _getBalance(address _owner, uint256 _id) internal view returns (uint256) { + return StorageSlot.getUint256Slot(keccak256(abi.encodePacked(_BALANCES_SLOT_KEY, _owner, _id))).value; + } + + function _setBalance(address _owner, uint256 _id, uint256 _balance) internal { + StorageSlot.getUint256Slot(keccak256(abi.encodePacked(_BALANCES_SLOT_KEY, _owner, _id))).value = _balance; + } + + function _updateBalance(address _owner, uint256 _id, uint256 _diff, Operations _operation) internal { + StorageSlot.Uint256Slot storage slot = StorageSlot.getUint256Slot(keccak256(abi.encodePacked(_BALANCES_SLOT_KEY, _owner, _id))); + if (_operation == Operations.Add) { + slot.value += _diff; + } else if (_operation == Operations.Sub) { + slot.value -= _diff; + } else { + revert("ERC1155Upgradeable#_updateBalance: INVALID_OPERATION"); + } + } + + function _getOperator(address _owner, address _operator) internal view returns (bool) { + return StorageSlot.getBooleanSlot(keccak256(abi.encodePacked(_OPERATORS_SLOT_KEY, _owner, _operator))).value; + } + + function _setOperator(address _owner, address _operator, bool _approved) internal { + StorageSlot.getBooleanSlot(keccak256(abi.encodePacked(_OPERATORS_SLOT_KEY, _owner, _operator))).value = _approved; + } + /***********************************| | ERC165 Functions | diff --git a/src/contracts/utils/ContextUpgradeable.sol b/src/contracts/utils/ContextUpgradeable.sol index ac21633..1afc9d8 100644 --- a/src/contracts/utils/ContextUpgradeable.sol +++ b/src/contracts/utils/ContextUpgradeable.sol @@ -27,11 +27,4 @@ abstract contract ContextUpgradeable is Initializable { function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } - - /** - * @dev This empty reserved space is put in place to allow future versions to add new - * variables without shifting down storage in the inheritance chain. - * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps - */ - uint256[50] private __gap; } diff --git a/src/contracts/utils/StorageSlot.sol b/src/contracts/utils/StorageSlot.sol index 9fe9f9b..11a740f 100644 --- a/src/contracts/utils/StorageSlot.sol +++ b/src/contracts/utils/StorageSlot.sol @@ -22,6 +22,10 @@ library StorageSlot { bytes32 value; } + struct StringSlot { + string value; + } + struct Uint256Slot { uint256 value; } @@ -53,6 +57,15 @@ library StorageSlot { } } + /** + * @dev Returns an `StringSlot` with member `value` located at `slot`. + */ + function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) { + assembly { + r.slot := slot + } + } + /** * @dev Returns an `Uint256Slot` with member `value` located at `slot`. */ diff --git a/tests/ERC1155.spec.ts b/tests/ERC1155.spec.ts index 32ff784..88ed8e7 100644 --- a/tests/ERC1155.spec.ts +++ b/tests/ERC1155.spec.ts @@ -10,13 +10,13 @@ import { HIGH_GAS_LIMIT } from './utils' -import { ERC1155MetaMintBurnMock, ERC1155ReceiverMock, ERC1155OperatorMock } from 'src' +import { ERC1155MetaMintBurnMock, ERC1155ReceiverMock, ERC1155OperatorMock, ProxyUpgradeableDeployerMock } from 'src' // init test wallets from package.json mnemonic import { web3 } from 'hardhat' -const { wallet: ownerWallet, provider: ownerProvider, signer: ownerSigner } = createTestWallet(web3, 0) -const { wallet: receiverWallet, provider: receiverProvider, signer: receiverSigner } = createTestWallet(web3, 2) +const { wallet: ownerWallet, provider: ownerProvider } = createTestWallet(web3, 0) +const { wallet: receiverWallet } = createTestWallet(web3, 2) const { wallet: operatorWallet, provider: operatorProvider, signer: operatorSigner } = createTestWallet(web3, 4) const usingUpgradeable = [false, true] @@ -39,6 +39,8 @@ usingUpgradeable.forEach(upgradeable => { let erc1155Contract: ERC1155MetaMintBurnMock let operatorERC1155Contract: ERC1155MetaMintBurnMock + let factoryContract: ProxyUpgradeableDeployerMock + // load contract abi and deploy to test server before(async () => { ownerAddress = await ownerWallet.getAddress() @@ -47,6 +49,9 @@ usingUpgradeable.forEach(upgradeable => { if (upgradeable) { erc1155Abstract = await AbstractContract.fromArtifactName('ERC1155MetaMintBurnUpgradeableMock') + // Create factory + const factoryAbstract = await AbstractContract.fromArtifactName('ProxyUpgradeableDeployerMock') + factoryContract = (await factoryAbstract.deploy(ownerWallet, [])) as ProxyUpgradeableDeployerMock } else { erc1155Abstract = await AbstractContract.fromArtifactName('ERC1155MetaMintBurnMock') } @@ -57,7 +62,14 @@ usingUpgradeable.forEach(upgradeable => { beforeEach(async () => { if (upgradeable) { erc1155Contract = (await erc1155Abstract.deploy(ownerWallet, [])) as ERC1155MetaMintBurnMock - erc1155Contract.initialize(NAME, METADATA_URI) + + // Create proxy + let tx = factoryContract.createProxy(erc1155Contract.address, ethers.constants.HashZero, ownerWallet.address); + await expect(tx).to.be.fulfilled + const proxyAddr = await factoryContract.predictProxyAddress(erc1155Contract.address, ethers.constants.HashZero, ownerWallet.address); + erc1155Contract = (await erc1155Abstract.connect(ownerWallet, proxyAddr)) as ERC1155MetaMintBurnMock; + tx = erc1155Contract.initialize(NAME, METADATA_URI) + await expect(tx).to.be.fulfilled } else { erc1155Contract = (await erc1155Abstract.deploy(ownerWallet, [NAME, METADATA_URI])) as ERC1155MetaMintBurnMock } diff --git a/tests/ERC1155Meta.spec.ts b/tests/ERC1155Meta.spec.ts index 8be759a..52266b7 100644 --- a/tests/ERC1155Meta.spec.ts +++ b/tests/ERC1155Meta.spec.ts @@ -13,19 +13,15 @@ import { HIGH_GAS_LIMIT } from './utils' -import { BigNumber, utils, ContractTransaction, EventFilter } from 'ethers' - -import { ERC1155MetaMintBurnMock, ERC1271WalletValidationMock, ERC1155ReceiverMock, ERC1155OperatorMock, ERC20Mock } from 'src' - +import { BigNumber, utils, ContractTransaction, EventFilter, constants } from 'ethers' +import { ERC1155MetaMintBurnMock, ERC1271WalletValidationMock, ERC1155ReceiverMock, ERC1155OperatorMock, ERC20Mock, ProxyUpgradeableDeployerMock } from 'src' import { GasReceipt, TransferSignature, ApprovalSignature, BatchTransferSignature } from 'src/typings/tx-types' // init test wallets from package.json mnemonic import { web3 } from 'hardhat' -const { wallet: ownerWallet, provider: ownerProvider, signer: ownerSigner } = createTestWallet(web3, 1) - +const { wallet: ownerWallet, provider: ownerProvider } = createTestWallet(web3, 1) const { wallet: receiverWallet, provider: receiverProvider, signer: receiverSigner } = createTestWallet(web3, 2) - const { wallet: operatorWallet, provider: operatorProvider, signer: operatorSigner } = createTestWallet(web3, 4) // Lower polling interval for faster tx send @@ -33,651 +29,439 @@ ownerProvider.pollingInterval = 1000 operatorProvider.pollingInterval = 1000 receiverProvider.pollingInterval = 1000 -describe('ERC1155Meta', () => { - const MAXVAL = BigNumber.from(2) - .pow(256) - .sub(1) // 2**256 - 1 - const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' - const DOMAIN_SEPARATOR_TYPEHASH = '0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749' - - const NAME = 'MyERC1155' - const METADATA_URI = 'https://example.com/' - - // Pass gas since ganache can't figure it out - const TX_PARAM = { gasLimit: 2000000 } - - let ownerAddress: string - let receiverAddress: string - let operatorAddress: string - let erc1155Abstract: AbstractContract - let operatorAbstract: AbstractContract - - let erc1155Contract: ERC1155MetaMintBurnMock - let operatorERC1155Contract: ERC1155MetaMintBurnMock - let receiverERC1155Contract: ERC1155MetaMintBurnMock - - // load contract abi and deploy to test server - before(async () => { - ownerAddress = await ownerWallet.getAddress() - receiverAddress = await receiverWallet.getAddress() - operatorAddress = await operatorWallet.getAddress() - - erc1155Abstract = await AbstractContract.fromArtifactName('ERC1155MetaMintBurnMock') - operatorAbstract = await AbstractContract.fromArtifactName('ERC1155OperatorMock') - }) - - // deploy before each test, to reset state of contract - beforeEach(async () => { - erc1155Contract = (await erc1155Abstract.deploy(ownerWallet, [NAME, METADATA_URI])) as ERC1155MetaMintBurnMock - operatorERC1155Contract = (await erc1155Contract.connect(operatorSigner)) as ERC1155MetaMintBurnMock - receiverERC1155Contract = (await erc1155Contract.connect(receiverSigner)) as ERC1155MetaMintBurnMock - }) - - describe('metaSafeTransferFrom() (Meta) Function', () => { - let receiverContract: ERC1155ReceiverMock - let operatorContract: ERC1155OperatorMock - - let transferData: string | null = 'Hello from the other side' - const initBalance = 100 - const amount = 10 - const nonce = BigNumber.from(0) - const id = 66 - - const feeTokenID = 666 - let isGasReceipt: boolean = true - const feeTokenInitBalance = BigNumber.from(100000000) - - const feeType = 0 // ERC-1155 - let feeTokenAddress: string - let feeTokenDataERC1155: string | Uint8Array - - let transferObj: TransferSignature - let domainHash: string - let gasReceipt: GasReceipt | null - let data: string - - const conditions = [ - [transferData, true, 'Gas receipt & transfer data'], - [null, true, 'Gas receipt w/o transfer data'], - [transferData, false, 'Transfer data w/o gas receipt '], - [null, false, 'No Gas receipt & No transfer data'] - ] - - conditions.forEach(function(condition) { - context(condition[2] as string, () => { - beforeEach(async () => { - // Get conditions - transferData = condition[0] as string | null - isGasReceipt = condition[1] as boolean - - // Deploy contracts - const abstract = await AbstractContract.fromArtifactName('ERC1155ReceiverMock') - receiverContract = (await abstract.deploy(ownerWallet)) as ERC1155ReceiverMock - operatorContract = (await operatorAbstract.deploy(operatorWallet)) as ERC1155OperatorMock - - feeTokenAddress = erc1155Contract.address - - feeTokenDataERC1155 = utils.defaultAbiCoder.encode( - ['address', 'uint256', 'uint8'], - [feeTokenAddress, feeTokenID, feeType] - ) - - // Gas Receipt - gasReceipt = { - gasLimitCallback: 130000, - gasFee: 30000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC1155 - } - - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null - - // Transfer Signature Object - transferObj = { - contractAddress: erc1155Contract.address, - signerWallet: ownerWallet, - receiver: receiverAddress, - id: id, - amount: amount, - isGasFee: isGasReceipt, - transferData: transferData === null ? null : utils.toUtf8Bytes(transferData), - nonce: nonce - } - - // Mint tokens - await erc1155Contract.mintMock(ownerAddress, id, initBalance, []) - - // Mint tokens used to pay for gas - await erc1155Contract.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) - - // Domain hash - domainHash = utils.keccak256( - utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, erc1155Contract.address]) - ) - - // Data to pass in transfer method - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - }) - - it('should REVERT if data is random', async () => { - const dataUint8 = utils.toUtf8Bytes('Breakthroughs! over the river! flips and crucifixions! gone down the flood!') - const data = BigNumber.from(dataUint8).toHexString() - - // Check if data length is more than 69 - expect(utils.arrayify(data).length).to.be.at.least(70) - - const tx = erc1155Contract.metaSafeTransferFrom( - ownerAddress, - receiverContract.address, - id, - amount, - isGasReceipt, - data, - { gasLimit: 100_000 } - ) - await expect(tx).to.be.rejectedWith(RevertError()) - }) - - it('should REVERT if contract address is incorrect', async () => { - // Domain hash - domainHash = utils.keccak256( - utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, receiverContract.address]) - ) - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) - - it('should REVERT if signer address is incorrect', async () => { - transferObj.signerWallet = operatorWallet - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) - - it('should REVERT if receiver address is incorrect', async () => { - transferObj.receiver = ownerAddress - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) - - it('should REVERT if token id is incorrect', async () => { - transferObj.id = id + 1 - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) - - it('should REVERT if token amount is incorrect', async () => { - transferObj.amount = amount + 1 - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) - - it('should REVERT if transfer data is incorrect', async () => { - const sigArgTypes = ['address', 'address', 'address', 'uint256', 'uint256', 'uint256'] - const txDataTypes = ['bytes', 'bytes'] - - const signer = await transferObj.signerWallet.getAddress() - - // Packed encoding of transfer signature message - let sigData = utils.solidityPack(sigArgTypes, [ - transferObj.contractAddress, - signer, - transferObj.receiver, - transferObj.id, - transferObj.amount, - transferObj.nonce - ]) - - const transferData = transferObj.transferData == null ? utils.toUtf8Bytes('') : transferObj.transferData - let goodGasAndTransferData - let badGasAndTransferData - - // Correct and incorrect transferData - if (isGasReceipt) { - goodGasAndTransferData = utils.defaultAbiCoder.encode([GasReceiptType, 'bytes'], [gasReceipt, transferData]) - badGasAndTransferData = utils.defaultAbiCoder.encode( - [GasReceiptType, 'bytes'], - [gasReceipt, utils.toUtf8Bytes('Goodbyebyebye')] - ) - } else { - goodGasAndTransferData = utils.defaultAbiCoder.encode(['bytes'], [transferData]) - badGasAndTransferData = utils.defaultAbiCoder.encode(['bytes'], [utils.toUtf8Bytes('Goodbyebyebye')]) - } - - // Encode normally the whole thing - sigData = utils.solidityPack(['bytes', 'bytes'], [sigData, goodGasAndTransferData]) - - // Get signature - const sig = (await ethSign(transferObj.signerWallet, sigData)).slice(0, -2) - const paddedNonce = utils.solidityPack(['uint256'], [transferObj.nonce]) - const ethsig_nonce = sig + paddedNonce.slice(2) + '02' // encode packed the nonce - - // PASS BAD DATA - data = utils.defaultAbiCoder.encode(txDataTypes, [ethsig_nonce, badGasAndTransferData]) - - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) - - it('should REVERT if nonce is incorrect', async () => { - transferObj.nonce = nonce.add(101) - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - - // Nonce higher - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_NONCE')) - - // Correct nonce - transferObj.nonce = nonce - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - await operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - - // Nonce lower - const tx2 = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - await expect(tx2).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_NONCE')) - }) - - it('should PASS if signature is valid', async () => { - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - await expect(tx).to.be.fulfilled - }) - - describe('ERC-1271 Receiver', () => { - let erc1271WalletValidationMockContract: ERC1271WalletValidationMock - let ERC1271WalletValidationMockAbstract: AbstractContract +const usingUpgradeable = [false, true] + +usingUpgradeable.forEach(upgradeable => { + describe('ERC1155Meta' + (upgradeable ? 'Upgradeable': ''), () => { + const MAXVAL = BigNumber.from(2) + .pow(256) + .sub(1) // 2**256 - 1 + const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' + const DOMAIN_SEPARATOR_TYPEHASH = '0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749' + + const NAME = 'MyERC1155' + const METADATA_URI = 'https://example.com/' + + let ownerAddress: string + let receiverAddress: string + let operatorAddress: string + let erc1155Abstract: AbstractContract + let operatorAbstract: AbstractContract + + let erc1155Contract: ERC1155MetaMintBurnMock + let operatorERC1155Contract: ERC1155MetaMintBurnMock + let receiverERC1155Contract: ERC1155MetaMintBurnMock + let factoryContract: ProxyUpgradeableDeployerMock + + // load contract abi and deploy to test server + before(async () => { + ownerAddress = await ownerWallet.getAddress() + receiverAddress = await receiverWallet.getAddress() + operatorAddress = await operatorWallet.getAddress() + + if (upgradeable) { + erc1155Abstract = await AbstractContract.fromArtifactName('ERC1155MetaMintBurnUpgradeableMock') + // Create factory + const factoryAbstract = await AbstractContract.fromArtifactName('ProxyUpgradeableDeployerMock') + factoryContract = (await factoryAbstract.deploy(ownerWallet, [])) as ProxyUpgradeableDeployerMock + } else { + erc1155Abstract = await AbstractContract.fromArtifactName('ERC1155MetaMintBurnMock') + } + operatorAbstract = await AbstractContract.fromArtifactName('ERC1155OperatorMock') + }) - let erc1271WalletAddress + const deployERC1155Contract = async () => { + let contract: ERC1155MetaMintBurnMock + if (upgradeable) { + contract = (await erc1155Abstract.deploy(ownerWallet, [])) as ERC1155MetaMintBurnMock + + // Create proxy + let tx = factoryContract.createProxy(contract.address, constants.HashZero, ownerWallet.address); + await expect(tx).to.be.fulfilled + const proxyAddr = await factoryContract.predictProxyAddress(contract.address, constants.HashZero, ownerWallet.address); + contract = (await erc1155Abstract.connect(ownerWallet, proxyAddr)) as ERC1155MetaMintBurnMock; + tx = contract.initialize(NAME, METADATA_URI) + await expect(tx).to.be.fulfilled + } else { + contract = (await erc1155Abstract.deploy(ownerWallet, [NAME, METADATA_URI])) as ERC1155MetaMintBurnMock + } + return contract + } + + // deploy before each test, to reset state of contract + beforeEach(async () => { + erc1155Contract = await deployERC1155Contract() + operatorERC1155Contract = (await erc1155Contract.connect(operatorSigner)) as ERC1155MetaMintBurnMock + receiverERC1155Contract = (await erc1155Contract.connect(receiverSigner)) as ERC1155MetaMintBurnMock + }) + describe('metaSafeTransferFrom() (Meta) Function', () => { + let receiverContract: ERC1155ReceiverMock + let operatorContract: ERC1155OperatorMock + + let transferData: string | null = 'Hello from the other side' + const initBalance = 100 + const amount = 10 + const nonce = BigNumber.from(0) + const id = 66 + + const feeTokenID = 666 + let isGasReceipt: boolean = true + const feeTokenInitBalance = BigNumber.from(100000000) + + const feeType = 0 // ERC-1155 + let feeTokenAddress: string + let feeTokenDataERC1155: string | Uint8Array + + let transferObj: TransferSignature + let domainHash: string + let gasReceipt: GasReceipt | null + let data: string + + const conditions = [ + [transferData, true, 'Gas receipt & transfer data'], + [null, true, 'Gas receipt w/o transfer data'], + [transferData, false, 'Transfer data w/o gas receipt '], + [null, false, 'No Gas receipt & No transfer data'] + ] + + conditions.forEach(function(condition) { + context(condition[2] as string, () => { beforeEach(async () => { - ERC1271WalletValidationMockAbstract = await AbstractContract.fromArtifactName('ERC1271WalletValidationMock') - erc1271WalletValidationMockContract = (await ERC1271WalletValidationMockAbstract.deploy(ownerWallet, [ - domainHash - ])) as ERC1271WalletValidationMock - erc1271WalletAddress = erc1271WalletValidationMockContract.address - - await erc1155Contract.mintMock(erc1271WalletAddress, id, initBalance, []) - await erc1155Contract.mintMock(erc1271WalletAddress, feeTokenID, feeTokenInitBalance, []) - }) + // Get conditions + transferData = condition[0] as string | null + isGasReceipt = condition[1] as boolean - describe(`EIP-1271 (bytes) signatures (03)`, () => { - it('should return REVERT if signature is invalid', async () => { - transferObj.from = erc1271WalletAddress - transferObj.signerWallet = receiverWallet + // Deploy contracts + const abstract = await AbstractContract.fromArtifactName('ERC1155ReceiverMock') + receiverContract = (await abstract.deploy(ownerWallet)) as ERC1155ReceiverMock + operatorContract = (await operatorAbstract.deploy(operatorWallet)) as ERC1155OperatorMock - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '03') - const tx = operatorERC1155Contract.metaSafeTransferFrom( - erc1271WalletAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data, - { gasLimit: 2000000 } - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) + feeTokenAddress = erc1155Contract.address - it('should REVERT if token ID is not 66', async () => { - const badID = 77 - await erc1155Contract.mintMock(erc1271WalletAddress, badID, initBalance, []) - transferObj.from = erc1271WalletAddress - transferObj.id = badID - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '03') - const tx = operatorERC1155Contract.metaSafeTransferFrom( - erc1271WalletAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data, - { gasLimit: 2000000 } - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) + feeTokenDataERC1155 = utils.defaultAbiCoder.encode( + ['address', 'uint256', 'uint8'], + [feeTokenAddress, feeTokenID, feeType] + ) - it('should REVERT if amount is more than 100', async () => { - await erc1155Contract.mintMock(erc1271WalletAddress, id, 101, []) - transferObj.from = erc1271WalletAddress - transferObj.amount = 101 - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '03') - const tx = operatorERC1155Contract.metaSafeTransferFrom( - erc1271WalletAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data, - { gasLimit: 2000000 } - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) + // Gas Receipt + gasReceipt = { + gasLimitCallback: 130000, + gasFee: 30000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC1155 + } - it('should PASS if signature is valid', async () => { - transferObj.from = erc1271WalletAddress - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '03') - const tx = operatorERC1155Contract.metaSafeTransferFrom( - erc1271WalletAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data, - { gasLimit: 2000000 } - ) - await expect(tx).to.be.fulfilled - }) - }) + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null + + // Transfer Signature Object + transferObj = { + contractAddress: erc1155Contract.address, + signerWallet: ownerWallet, + receiver: receiverAddress, + id: id, + amount: amount, + isGasFee: isGasReceipt, + transferData: transferData === null ? null : utils.toUtf8Bytes(transferData), + nonce: nonce + } - describe(`EIP-1271 (bytes32) signatures (04)`, () => { - it('should return REVERT if signature is invalid', async () => { - transferObj.from = erc1271WalletAddress - transferObj.signerWallet = receiverWallet + // Mint tokens + await erc1155Contract.mintMock(ownerAddress, id, initBalance, []) - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '04') - const tx = operatorERC1155Contract.metaSafeTransferFrom( - erc1271WalletAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data, - { gasLimit: 2000000 } - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) + // Mint tokens used to pay for gas + await erc1155Contract.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) - it('should PASS if signature is valid', async () => { - transferObj.from = erc1271WalletAddress - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '04') - const tx = operatorERC1155Contract.metaSafeTransferFrom( - erc1271WalletAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data, - { gasLimit: 2000000 } - ) - await expect(tx).to.be.fulfilled - }) - }) - }) + // Domain hash + domainHash = utils.keccak256( + utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, erc1155Contract.address]) + ) - describe('When signature is valid', () => { - it('should REVERT if insufficient balance', async () => { - transferObj.amount = initBalance + 1 + // Data to pass in transfer method data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeTransferFrom( - ownerAddress, - receiverAddress, - id, - initBalance + 1, - isGasReceipt, - data, - HIGH_GAS_LIMIT - ) - await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) }) - it('should REVERT if sending to 0x0', async () => { - transferObj.receiver = ZERO_ADDRESS - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + it('should REVERT if data is random', async () => { + const dataUint8 = utils.toUtf8Bytes('Breakthroughs! over the river! flips and crucifixions! gone down the flood!') + const data = BigNumber.from(dataUint8).toHexString() - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, ZERO_ADDRESS, id, amount, isGasReceipt, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#metaSafeTransferFrom: INVALID_RECIPIENT')) - }) + // Check if data length is more than 69 + expect(utils.arrayify(data).length).to.be.at.least(70) - it('should REVERT if transfer leads to overflow', async () => { - await erc1155Contract.mintMock(receiverAddress, id, MAXVAL, []) - const tx = operatorERC1155Contract.metaSafeTransferFrom( + const tx = erc1155Contract.metaSafeTransferFrom( ownerAddress, - receiverAddress, + receiverContract.address, id, amount, isGasReceipt, data, - HIGH_GAS_LIMIT + { gasLimit: 100_000 } ) - await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) + await expect(tx).to.be.rejectedWith(RevertError()) }) - it('should REVERT when sending to non-receiver contract', async () => { - transferObj.receiver = erc1155Contract.address + it('should REVERT if contract address is incorrect', async () => { + // Domain hash + domainHash = utils.keccak256( + utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, receiverContract.address]) + ) data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSafeTransferFrom( - ownerAddress, - erc1155Contract.address, - id, - amount, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnMock: INVALID_METHOD')) + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) }) - it('should REVERT if invalid response from receiver contract', async () => { - transferObj.receiver = receiverContract.address + it('should REVERT if signer address is incorrect', async () => { + transferObj.signerWallet = operatorWallet data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - // Force invalid response - await receiverContract.setShouldReject(true) - - const tx = operatorERC1155Contract.metaSafeTransferFrom( - ownerAddress, - receiverContract.address, - id, - amount, - isGasReceipt, - data, - { gasLimit: 100_000 } - ) - await expect(tx).to.be.rejectedWith(/ERC1155#_callonERC1155Received: INVALID_ON_RECEIVE_MESSAGE|out of gas/) + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) }) - it('should PASS if valid response from receiver contract', async () => { - transferObj.receiver = receiverContract.address - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '02') - - const tx = operatorERC1155Contract.metaSafeTransferFrom( - ownerAddress, - receiverContract.address, - id, - amount, - isGasReceipt, - data - ) + it('should REVERT if receiver address is incorrect', async () => { + transferObj.receiver = ownerAddress + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - //await expect(tx).to.be.fulfilled - await expect(tx).to.be.fulfilled + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) }) - describe('When gas is reimbursed', () => { - before(async function() { - if (!condition[1]) { - this.test!.parent!.pending = true - this.skip() - } - }) - - it('should send gas fee to msg.sender is fee recipient ix 0x0', async () => { - gasReceipt!.feeRecipient = ZERO_ADDRESS + it('should REVERT if token id is incorrect', async () => { + transferObj.id = id + 1 + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + }) - await receiverERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + it('should REVERT if token amount is incorrect', async () => { + transferObj.amount = amount + 1 + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - const receiverBalance = await erc1155Contract.balanceOf(receiverAddress, feeTokenID) + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + }) - expect(gasReceipt!.gasFee).to.be.eql(receiverBalance.toNumber()) - }) + it('should REVERT if transfer data is incorrect', async () => { + const sigArgTypes = ['address', 'address', 'address', 'uint256', 'uint256', 'uint256'] + const txDataTypes = ['bytes', 'bytes'] + + const signer = await transferObj.signerWallet.getAddress() + + // Packed encoding of transfer signature message + let sigData = utils.solidityPack(sigArgTypes, [ + transferObj.contractAddress, + signer, + transferObj.receiver, + transferObj.id, + transferObj.amount, + transferObj.nonce + ]) + + const transferData = transferObj.transferData == null ? utils.toUtf8Bytes('') : transferObj.transferData + let goodGasAndTransferData + let badGasAndTransferData + + // Correct and incorrect transferData + if (isGasReceipt) { + goodGasAndTransferData = utils.defaultAbiCoder.encode([GasReceiptType, 'bytes'], [gasReceipt, transferData]) + badGasAndTransferData = utils.defaultAbiCoder.encode( + [GasReceiptType, 'bytes'], + [gasReceipt, utils.toUtf8Bytes('Goodbyebyebye')] + ) + } else { + goodGasAndTransferData = utils.defaultAbiCoder.encode(['bytes'], [transferData]) + badGasAndTransferData = utils.defaultAbiCoder.encode(['bytes'], [utils.toUtf8Bytes('Goodbyebyebye')]) + } - it('should send gas fee to specified fee recipient (if not 0x0), not tx.origin', async () => { - await receiverERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - const operatorBalance = await erc1155Contract.balanceOf(operatorAddress, feeTokenID) + // Encode normally the whole thing + sigData = utils.solidityPack(['bytes', 'bytes'], [sigData, goodGasAndTransferData]) - expect(gasReceipt!.gasFee).to.be.eql(operatorBalance.toNumber()) - }) + // Get signature + const sig = (await ethSign(transferObj.signerWallet, sigData)).slice(0, -2) + const paddedNonce = utils.solidityPack(['uint256'], [transferObj.nonce]) + const ethsig_nonce = sig + paddedNonce.slice(2) + '02' // encode packed the nonce - it('should REVERT if gasReceipt is incorrect', async () => { - const sigArgTypes = ['address', 'address', 'address', 'uint256', 'uint256', 'uint256'] - const txDataTypes = ['bytes', 'bytes'] + // PASS BAD DATA + data = utils.defaultAbiCoder.encode(txDataTypes, [ethsig_nonce, badGasAndTransferData]) - const signer = await transferObj.signerWallet.getAddress() + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + }) - // Packed encoding of transfer signature message - let sigData = utils.solidityPack(sigArgTypes, [ - transferObj.contractAddress, - signer, - transferObj.receiver, - transferObj.id, - transferObj.amount, - transferObj.nonce - ]) + it('should REVERT if nonce is incorrect', async () => { + transferObj.nonce = nonce.add(101) + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - // Form bad gas receipt - const badGasReceipt = { ...gasReceipt, gasPrice: 109284123 } + // Nonce higher + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_NONCE')) - const transferData = transferObj.transferData == null ? utils.toUtf8Bytes('') : transferObj.transferData + // Correct nonce + transferObj.nonce = nonce + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + await operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - // Correct and incorrect transferData - const goodGasAndTransferData = utils.defaultAbiCoder.encode([GasReceiptType, 'bytes'], [gasReceipt, transferData]) - const badGasAndTransferData = utils.defaultAbiCoder.encode([GasReceiptType, 'bytes'], [badGasReceipt, transferData]) + // Nonce lower + const tx2 = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + await expect(tx2).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_NONCE')) + }) - // Encode normally the whole thing - sigData = utils.solidityPack(['bytes', 'bytes'], [sigData, goodGasAndTransferData]) + it('should PASS if signature is valid', async () => { + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + await expect(tx).to.be.fulfilled + }) - // Get signature - const sig = (await ethSign(transferObj.signerWallet, sigData)).slice(0, -2) - const paddedNonce = utils.solidityPack(['uint256'], [transferObj.nonce]) - const ethsig_nonce = sig + paddedNonce.slice(2) + '02' // encode packed the nonce + describe('ERC-1271 Receiver', () => { + let erc1271WalletValidationMockContract: ERC1271WalletValidationMock + let ERC1271WalletValidationMockAbstract: AbstractContract - // PASS BAD DATA - data = utils.defaultAbiCoder.encode(txDataTypes, [ethsig_nonce, badGasAndTransferData]) + let erc1271WalletAddress - const tx = operatorERC1155Contract.metaSafeTransferFrom( - ownerAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + beforeEach(async () => { + ERC1271WalletValidationMockAbstract = await AbstractContract.fromArtifactName('ERC1271WalletValidationMock') + erc1271WalletValidationMockContract = (await ERC1271WalletValidationMockAbstract.deploy(ownerWallet, [ + domainHash + ])) as ERC1271WalletValidationMock + erc1271WalletAddress = erc1271WalletValidationMockContract.address + + await erc1155Contract.mintMock(erc1271WalletAddress, id, initBalance, []) + await erc1155Contract.mintMock(erc1271WalletAddress, feeTokenID, feeTokenInitBalance, []) }) - it('should PASS if another approved ERC-1155 is used for fee', async () => { - const erc1155Contract2 = (await erc1155Abstract.deploy(ownerWallet, [ - NAME, - METADATA_URI - ])) as ERC1155MetaMintBurnMock - await erc1155Contract2.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) - await erc1155Contract2.setApprovalForAll(operatorERC1155Contract.address, true) - - feeTokenDataERC1155 = utils.defaultAbiCoder.encode( - ['address', 'uint256', 'uint8'], - [erc1155Contract2.address, feeTokenID, 0] - ) + describe(`EIP-1271 (bytes) signatures (03)`, () => { + it('should return REVERT if signature is invalid', async () => { + transferObj.from = erc1271WalletAddress + transferObj.signerWallet = receiverWallet - gasReceipt = { - gasLimitCallback: 130000, - gasFee: 1000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC1155 - } + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '03') + const tx = operatorERC1155Contract.metaSafeTransferFrom( + erc1271WalletAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + { gasLimit: 2000000 } + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + }) - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null + it('should REVERT if token ID is not 66', async () => { + const badID = 77 + await erc1155Contract.mintMock(erc1271WalletAddress, badID, initBalance, []) + transferObj.from = erc1271WalletAddress + transferObj.id = badID + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '03') + const tx = operatorERC1155Contract.metaSafeTransferFrom( + erc1271WalletAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + { gasLimit: 2000000 } + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + }) - // Data to pass in transfer method - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + it('should REVERT if amount is more than 100', async () => { + await erc1155Contract.mintMock(erc1271WalletAddress, id, 101, []) + transferObj.from = erc1271WalletAddress + transferObj.amount = 101 + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '03') + const tx = operatorERC1155Contract.metaSafeTransferFrom( + erc1271WalletAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + { gasLimit: 2000000 } + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + }) - const tx = operatorERC1155Contract.metaSafeTransferFrom( - ownerAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data, - { gasLimit: 2000000 } - ) - await expect(tx).to.be.fulfilled + it('should PASS if signature is valid', async () => { + transferObj.from = erc1271WalletAddress + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '03') + const tx = operatorERC1155Contract.metaSafeTransferFrom( + erc1271WalletAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + { gasLimit: 2000000 } + ) + await expect(tx).to.be.fulfilled + }) }) - it('should REVERT if NOT approved ERC-1155 is used for fee', async () => { - const erc1155Contract2 = (await erc1155Abstract.deploy(ownerWallet, [ - NAME, - METADATA_URI - ])) as ERC1155MetaMintBurnMock - await erc1155Contract2.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) + describe(`EIP-1271 (bytes32) signatures (04)`, () => { + it('should return REVERT if signature is invalid', async () => { + transferObj.from = erc1271WalletAddress + transferObj.signerWallet = receiverWallet - feeTokenDataERC1155 = utils.defaultAbiCoder.encode( - ['address', 'uint256', 'uint8'], - [erc1155Contract2.address, feeTokenID, 0] - ) - - gasReceipt = { - gasLimitCallback: 130000, - gasFee: 1000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC1155 - } + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '04') + const tx = operatorERC1155Contract.metaSafeTransferFrom( + erc1271WalletAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + { gasLimit: 2000000 } + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + }) - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null + it('should PASS if signature is valid', async () => { + transferObj.from = erc1271WalletAddress + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '04') + const tx = operatorERC1155Contract.metaSafeTransferFrom( + erc1271WalletAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + { gasLimit: 2000000 } + ) + await expect(tx).to.be.fulfilled + }) + }) + }) - // Data to pass in transfer method + describe('When signature is valid', () => { + it('should REVERT if insufficient balance', async () => { + transferObj.amount = initBalance + 1 data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeTransferFrom( ownerAddress, receiverAddress, id, - amount, + initBalance + 1, isGasReceipt, data, - { gasLimit: 2000000 } + HIGH_GAS_LIMIT ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155#safeTransferFrom: INVALID_OPERATOR')) + await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) }) - it('should REVERT if another ERC-1155 is used for fee without sufficient balance', async () => { - const erc1155Contract2 = (await erc1155Abstract.deploy(ownerWallet, [ - NAME, - METADATA_URI - ])) as ERC1155MetaMintBurnMock - await erc1155Contract2.mintMock(ownerAddress, feeTokenID, 100, []) - await erc1155Contract2.setApprovalForAll(operatorERC1155Contract.address, true) - - feeTokenDataERC1155 = utils.defaultAbiCoder.encode( - ['address', 'uint256', 'uint8'], - [erc1155Contract2.address, feeTokenID, 0] - ) - - gasReceipt = { - gasLimitCallback: 130000, - gasFee: 1000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC1155 - } - - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null - - // Data to pass in transfer method + it('should REVERT if sending to 0x0', async () => { + transferObj.receiver = ZERO_ADDRESS data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, ZERO_ADDRESS, id, amount, isGasReceipt, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#metaSafeTransferFrom: INVALID_RECIPIENT')) + }) + + it('should REVERT if transfer leads to overflow', async () => { + await erc1155Contract.mintMock(receiverAddress, id, MAXVAL, []) const tx = operatorERC1155Contract.metaSafeTransferFrom( ownerAddress, receiverAddress, @@ -690,231 +474,259 @@ describe('ERC1155Meta', () => { await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) }) - it('should PASS if approved ERC20 is used for fee', async () => { - const erc20Abstract = await AbstractContract.fromArtifactName('ERC20Mock') - const erc20Contract = (await erc20Abstract.deploy(ownerWallet)) as ERC20Mock - await erc20Contract.mockMint(ownerAddress, feeTokenInitBalance) - await erc20Contract.approve(operatorERC1155Contract.address, feeTokenInitBalance) - - const feeTokenDataERC20 = utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 1]) - - gasReceipt = { - gasLimitCallback: 130000, - gasFee: 1000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC20 - } - - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null - - // Data to pass in transfer method + it('should REVERT when sending to non-receiver contract', async () => { + transferObj.receiver = erc1155Contract.address data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeTransferFrom( ownerAddress, - receiverAddress, + erc1155Contract.address, id, amount, isGasReceipt, - data, - { gasLimit: 2000000 } + data ) - await expect(tx).to.be.fulfilled + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnMock: INVALID_METHOD')) }) - it('should REVERT if NOT approved ERC20 is used for fee', async () => { - const erc20Abstract = await AbstractContract.fromArtifactName('ERC20Mock') - const erc20Contract = (await erc20Abstract.deploy(ownerWallet)) as ERC20Mock - await erc20Contract.mockMint(ownerAddress, feeTokenInitBalance) - - const feeTokenDataERC20 = utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 1]) - - gasReceipt = { - gasLimitCallback: 130000, - gasFee: 1000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC20 - } - - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null - - // Data to pass in transfer method + it.skip('should REVERT if invalid response from receiver contract', async () => { + transferObj.receiver = receiverContract.address data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + // Force invalid response + await receiverContract.setShouldReject(true) + const tx = operatorERC1155Contract.metaSafeTransferFrom( ownerAddress, - receiverAddress, + receiverContract.address, id, amount, isGasReceipt, data, - HIGH_GAS_LIMIT + { gasLimit: 100_000 } ) - await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) + //FIXME Wrong error returned + await expect(tx).to.be.rejectedWith(RevertOutOfGasError()) }) - it('should REVERT if approved ERC20 balance is insufficient', async () => { - const erc20Abstract = await AbstractContract.fromArtifactName('ERC20Mock') - const erc20Contract = (await erc20Abstract.deploy(ownerWallet)) as ERC20Mock - await erc20Contract.mockMint(ownerAddress, 100) - await erc20Contract.approve(operatorERC1155Contract.address, feeTokenInitBalance) - - const feeTokenDataERC20 = utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 1]) - - gasReceipt = { - gasLimitCallback: 130000, - gasFee: 1000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC20 - } - - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null - - // Data to pass in transfer method - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + it('should PASS if valid response from receiver contract', async () => { + transferObj.receiver = receiverContract.address + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '02') const tx = operatorERC1155Contract.metaSafeTransferFrom( ownerAddress, - receiverAddress, + receiverContract.address, id, amount, isGasReceipt, - data, - HIGH_GAS_LIMIT + data ) - await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) + + await expect(tx).to.be.fulfilled }) - it('should REVERT if FeeTokenType is not supported', async () => { - const erc20Abstract = await AbstractContract.fromArtifactName('ERC20Mock') - const erc20Contract = (await erc20Abstract.deploy(ownerWallet)) as ERC20Mock - await erc20Contract.mockMint(ownerAddress, feeTokenInitBalance) - await erc20Contract.approve(operatorERC1155Contract.address, feeTokenInitBalance) + describe('When gas is reimbursed', () => { + before(async function() { + if (!condition[1]) { + this.test!.parent!.pending = true + this.skip() + } + }) - let feeTokenDataERC20 = utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 2]) + it('should send gas fee to msg.sender is fee recipient ix 0x0', async () => { + gasReceipt!.feeRecipient = ZERO_ADDRESS - gasReceipt = { - gasLimitCallback: 130000, - gasFee: 1000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC20 - } + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null + await receiverERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - // Data to pass in transfer method - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + const receiverBalance = await erc1155Contract.balanceOf(receiverAddress, feeTokenID) - const tx = operatorERC1155Contract.metaSafeTransferFrom( - ownerAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data, - { gasLimit: 2000000 } - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_transferGasFee: UNSUPPORTED_TOKEN')) + expect(gasReceipt!.gasFee).to.be.eql(receiverBalance.toNumber()) + }) - feeTokenDataERC20 = utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 3]) + it('should send gas fee to specified fee recipient (if not 0x0), not tx.origin', async () => { + await receiverERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + const operatorBalance = await erc1155Contract.balanceOf(operatorAddress, feeTokenID) - gasReceipt = { - gasLimitCallback: 130000, - gasFee: 1000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC20 - } + expect(gasReceipt!.gasFee).to.be.eql(operatorBalance.toNumber()) + }) - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null + it('should REVERT if gasReceipt is incorrect', async () => { + const sigArgTypes = ['address', 'address', 'address', 'uint256', 'uint256', 'uint256'] + const txDataTypes = ['bytes', 'bytes'] - // Data to pass in transfer method - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + const signer = await transferObj.signerWallet.getAddress() - const tx2 = operatorERC1155Contract.metaSafeTransferFrom( - ownerAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data, - { gasLimit: 2000000 } - ) - await expect(tx2).to.be.rejectedWith(RevertError('ERC1155Meta#_transferGasFee: UNSUPPORTED_TOKEN')) - }) + // Packed encoding of transfer signature message + let sigData = utils.solidityPack(sigArgTypes, [ + transferObj.contractAddress, + signer, + transferObj.receiver, + transferObj.id, + transferObj.amount, + transferObj.nonce + ]) - it('should REVERT if gas receipt is passed, but not claimed', async () => { - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, false, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) + // Form bad gas receipt + const badGasReceipt = { ...gasReceipt, gasPrice: 109284123 } - it('should REVERT if gas receipt is passed but isGasFee is false', async () => { - transferObj.isGasFee = false - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, true, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) + const transferData = transferObj.transferData == null ? utils.toUtf8Bytes('') : transferObj.transferData - it('should PASS if gas receipt is passed with isGasFee to false and not claimed', async () => { - transferObj.isGasFee = false - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, false, data) - await expect(tx).to.be.fulfilled - }) + // Correct and incorrect transferData + const goodGasAndTransferData = utils.defaultAbiCoder.encode([GasReceiptType, 'bytes'], [gasReceipt, transferData]) + const badGasAndTransferData = utils.defaultAbiCoder.encode([GasReceiptType, 'bytes'], [badGasReceipt, transferData]) + + // Encode normally the whole thing + sigData = utils.solidityPack(['bytes', 'bytes'], [sigData, goodGasAndTransferData]) + + // Get signature + const sig = (await ethSign(transferObj.signerWallet, sigData)).slice(0, -2) + const paddedNonce = utils.solidityPack(['uint256'], [transferObj.nonce]) + const ethsig_nonce = sig + paddedNonce.slice(2) + '02' // encode packed the nonce + + // PASS BAD DATA + data = utils.defaultAbiCoder.encode(txDataTypes, [ethsig_nonce, badGasAndTransferData]) - describe('When receiver is a contract', () => { - it('should REVERT if gas used in onERC1155Received exceeds limit', async () => { - const lowGasLimit = 1000 - gasReceipt!.gasLimitCallback = lowGasLimit - transferObj.receiver = receiverContract.address + const tx = operatorERC1155Contract.metaSafeTransferFrom( + ownerAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + }) + it('should PASS if another approved ERC-1155 is used for fee', async () => { + const erc1155Contract2 = await deployERC1155Contract() + await erc1155Contract2.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) + await erc1155Contract2.setApprovalForAll(operatorERC1155Contract.address, true) + + feeTokenDataERC1155 = utils.defaultAbiCoder.encode( + ['address', 'uint256', 'uint8'], + [erc1155Contract2.address, feeTokenID, 0] + ) + + gasReceipt = { + gasLimitCallback: 130000, + gasFee: 1000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC1155 + } + + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null + + // Data to pass in transfer method data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeTransferFrom( ownerAddress, - receiverContract.address, + receiverAddress, id, amount, isGasReceipt, data, { gasLimit: 2000000 } ) - await expect(tx).to.be.rejectedWith(RevertOutOfGasError()) + await expect(tx).to.be.fulfilled }) - it('should PASS if gas used in onERC1155Received does not exceed limit', async () => { - const okGasLimit = 9700 - gasReceipt!.gasLimitCallback = okGasLimit - transferObj.receiver = receiverContract.address + it('should REVERT if NOT approved ERC-1155 is used for fee', async () => { + const erc1155Contract2 = await deployERC1155Contract() + await erc1155Contract2.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) + feeTokenDataERC1155 = utils.defaultAbiCoder.encode( + ['address', 'uint256', 'uint8'], + [erc1155Contract2.address, feeTokenID, 0] + ) + + gasReceipt = { + gasLimitCallback: 130000, + gasFee: 1000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC1155 + } + + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null + + // Data to pass in transfer method data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeTransferFrom( ownerAddress, - receiverContract.address, + receiverAddress, id, amount, isGasReceipt, data, { gasLimit: 2000000 } ) - await expect(tx).to.be.fulfilled + await expect(tx).to.be.rejectedWith(RevertError('ERC1155#safeTransferFrom: INVALID_OPERATOR')) + }) + + it('should REVERT if another ERC-1155 is used for fee without sufficient balance', async () => { + const erc1155Contract2 = await deployERC1155Contract() + await erc1155Contract2.mintMock(ownerAddress, feeTokenID, 100, []) + await erc1155Contract2.setApprovalForAll(operatorERC1155Contract.address, true) + + feeTokenDataERC1155 = utils.defaultAbiCoder.encode( + ['address', 'uint256', 'uint8'], + [erc1155Contract2.address, feeTokenID, 0] + ) + + gasReceipt = { + gasLimitCallback: 130000, + gasFee: 1000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC1155 + } + + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null + + // Data to pass in transfer method + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSafeTransferFrom( + ownerAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + HIGH_GAS_LIMIT + ) + await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) }) - it('should PASS if gasLimitCallback is higher than gas sent in transaction', async () => { - const highGasLimit = 30000000 - gasReceipt!.gasLimitCallback = highGasLimit - transferObj.receiver = receiverContract.address + it('should PASS if approved ERC20 is used for fee', async () => { + const erc20Abstract = await AbstractContract.fromArtifactName('ERC20Mock') + const erc20Contract = (await erc20Abstract.deploy(ownerWallet)) as ERC20Mock + await erc20Contract.mockMint(ownerAddress, feeTokenInitBalance) + await erc20Contract.approve(operatorERC1155Contract.address, feeTokenInitBalance) + + const feeTokenDataERC20 = utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 1]) + + gasReceipt = { + gasLimitCallback: 130000, + gasFee: 1000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC20 + } + + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null + // Data to pass in transfer method data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeTransferFrom( ownerAddress, - receiverContract.address, + receiverAddress, id, amount, isGasReceipt, @@ -923,508 +735,480 @@ describe('ERC1155Meta', () => { ) await expect(tx).to.be.fulfilled }) - }) - }) - describe('When gas is NOT reimbursed', () => { - before(async function() { - if (condition[1]) { - this.test!.parent!.pending = true - this.skip() - } - }) + it('should REVERT if NOT approved ERC20 is used for fee', async () => { + const erc20Abstract = await AbstractContract.fromArtifactName('ERC20Mock') + const erc20Contract = (await erc20Abstract.deploy(ownerWallet)) as ERC20Mock + await erc20Contract.mockMint(ownerAddress, feeTokenInitBalance) - it('should PASS if gas receipt is not passed and not claimed', async () => { - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, false, data) - await expect(tx).to.be.fulfilled - }) + const feeTokenDataERC20 = utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 1]) - it('should REVER if gas receipt is not passed and claimed', async () => { - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, true, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) + gasReceipt = { + gasLimitCallback: 130000, + gasFee: 1000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC20 + } - it('should REVERT if gas receipt is not passed but isGasFee is set to true and is claimed', async () => { - transferObj.isGasFee = true - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, true, data) - await expect(tx).to.be.rejectedWith(RevertError()) - }) - }) + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null - context('When successful transfer', () => { - let tx: ContractTransaction + // Data to pass in transfer method + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - beforeEach(async () => { - tx = await operatorERC1155Contract.metaSafeTransferFrom( - ownerAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data - ) - }) + const tx = operatorERC1155Contract.metaSafeTransferFrom( + ownerAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + HIGH_GAS_LIMIT + ) + await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) + }) - it('should correctly update balance of sender', async () => { - const balance = await erc1155Contract.balanceOf(ownerAddress, id) - expect(balance).to.be.eql(BigNumber.from(initBalance - amount)) - }) + it('should REVERT if approved ERC20 balance is insufficient', async () => { + const erc20Abstract = await AbstractContract.fromArtifactName('ERC20Mock') + const erc20Contract = (await erc20Abstract.deploy(ownerWallet)) as ERC20Mock + await erc20Contract.mockMint(ownerAddress, 100) + await erc20Contract.approve(operatorERC1155Contract.address, feeTokenInitBalance) - it('should correctly update balance of receiver', async () => { - const balance = await erc1155Contract.balanceOf(receiverAddress, id) - expect(balance).to.be.eql(BigNumber.from(amount)) - }) + const feeTokenDataERC20 = utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 1]) - describe('When gas is reimbursed', () => { - before(async function() { - if (!condition[1]) { - this.test!.parent!.pending = true - this.skip() + gasReceipt = { + gasLimitCallback: 130000, + gasFee: 1000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC20 } - }) - it('should update gas token balance of sender', async () => { - const senderBalance = await erc1155Contract.balanceOf(ownerAddress, feeTokenID) - expect(senderBalance).to.be.eql(feeTokenInitBalance.sub(gasReceipt!.gasFee)) - }) + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null + + // Data to pass in transfer method + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - it('should update gas token balance of executor', async () => { - const balance = await erc1155Contract.balanceOf(operatorAddress, feeTokenID) - expect(gasReceipt!.gasFee).to.be.eql(balance.toNumber()) + const tx = operatorERC1155Contract.metaSafeTransferFrom( + ownerAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + HIGH_GAS_LIMIT + ) + await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) }) - }) - describe('TransferSingle event', async () => { - let filterFromOperatorContract: EventFilter + it('should REVERT if FeeTokenType is not supported', async () => { + const erc20Abstract = await AbstractContract.fromArtifactName('ERC20Mock') + const erc20Contract = (await erc20Abstract.deploy(ownerWallet)) as ERC20Mock + await erc20Contract.mockMint(ownerAddress, feeTokenInitBalance) + await erc20Contract.approve(operatorERC1155Contract.address, feeTokenInitBalance) - it('should emit TransferSingle event', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events!.pop()! - expect(ev.event).to.be.eql('TransferSingle') - }) + let feeTokenDataERC20 = utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 2]) - it('should have `msg.sender` as `_operator` field, not _from', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events!.pop()! + gasReceipt = { + gasLimitCallback: 130000, + gasFee: 1000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC20 + } - const args = ev.args! as any - expect(args._operator).to.be.eql(operatorAddress) - }) + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null - it('should have `msg.sender` as `_operator` field, not tx.origin', async () => { - // Get event filter to get internal tx event - filterFromOperatorContract = erc1155Contract.filters.TransferSingle( - operatorContract.address, - null, - null, - null, - null + // Data to pass in transfer method + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSafeTransferFrom( + ownerAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + { gasLimit: 2000000 } ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_transferGasFee: UNSUPPORTED_TOKEN')) + + feeTokenDataERC20 = utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 3]) + + gasReceipt = { + gasLimitCallback: 130000, + gasFee: 1000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC20 + } + + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null - // Increment nonce because it's the second transfer - transferObj.nonce = nonce.add(1) + // Data to pass in transfer method data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - // Execute transfer from operator contract - // @ts-ignore (https://github.com/ethereum-ts/TypeChain/issues/118) - await operatorContract.metaSafeTransferFrom( - erc1155Contract.address, + const tx2 = operatorERC1155Contract.metaSafeTransferFrom( ownerAddress, receiverAddress, id, amount, isGasReceipt, data, - { gasLimit: 1000000 } // INCORRECT GAS ESTIMATION + { gasLimit: 2000000 } ) + await expect(tx2).to.be.rejectedWith(RevertError('ERC1155Meta#_transferGasFee: UNSUPPORTED_TOKEN')) + }) - // Get logs from internal transaction event - // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) - filterFromOperatorContract.fromBlock = 0 - const logs = await operatorProvider.getLogs(filterFromOperatorContract) - const args = erc1155Contract.interface.decodeEventLog( - erc1155Contract.interface.events['TransferSingle(address,address,address,uint256,uint256)'], - logs[0].data, - logs[0].topics - ) + it('should REVERT if gas receipt is passed, but not claimed', async () => { + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, false, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + }) + + it('should REVERT if gas receipt is passed but isGasFee is false', async () => { + transferObj.isGasFee = false + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, true, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + }) + + it('should PASS if gas receipt is passed with isGasFee to false and not claimed', async () => { + transferObj.isGasFee = false + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, false, data) + await expect(tx).to.be.fulfilled + }) + + describe('When receiver is a contract', () => { + it('should REVERT if gas used in onERC1155Received exceeds limit', async () => { + const lowGasLimit = 1000 + gasReceipt!.gasLimitCallback = lowGasLimit + transferObj.receiver = receiverContract.address + + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSafeTransferFrom( + ownerAddress, + receiverContract.address, + id, + amount, + isGasReceipt, + data, + { gasLimit: 2000000 } + ) + await expect(tx).to.be.rejectedWith(RevertOutOfGasError()) + }) + + it('should PASS if gas used in onERC1155Received does not exceed limit', async () => { + const okGasLimit = 9700 + gasReceipt!.gasLimitCallback = okGasLimit + transferObj.receiver = receiverContract.address + + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSafeTransferFrom( + ownerAddress, + receiverContract.address, + id, + amount, + isGasReceipt, + data, + { gasLimit: 2000000 } + ) + await expect(tx).to.be.fulfilled + }) + + it('should PASS if gasLimitCallback is higher than gas sent in transaction', async () => { + const highGasLimit = 30000000 + gasReceipt!.gasLimitCallback = highGasLimit + transferObj.receiver = receiverContract.address + + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSafeTransferFrom( + ownerAddress, + receiverContract.address, + id, + amount, + isGasReceipt, + data, + { gasLimit: 2000000 } + ) + await expect(tx).to.be.fulfilled + }) + }) + }) + + describe('When gas is NOT reimbursed', () => { + before(async function() { + if (condition[1]) { + this.test!.parent!.pending = true + this.skip() + } + }) + + it('should PASS if gas receipt is not passed and not claimed', async () => { + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, false, data) + await expect(tx).to.be.fulfilled + }) + + it('should REVER if gas receipt is not passed and claimed', async () => { + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, true, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + }) - // operator arg should be equal to msg.sender, not tx.origin - expect(args._operator).to.be.eql(operatorContract.address) + it('should REVERT if gas receipt is not passed but isGasFee is set to true and is claimed', async () => { + transferObj.isGasFee = true + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, true, data) + await expect(tx).to.be.rejectedWith(RevertError()) }) + }) + + context('When successful transfer', () => { + let tx: ContractTransaction - it('should emit NonceChange event', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events![0] - expect(ev.event).to.be.eql('NonceChange') + beforeEach(async () => { + tx = await operatorERC1155Contract.metaSafeTransferFrom( + ownerAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data + ) }) - it('should have `_signer` as `signer` in NonceChange', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events![0] + it('should correctly update balance of sender', async () => { + const balance = await erc1155Contract.balanceOf(ownerAddress, id) + expect(balance).to.be.eql(BigNumber.from(initBalance - amount)) + }) - const args = ev.args! as any - expect(args.signer).to.be.eql(ownerWallet.address) + it('should correctly update balance of receiver', async () => { + const balance = await erc1155Contract.balanceOf(receiverAddress, id) + expect(balance).to.be.eql(BigNumber.from(amount)) }) - it('should have `nonce` as `nonce + 1` in NonceChange', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events![0] + describe('When gas is reimbursed', () => { + before(async function() { + if (!condition[1]) { + this.test!.parent!.pending = true + this.skip() + } + }) + + it('should update gas token balance of sender', async () => { + const senderBalance = await erc1155Contract.balanceOf(ownerAddress, feeTokenID) + expect(senderBalance).to.be.eql(feeTokenInitBalance.sub(gasReceipt!.gasFee)) + }) + + it('should update gas token balance of executor', async () => { + const balance = await erc1155Contract.balanceOf(operatorAddress, feeTokenID) + expect(gasReceipt!.gasFee).to.be.eql(balance.toNumber()) + }) + }) - const args = ev.args! as any - expect(args.newNonce).to.be.eql(nonce.add(1)) + describe('TransferSingle event', async () => { + let filterFromOperatorContract: EventFilter + + it('should emit TransferSingle event', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events!.pop()! + expect(ev.event).to.be.eql('TransferSingle') + }) + + it('should have `msg.sender` as `_operator` field, not _from', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events!.pop()! + + const args = ev.args! as any + expect(args._operator).to.be.eql(operatorAddress) + }) + + it('should have `msg.sender` as `_operator` field, not tx.origin', async () => { + // Get event filter to get internal tx event + filterFromOperatorContract = erc1155Contract.filters.TransferSingle( + operatorContract.address, + null, + null, + null, + null + ) + + // Increment nonce because it's the second transfer + transferObj.nonce = nonce.add(1) + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + + // Execute transfer from operator contract + // @ts-ignore (https://github.com/ethereum-ts/TypeChain/issues/118) + await operatorContract.metaSafeTransferFrom( + erc1155Contract.address, + ownerAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + { gasLimit: 1000000 } // INCORRECT GAS ESTIMATION + ) + + // Get logs from internal transaction event + // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) + filterFromOperatorContract.fromBlock = 0 + const logs = await operatorProvider.getLogs(filterFromOperatorContract) + const args = erc1155Contract.interface.decodeEventLog( + erc1155Contract.interface.events['TransferSingle(address,address,address,uint256,uint256)'], + logs[0].data, + logs[0].topics + ) + + // operator arg should be equal to msg.sender, not tx.origin + expect(args._operator).to.be.eql(operatorContract.address) + }) + + it('should emit NonceChange event', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events![0] + expect(ev.event).to.be.eql('NonceChange') + }) + + it('should have `_signer` as `signer` in NonceChange', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events![0] + + const args = ev.args! as any + expect(args.signer).to.be.eql(ownerWallet.address) + }) + + it('should have `nonce` as `nonce + 1` in NonceChange', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events![0] + + const args = ev.args! as any + expect(args.newNonce).to.be.eql(nonce.add(1)) + }) }) }) }) }) }) }) - }) - describe('metaSafeBatchTransferFrom() Function', () => { - let receiverContract: ERC1155ReceiverMock - let operatorContract: ERC1155OperatorMock - const METATRANSFER_IDENTIFIER = '0xebc71fa5' - - let transferData: string | null = 'Hello from the other side' - const initBalance = 100 - const amount = 10 - const nonce = BigNumber.from(0) - - // Parameters for balances - let ids: any[], amounts: any[] - const nTokenTypes = 33 - - let isGasReceipt: boolean = true - const feeTokenInitBalance = BigNumber.from(100000000) - - const feeType = 0 - const feeTokenID = 666 - let feeTokenAddress: string - let feeTokenDataERC1155: string | Uint8Array - - let transferObj: BatchTransferSignature - let gasReceipt: GasReceipt | null - let domainHash: string - let data: string - - const conditions = [ - [transferData, true, 'Gas receipt & transfer data'], - [null, true, 'Gas receipt w/o transfer data'], - [transferData, false, 'Transfer data w/o gas receipt '], - [null, false, 'No Gas receipt & No transfer data'] - ] - - conditions.forEach(function(condition) { - context(condition[2] as string, () => { - beforeEach(async () => { - // Get conditions - transferData = (await condition[0]) as string | null - isGasReceipt = (await condition[1]) as boolean - - // Deploy contracts - const abstract = await AbstractContract.fromArtifactName('ERC1155ReceiverMock') - receiverContract = (await abstract.deploy(ownerWallet)) as ERC1155ReceiverMock - operatorContract = (await operatorAbstract.deploy(operatorWallet)) as ERC1155OperatorMock - - // Mint tokens - ;(ids = []), (amounts = []) - - // Minting enough amounts for transfer for each types - for (let i = 0; i < nTokenTypes; i++) { - await erc1155Contract.mintMock(ownerAddress, i, initBalance, []) - ids.push(i) - amounts.push(amount) - } - - feeTokenAddress = erc1155Contract.address - - feeTokenDataERC1155 = utils.defaultAbiCoder.encode( - ['address', 'uint256', 'uint8'], - [feeTokenAddress, feeTokenID, feeType] - ) - - // Gas Receipt - gasReceipt = { - gasLimitCallback: 160000, - gasFee: 30000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC1155 - } - - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null - - // Transfer Signature Object - transferObj = { - contractAddress: erc1155Contract.address, - signerWallet: ownerWallet, - receiver: receiverAddress, - ids: ids.slice(0), - amounts: amounts.slice(0), - isGasFee: isGasReceipt, - transferData: transferData === null ? null : utils.toUtf8Bytes(transferData), - nonce: nonce - } - - // Mint tokens used to pay for gas - await erc1155Contract.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) - - // Domain hash - domainHash = utils.keccak256( - utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, erc1155Contract.address]) - ) - - // Data to pass in transfer method - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - }) + describe('metaSafeBatchTransferFrom() Function', () => { + let receiverContract: ERC1155ReceiverMock - it('should REVERT if contract address is incorrect', async () => { - domainHash = utils.keccak256( - utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, receiverContract.address]) - ) - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) + let transferData: string | null = 'Hello from the other side' + const initBalance = 100 + const amount = 10 + const nonce = BigNumber.from(0) - it('should REVERT if signer address is incorrect', async () => { - transferObj.signerWallet = operatorWallet - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) + // Parameters for balances + let ids: any[], amounts: any[] + const nTokenTypes = 33 - it('should REVERT if receiver address is incorrect', async () => { - transferObj.receiver = ownerAddress - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) + let isGasReceipt: boolean = true + const feeTokenInitBalance = BigNumber.from(100000000) - it('should REVERT if token id is incorrect', async () => { - transferObj.ids[0] = 6 - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) + const feeType = 0 + const feeTokenID = 666 + let feeTokenAddress: string + let feeTokenDataERC1155: string | Uint8Array - it('should REVERT if token amount is incorrect', async () => { - transferObj.amounts[0] = amount + 1 - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) + let transferObj: BatchTransferSignature + let gasReceipt: GasReceipt | null + let domainHash: string + let data: string - it('should REVERT if transfer data is incorrect', async () => { - const sigArgTypes = ['address', 'address', 'address', 'uint256[]', 'uint256[]', 'uint256'] - const txDataTypes = ['bytes', 'bytes'] - - const signer = await transferObj.signerWallet.getAddress() - - // Packed encoding of transfer signature message - let sigData = utils.solidityPack(sigArgTypes, [ - transferObj.contractAddress, - signer, - transferObj.receiver, - transferObj.ids, - transferObj.amounts, - transferObj.nonce - ]) - - const transferData = transferObj.transferData == null ? utils.toUtf8Bytes('') : transferObj.transferData - let goodGasAndTransferData - let badGasAndTransferData - - // Correct and incorrect transferData - if (isGasReceipt) { - goodGasAndTransferData = utils.defaultAbiCoder.encode([GasReceiptType, 'bytes'], [gasReceipt, transferData]) - badGasAndTransferData = utils.defaultAbiCoder.encode( - [GasReceiptType, 'bytes'], - [gasReceipt, utils.toUtf8Bytes('Goodbyebyebye')] - ) - } else { - goodGasAndTransferData = utils.defaultAbiCoder.encode(['bytes'], [transferData]) - badGasAndTransferData = utils.defaultAbiCoder.encode(['bytes'], [utils.toUtf8Bytes('Goodbyebyebye')]) - } - - // Encode normally the whole thing - sigData = utils.solidityPack(['bytes', 'bytes'], [sigData, goodGasAndTransferData]) - - // Get signature - const sig = (await ethSign(transferObj.signerWallet, sigData)).slice(0, -2) - const paddedNonce = utils.solidityPack(['uint256'], [transferObj.nonce]) - const ethsig_nonce = sig + paddedNonce.slice(2) + '02' // encode packed the nonce - - // PASS BAD DATA - data = utils.defaultAbiCoder.encode(txDataTypes, [ethsig_nonce, badGasAndTransferData]) - - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) + const conditions = [ + [transferData, true, 'Gas receipt & transfer data'], + [null, true, 'Gas receipt w/o transfer data'], + [transferData, false, 'Transfer data w/o gas receipt '], + [null, false, 'No Gas receipt & No transfer data'] + ] - it('should REVERT if nonce is incorrect', async () => { - transferObj.nonce = nonce.add(101) - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_NONCE')) - - // Correct nonce - transferObj.nonce = nonce - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - await operatorERC1155Contract.metaSafeBatchTransferFrom(ownerAddress, receiverAddress, ids, amounts, isGasReceipt, data) - - // Nonce lower - const tx2 = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx2).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_NONCE')) - }) + conditions.forEach(function(condition) { + context(condition[2] as string, () => { + beforeEach(async () => { + // Get conditions + transferData = (await condition[0]) as string | null + isGasReceipt = (await condition[1]) as boolean + + // Deploy contracts + const abstract = await AbstractContract.fromArtifactName('ERC1155ReceiverMock') + receiverContract = (await abstract.deploy(ownerWallet)) as ERC1155ReceiverMock + + // Mint tokens + ids = [] + amounts = [] + + // Minting enough amounts for transfer for each types + for (let i = 0; i < nTokenTypes; i++) { + await erc1155Contract.mintMock(ownerAddress, i, initBalance, []) + ids.push(i) + amounts.push(amount) + } - it('should PASS if signature is valid', async () => { - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.fulfilled - }) + feeTokenAddress = erc1155Contract.address - describe('ERC-1271 Receiver', () => { - let erc1271WalletValidationMockContract: ERC1271WalletValidationMock - let ERC1271WalletValidationMockAbstract: AbstractContract + feeTokenDataERC1155 = utils.defaultAbiCoder.encode( + ['address', 'uint256', 'uint8'], + [feeTokenAddress, feeTokenID, feeType] + ) - let erc1271WalletAddress + // Gas Receipt + gasReceipt = { + gasLimitCallback: 160000, + gasFee: 30000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC1155 + } - beforeEach(async () => { - ERC1271WalletValidationMockAbstract = await AbstractContract.fromArtifactName('ERC1271WalletValidationMock') - erc1271WalletValidationMockContract = (await ERC1271WalletValidationMockAbstract.deploy(ownerWallet, [ - domainHash - ])) as ERC1271WalletValidationMock - erc1271WalletAddress = erc1271WalletValidationMockContract.address - - await erc1155Contract.batchMintMock(erc1271WalletAddress, ids, amounts, []) - await erc1155Contract.mintMock(erc1271WalletAddress, feeTokenID, feeTokenInitBalance, []) - }) + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null + + // Transfer Signature Object + transferObj = { + contractAddress: erc1155Contract.address, + signerWallet: ownerWallet, + receiver: receiverAddress, + ids: ids.slice(0), + amounts: amounts.slice(0), + isGasFee: isGasReceipt, + transferData: transferData === null ? null : utils.toUtf8Bytes(transferData), + nonce: nonce + } - describe(`EIP-1271 (bytes) signatures (03)`, () => { - it('should return REVERT if signature is invalid', async () => { - transferObj.from = erc1271WalletAddress - transferObj.signerWallet = receiverWallet + // Mint tokens used to pay for gas + await erc1155Contract.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt, '03') - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - erc1271WalletAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) - }) + // Domain hash + domainHash = utils.keccak256( + utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, erc1155Contract.address]) + ) - describe(`EIP-1271 (bytes32) signatures (04)`, () => { - it('should return REVERT if signature is invalid', async () => { - transferObj.from = erc1271WalletAddress - transferObj.signerWallet = receiverWallet + // Data to pass in transfer method + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + }, 60000) - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt, '04') - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - erc1271WalletAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) + it('should REVERT if contract address is incorrect', async () => { + domainHash = utils.keccak256( + utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, receiverContract.address]) + ) + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - it('should PASS if signature is valid', async () => { - transferObj.from = erc1271WalletAddress - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt, '04') - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - erc1271WalletAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.fulfilled - }) + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, + ids, + amounts, + isGasReceipt, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) }) - }) - describe('When signature is valid', () => { - it('should REVERT if insufficient balance', async () => { - transferObj.amounts[0] = initBalance + 1 - amounts[0] = initBalance + 1 + it('should REVERT if signer address is incorrect', async () => { + transferObj.signerWallet = operatorWallet data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( @@ -1433,780 +1217,977 @@ describe('ERC1155Meta', () => { ids, amounts, isGasReceipt, - data, - HIGH_GAS_LIMIT + data ) - await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) }) - it('should REVERT if sending to 0x0', async () => { - transferObj.receiver = ZERO_ADDRESS + it('should REVERT if receiver address is incorrect', async () => { + transferObj.receiver = ownerAddress data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - ZERO_ADDRESS, + receiverAddress, ids, amounts, isGasReceipt, data ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#metaSafeBatchTransferFrom: INVALID_RECIPIENT')) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) }) - it('should REVERT if transfer leads to overflow', async () => { - await operatorERC1155Contract.mintMock(receiverAddress, ids[0], MAXVAL, []) + it('should REVERT if token id is incorrect', async () => { + transferObj.ids[0] = 6 + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, receiverAddress, ids, amounts, isGasReceipt, - data, - HIGH_GAS_LIMIT + data ) - await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) }) - it('should REVERT when sending to non-receiver contract', async () => { - transferObj.receiver = erc1155Contract.address + it('should REVERT if token amount is incorrect', async () => { + transferObj.amounts[0] = amount + 1 data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - erc1155Contract.address, + receiverAddress, ids, amounts, isGasReceipt, data ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnMock: INVALID_METHOD')) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) }) - it('should REVERT if invalid response from receiver contract', async () => { - transferObj.receiver = receiverContract.address - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + it('should REVERT if transfer data is incorrect', async () => { + const sigArgTypes = ['address', 'address', 'address', 'uint256[]', 'uint256[]', 'uint256'] + const txDataTypes = ['bytes', 'bytes'] + + const signer = await transferObj.signerWallet.getAddress() + + // Packed encoding of transfer signature message + let sigData = utils.solidityPack(sigArgTypes, [ + transferObj.contractAddress, + signer, + transferObj.receiver, + transferObj.ids, + transferObj.amounts, + transferObj.nonce + ]) + + const transferData = transferObj.transferData == null ? utils.toUtf8Bytes('') : transferObj.transferData + let goodGasAndTransferData + let badGasAndTransferData + + // Correct and incorrect transferData + if (isGasReceipt) { + goodGasAndTransferData = utils.defaultAbiCoder.encode([GasReceiptType, 'bytes'], [gasReceipt, transferData]) + badGasAndTransferData = utils.defaultAbiCoder.encode( + [GasReceiptType, 'bytes'], + [gasReceipt, utils.toUtf8Bytes('Goodbyebyebye')] + ) + } else { + goodGasAndTransferData = utils.defaultAbiCoder.encode(['bytes'], [transferData]) + badGasAndTransferData = utils.defaultAbiCoder.encode(['bytes'], [utils.toUtf8Bytes('Goodbyebyebye')]) + } + + // Encode normally the whole thing + sigData = utils.solidityPack(['bytes', 'bytes'], [sigData, goodGasAndTransferData]) + + // Get signature + const sig = (await ethSign(transferObj.signerWallet, sigData)).slice(0, -2) + const paddedNonce = utils.solidityPack(['uint256'], [transferObj.nonce]) + const ethsig_nonce = sig + paddedNonce.slice(2) + '02' // encode packed the nonce - // Force invalid response - await receiverContract.setShouldReject(true) + // PASS BAD DATA + data = utils.defaultAbiCoder.encode(txDataTypes, [ethsig_nonce, badGasAndTransferData]) const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - receiverContract.address, + receiverAddress, ids, amounts, isGasReceipt, - data, - { gasLimit: 200_000 } + data ) - await expect(tx).to.be.rejectedWith(/ERC1155#_callonERC1155Received: INVALID_ON_RECEIVE_MESSAGE|out of gas/) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) }) - it('should PASS if valid response from receiver contract', async () => { - transferObj.receiver = receiverContract.address + it('should REVERT if nonce is incorrect', async () => { + transferObj.nonce = nonce.add(101) data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - receiverContract.address, + receiverAddress, ids, amounts, isGasReceipt, - data, - { gasLimit: 2000000 } + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_NONCE')) + + // Correct nonce + transferObj.nonce = nonce + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + await operatorERC1155Contract.metaSafeBatchTransferFrom(ownerAddress, receiverAddress, ids, amounts, isGasReceipt, data) + + // Nonce lower + const tx2 = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, + ids, + amounts, + isGasReceipt, + data ) + await expect(tx2).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_NONCE')) + }) + it('should PASS if signature is valid', async () => { + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, + ids, + amounts, + isGasReceipt, + data + ) await expect(tx).to.be.fulfilled }) - describe('When gas is reimbursed', () => { - before(async function() { - if (!condition[1]) { - this.test!.parent!.pending = true - this.skip() - } + describe('ERC-1271 Receiver', () => { + let erc1271WalletValidationMockContract: ERC1271WalletValidationMock + let ERC1271WalletValidationMockAbstract: AbstractContract + + let erc1271WalletAddress + + beforeEach(async () => { + ERC1271WalletValidationMockAbstract = await AbstractContract.fromArtifactName('ERC1271WalletValidationMock') + erc1271WalletValidationMockContract = (await ERC1271WalletValidationMockAbstract.deploy(ownerWallet, [ + domainHash + ])) as ERC1271WalletValidationMock + erc1271WalletAddress = erc1271WalletValidationMockContract.address + + await erc1155Contract.batchMintMock(erc1271WalletAddress, ids, amounts, []) + await erc1155Contract.mintMock(erc1271WalletAddress, feeTokenID, feeTokenInitBalance, []) }) - it('should send gas fee to tx.origin is fee recipient ix 0x0', async () => { - gasReceipt!.feeRecipient = ZERO_ADDRESS + describe(`EIP-1271 (bytes) signatures (03)`, () => { + it('should return REVERT if signature is invalid', async () => { + transferObj.from = erc1271WalletAddress + transferObj.signerWallet = receiverWallet + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt, '03') + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + erc1271WalletAddress, + receiverAddress, + ids, + amounts, + isGasReceipt, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + }) + }) + + describe(`EIP-1271 (bytes32) signatures (04)`, () => { + it('should return REVERT if signature is invalid', async () => { + transferObj.from = erc1271WalletAddress + transferObj.signerWallet = receiverWallet + + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt, '04') + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + erc1271WalletAddress, + receiverAddress, + ids, + amounts, + isGasReceipt, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + }) + + it('should PASS if signature is valid', async () => { + transferObj.from = erc1271WalletAddress + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt, '04') + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + erc1271WalletAddress, + receiverAddress, + ids, + amounts, + isGasReceipt, + data + ) + await expect(tx).to.be.fulfilled + }) + }) + }) + + describe('When signature is valid', () => { + it('should REVERT if insufficient balance', async () => { + transferObj.amounts[0] = initBalance + 1 + amounts[0] = initBalance + 1 data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - await receiverERC1155Contract.metaSafeBatchTransferFrom( + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, receiverAddress, ids, amounts, isGasReceipt, - data + data, + HIGH_GAS_LIMIT ) - - const receiverBalance = await operatorERC1155Contract.balanceOf(receiverAddress, feeTokenID) - - expect(gasReceipt!.gasFee).to.be.eql(receiverBalance.toNumber()) + await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) }) - it('should send gas fee to specified fee recipient (if not 0x0), not tx.origin', async () => { - await receiverERC1155Contract.metaSafeBatchTransferFrom( + it('should REVERT if sending to 0x0', async () => { + transferObj.receiver = ZERO_ADDRESS + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - receiverAddress, + ZERO_ADDRESS, ids, amounts, isGasReceipt, data ) - const operatorBalance = await operatorERC1155Contract.balanceOf(operatorAddress, feeTokenID) - - expect(gasReceipt!.gasFee).to.be.eql(operatorBalance.toNumber()) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#metaSafeBatchTransferFrom: INVALID_RECIPIENT')) }) - it('should REVERT if gasReceipt is incorrect', async () => { - const sigArgTypes = ['address', 'address', 'address', 'uint256[]', 'uint256[]', 'uint256'] - const txDataTypes = ['bytes', 'bytes'] - - const signer = await transferObj.signerWallet.getAddress() - - // Packed encoding of transfer signature message - let sigData = utils.solidityPack(sigArgTypes, [ - transferObj.contractAddress, - signer, - transferObj.receiver, - transferObj.ids, - transferObj.amounts, - transferObj.nonce - ]) - - // Form bad gas receipt - const badGasReceipt = { ...gasReceipt, gasPrice: 109284123 } - - const transferData = transferObj.transferData == null ? utils.toUtf8Bytes('') : transferObj.transferData - - // Correct and incorrect transferData - const goodGasAndTransferData = utils.defaultAbiCoder.encode([GasReceiptType, 'bytes'], [gasReceipt, transferData]) - const badGasAndTransferData = utils.defaultAbiCoder.encode([GasReceiptType, 'bytes'], [badGasReceipt, transferData]) - - // Encode normally the whole thing - sigData = utils.solidityPack(['bytes', 'bytes'], [sigData, goodGasAndTransferData]) - - // Get signature - const sig = (await ethSign(transferObj.signerWallet, sigData)).slice(0, -2) - const paddedNonce = utils.solidityPack(['uint256'], [transferObj.nonce]) - const ethsig_nonce = sig + paddedNonce.slice(2) + '02' // encode packed the nonce - - // PASS BAD DATA - data = utils.defaultAbiCoder.encode(txDataTypes, [ethsig_nonce, badGasAndTransferData]) - + it('should REVERT if transfer leads to overflow', async () => { + await operatorERC1155Contract.mintMock(receiverAddress, ids[0], MAXVAL, []) const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, receiverAddress, ids, amounts, isGasReceipt, - data + data, + HIGH_GAS_LIMIT ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) }) - it('should REVERT if gas receipt is passed, but not claimed', async () => { + it('should REVERT when sending to non-receiver contract', async () => { + transferObj.receiver = erc1155Contract.address + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - receiverAddress, + erc1155Contract.address, ids, amounts, - false, + isGasReceipt, data ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnMock: INVALID_METHOD')) }) - it('should REVERT if gas receipt is passed but isGasFee is false', async () => { - transferObj.isGasFee = false + it.skip('should REVERT if invalid response from receiver contract', async () => { + transferObj.receiver = receiverContract.address data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + + // Force invalid response + await receiverContract.setShouldReject(true) + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - receiverAddress, + receiverContract.address, ids, amounts, - true, - data + isGasReceipt, + data, + { gasLimit: 200_000 } ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + //FIXME Wrong error returned + await expect(tx).to.be.rejectedWith(RevertOutOfGasError()) }) - it('should PASS if gas receipt is passed with isGasFee to false and not claimed', async () => { - transferObj.isGasFee = false + it('should PASS if valid response from receiver contract', async () => { + transferObj.receiver = receiverContract.address data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - receiverAddress, + receiverContract.address, ids, amounts, - false, - data + isGasReceipt, + data, + { gasLimit: 2000000 } ) + await expect(tx).to.be.fulfilled }) - describe('When receiver is a contract', () => { - it('should REVERT if gas used in onERC1155BatchReceived exceeds limit', async () => { - const lowGasLimit = 1000 - gasReceipt!.gasLimitCallback = lowGasLimit - transferObj.receiver = receiverContract.address - - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverContract.address, - ids, - amounts, - isGasReceipt, - data, - { gasLimit: 2_000_000 } - ) - await expect(tx).to.be.rejectedWith(RevertOutOfGasError()) + describe('When gas is reimbursed', () => { + before(async function() { + if (!condition[1]) { + this.test!.parent!.pending = true + this.skip() + } }) - it('should PASS if gas used in onERC1155BatchReceived does not exceed limit', async () => { - const okGasLimit = 160000 - gasReceipt!.gasLimitCallback = okGasLimit - transferObj.receiver = receiverContract.address + it('should send gas fee to tx.origin is fee recipient ix 0x0', async () => { + gasReceipt!.feeRecipient = ZERO_ADDRESS data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + await receiverERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - receiverContract.address, + receiverAddress, ids, amounts, isGasReceipt, - data, - { gasLimit: 2000000 } + data ) - await expect(tx).to.be.fulfilled - }) - it('should PASS if gasLimit is higher than gas sent in transaction', async () => { - const highGasLimit = 3000000 - gasReceipt!.gasLimitCallback = highGasLimit - transferObj.receiver = receiverContract.address + const receiverBalance = await operatorERC1155Contract.balanceOf(receiverAddress, feeTokenID) - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + expect(gasReceipt!.gasFee).to.be.eql(receiverBalance.toNumber()) + }) - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + it('should send gas fee to specified fee recipient (if not 0x0), not tx.origin', async () => { + await receiverERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - receiverContract.address, + receiverAddress, ids, amounts, isGasReceipt, - data, - { gasLimit: 2000000 } + data ) - await expect(tx).to.be.fulfilled - }) - }) - }) - - describe('When gas is NOT reimbursed', () => { - before(async function() { - if (condition[1]) { - this.test!.parent!.pending = true - this.skip() - } - }) - - it('should PASS if gas receipt is not passed and not claimed', async () => { - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - false, - data - ) - await expect(tx).to.be.fulfilled - }) - - it('should REVER if gas receipt is not passed and claimed', async () => { - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - true, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) - - it('should REVERT if gas receipt is not passed but isGasFee is set to true and is claimed', async () => { - transferObj.isGasFee = true - data = await encodeMetaBatchTransferFromData(transferObj, domainHash) - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - true, - data - ) - await expect(tx).to.be.rejectedWith(RevertError()) - }) - }) - - context('When successful transfer', () => { - let tx: ContractTransaction + const operatorBalance = await operatorERC1155Contract.balanceOf(operatorAddress, feeTokenID) - beforeEach(async () => { - tx = await operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - }) + expect(gasReceipt!.gasFee).to.be.eql(operatorBalance.toNumber()) + }) - it('should correctly update balance of sender and receiver', async () => { - let balanceFrom: BigNumber - let balanceTo: BigNumber + it('should REVERT if gasReceipt is incorrect', async () => { + const sigArgTypes = ['address', 'address', 'address', 'uint256[]', 'uint256[]', 'uint256'] + const txDataTypes = ['bytes', 'bytes'] - for (let i = 0; i < ids.length; i++) { - balanceFrom = await operatorERC1155Contract.balanceOf(ownerAddress, ids[i]) - balanceTo = await operatorERC1155Contract.balanceOf(receiverAddress, ids[i]) + const signer = await transferObj.signerWallet.getAddress() - expect(balanceFrom).to.be.eql(BigNumber.from(initBalance - amounts[i])) - expect(balanceTo).to.be.eql(BigNumber.from(amounts[i])) - } - }) + // Packed encoding of transfer signature message + let sigData = utils.solidityPack(sigArgTypes, [ + transferObj.contractAddress, + signer, + transferObj.receiver, + transferObj.ids, + transferObj.amounts, + transferObj.nonce + ]) - describe('When gas is reimbursed', () => { - before(async function() { - if (!condition[1]) { - this.test!.parent!.pending = true - this.skip() - } + // Form bad gas receipt + const badGasReceipt = { ...gasReceipt, gasPrice: 109284123 } + + const transferData = transferObj.transferData == null ? utils.toUtf8Bytes('') : transferObj.transferData + + // Correct and incorrect transferData + const goodGasAndTransferData = utils.defaultAbiCoder.encode([GasReceiptType, 'bytes'], [gasReceipt, transferData]) + const badGasAndTransferData = utils.defaultAbiCoder.encode([GasReceiptType, 'bytes'], [badGasReceipt, transferData]) + + // Encode normally the whole thing + sigData = utils.solidityPack(['bytes', 'bytes'], [sigData, goodGasAndTransferData]) + + // Get signature + const sig = (await ethSign(transferObj.signerWallet, sigData)).slice(0, -2) + const paddedNonce = utils.solidityPack(['uint256'], [transferObj.nonce]) + const ethsig_nonce = sig + paddedNonce.slice(2) + '02' // encode packed the nonce + + // PASS BAD DATA + data = utils.defaultAbiCoder.encode(txDataTypes, [ethsig_nonce, badGasAndTransferData]) + + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, + ids, + amounts, + isGasReceipt, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) }) - it('should update gas token balance of sender', async () => { - const senderBalance = await operatorERC1155Contract.balanceOf(ownerAddress, feeTokenID) - expect(senderBalance).to.be.eql(feeTokenInitBalance.sub(gasReceipt!.gasFee)) + it('should REVERT if gas receipt is passed, but not claimed', async () => { + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, + ids, + amounts, + false, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) }) - it('should update gas token balance of executor', async () => { - const balance = await operatorERC1155Contract.balanceOf(operatorAddress, feeTokenID) - expect(gasReceipt!.gasFee).to.be.eql(balance.toNumber()) + it('should REVERT if gas receipt is passed but isGasFee is false', async () => { + transferObj.isGasFee = false + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, + ids, + amounts, + true, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) }) - }) - describe('TransferBatch event', async () => { - let filterFromOperatorContract: EventFilter - let operatorContract: ERC1155OperatorMock + it('should PASS if gas receipt is passed with isGasFee to false and not claimed', async () => { + transferObj.isGasFee = false + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - beforeEach(async () => { - operatorContract = (await operatorAbstract.deploy(operatorWallet)) as ERC1155OperatorMock + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, + ids, + amounts, + false, + data + ) + await expect(tx).to.be.fulfilled }) - it('should emit 1 TransferBatch events of N transfers', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events![1] - expect(ev.event).to.be.eql('TransferBatch') + describe('When receiver is a contract', () => { + it('should REVERT if gas used in onERC1155BatchReceived exceeds limit', async () => { + const lowGasLimit = 1000 + gasReceipt!.gasLimitCallback = lowGasLimit + transferObj.receiver = receiverContract.address + + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverContract.address, + ids, + amounts, + isGasReceipt, + data, + { gasLimit: 2_000_000 } + ) + await expect(tx).to.be.rejectedWith(RevertOutOfGasError()) + }) + + it('should PASS if gas used in onERC1155BatchReceived does not exceed limit', async () => { + const okGasLimit = 160000 + gasReceipt!.gasLimitCallback = okGasLimit + transferObj.receiver = receiverContract.address + + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverContract.address, + ids, + amounts, + isGasReceipt, + data, + { gasLimit: 2000000 } + ) + await expect(tx).to.be.fulfilled + }) + + it('should PASS if gasLimit is higher than gas sent in transaction', async () => { + const highGasLimit = 3000000 + gasReceipt!.gasLimitCallback = highGasLimit + transferObj.receiver = receiverContract.address + + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverContract.address, + ids, + amounts, + isGasReceipt, + data, + { gasLimit: 2000000 } + ) + await expect(tx).to.be.fulfilled + }) + }) + }) - const args = ev.args! as any - expect(args._ids.length).to.be.eql(ids.length) + describe('When gas is NOT reimbursed', () => { + before(async function() { + if (condition[1]) { + this.test!.parent!.pending = true + this.skip() + } }) - it('should have `msg.sender` as `_operator` field, not _from', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events!.pop()! + it('should PASS if gas receipt is not passed and not claimed', async () => { + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, + ids, + amounts, + false, + data + ) + await expect(tx).to.be.fulfilled + }) - const args = ev.args! as any - expect(args._operator).to.be.eql(operatorAddress) + it('should REVER if gas receipt is not passed and claimed', async () => { + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, + ids, + amounts, + true, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) }) - it('should have `msg.sender` as `_operator` field, not tx.origin', async () => { - // Get event filter to get internal tx event - filterFromOperatorContract = erc1155Contract.filters.TransferBatch( - operatorContract.address, - null, - null, - null, - null + it('should REVERT if gas receipt is not passed but isGasFee is set to true and is claimed', async () => { + transferObj.isGasFee = true + data = await encodeMetaBatchTransferFromData(transferObj, domainHash) + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, + ids, + amounts, + true, + data ) + await expect(tx).to.be.rejectedWith(RevertError()) + }) + }) - //Increment nonce because it's the second transfer - transferObj.nonce = nonce.add(1) - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + context('When successful transfer', () => { + let tx: ContractTransaction - // Execute transfer from operator contract - // @ts-ignore (https://github.com/ethereum-ts/TypeChain/issues/118) - await operatorContract.metaSafeBatchTransferFrom( - erc1155Contract.address, + beforeEach(async () => { + tx = await operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, receiverAddress, ids, amounts, isGasReceipt, - data, - { gasLimit: 2000000 } // INCORRECT GAS ESTIMATION - ) - - // Get logs from internal transaction event - // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) - filterFromOperatorContract.fromBlock = 0 - const logs = await operatorProvider.getLogs(filterFromOperatorContract) - const args = erc1155Contract.interface.decodeEventLog( - erc1155Contract.interface.events['TransferBatch(address,address,address,uint256[],uint256[])'], - logs[0].data, - logs[0].topics + data ) - - // operator arg should be equal to msg.sender, not tx.origin - expect(args._operator).to.be.eql(operatorContract.address) }) - it('should emit NonceChange event', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events![0] - expect(ev.event).to.be.eql('NonceChange') - }) + it('should correctly update balance of sender and receiver', async () => { + let balanceFrom: BigNumber + let balanceTo: BigNumber - it('should have `_signer` as `signer` in NonceChange', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events![0] + for (let i = 0; i < ids.length; i++) { + balanceFrom = await operatorERC1155Contract.balanceOf(ownerAddress, ids[i]) + balanceTo = await operatorERC1155Contract.balanceOf(receiverAddress, ids[i]) - const args = ev.args! as any - expect(args.signer).to.be.eql(ownerWallet.address) + expect(balanceFrom).to.be.eql(BigNumber.from(initBalance - amounts[i])) + expect(balanceTo).to.be.eql(BigNumber.from(amounts[i])) + } }) - it('should have `nonce` as `nonce + 1` in NonceChange', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events![0] + describe('When gas is reimbursed', () => { + before(async function() { + if (!condition[1]) { + this.test!.parent!.pending = true + this.skip() + } + }) + + it('should update gas token balance of sender', async () => { + const senderBalance = await operatorERC1155Contract.balanceOf(ownerAddress, feeTokenID) + expect(senderBalance).to.be.eql(feeTokenInitBalance.sub(gasReceipt!.gasFee)) + }) + + it('should update gas token balance of executor', async () => { + const balance = await operatorERC1155Contract.balanceOf(operatorAddress, feeTokenID) + expect(gasReceipt!.gasFee).to.be.eql(balance.toNumber()) + }) + }) - const args = ev.args! as any - expect(args.newNonce).to.be.eql(nonce.add(1)) + describe('TransferBatch event', async () => { + let filterFromOperatorContract: EventFilter + let operatorContract: ERC1155OperatorMock + + beforeEach(async () => { + operatorContract = (await operatorAbstract.deploy(operatorWallet)) as ERC1155OperatorMock + }) + + it('should emit 1 TransferBatch events of N transfers', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events![1] + expect(ev.event).to.be.eql('TransferBatch') + + const args = ev.args! as any + expect(args._ids.length).to.be.eql(ids.length) + }) + + it('should have `msg.sender` as `_operator` field, not _from', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events!.pop()! + + const args = ev.args! as any + expect(args._operator).to.be.eql(operatorAddress) + }) + + it('should have `msg.sender` as `_operator` field, not tx.origin', async () => { + // Get event filter to get internal tx event + filterFromOperatorContract = erc1155Contract.filters.TransferBatch( + operatorContract.address, + null, + null, + null, + null + ) + + //Increment nonce because it's the second transfer + transferObj.nonce = nonce.add(1) + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + + // Execute transfer from operator contract + // @ts-ignore (https://github.com/ethereum-ts/TypeChain/issues/118) + await operatorContract.metaSafeBatchTransferFrom( + erc1155Contract.address, + ownerAddress, + receiverAddress, + ids, + amounts, + isGasReceipt, + data, + { gasLimit: 2000000 } // INCORRECT GAS ESTIMATION + ) + + // Get logs from internal transaction event + // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) + filterFromOperatorContract.fromBlock = 0 + const logs = await operatorProvider.getLogs(filterFromOperatorContract) + const args = erc1155Contract.interface.decodeEventLog( + erc1155Contract.interface.events['TransferBatch(address,address,address,uint256[],uint256[])'], + logs[0].data, + logs[0].topics + ) + + // operator arg should be equal to msg.sender, not tx.origin + expect(args._operator).to.be.eql(operatorContract.address) + }) + + it('should emit NonceChange event', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events![0] + expect(ev.event).to.be.eql('NonceChange') + }) + + it('should have `_signer` as `signer` in NonceChange', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events![0] + + const args = ev.args! as any + expect(args.signer).to.be.eql(ownerWallet.address) + }) + + it('should have `nonce` as `nonce + 1` in NonceChange', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events![0] + + const args = ev.args! as any + expect(args.newNonce).to.be.eql(nonce.add(1)) + }) }) }) }) }) }) }) - }) - describe('metaSetApprovalForAll() function', () => { - const initBalance = 100 - let isGasReimbursed = true - const approved = true - const nonce = BigNumber.from(0) - const id = 66 - - let approvalObj: ApprovalSignature - let gasReceipt: GasReceipt | null - let domainHash: string - let data: string - - let isGasReceipt: boolean = true - const feeTokenInitBalance = BigNumber.from(100000000) - - const feeType = 0 - const feeTokenID = 666 - let feeTokenAddress: string - let feeTokenDataERC1155: string | Uint8Array - - const conditions = [ - [true, 'Gas receipt'], - [false, 'No Gas receipt'] - ] - - conditions.forEach(function(condition) { - context(condition[1] as string, () => { - beforeEach(async () => { - isGasReceipt = condition[0] as boolean - - feeTokenAddress = erc1155Contract.address - - feeTokenDataERC1155 = utils.defaultAbiCoder.encode( - ['address', 'uint256', 'uint8'], - [feeTokenAddress, feeTokenID, feeType] - ) - - // Gas Receipt - gasReceipt = { - gasLimitCallback: 125000, - gasFee: 30000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC1155 - } - - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null - isGasReimbursed = isGasReceipt ? true : false - - // Approval Signture Object - approvalObj = { - contractAddress: erc1155Contract.address, - signerWallet: ownerWallet, - operator: operatorAddress, - approved: approved, - isGasFee: isGasReceipt, - nonce: nonce - } - - // Mint tokens - await erc1155Contract.mintMock(ownerAddress, id, initBalance, []) - - // Mint tokens used to pay for gas - await erc1155Contract.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) - - // Domain hash - domainHash = utils.keccak256( - utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, erc1155Contract.address]) - ) - // Data to pass in approval method - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - }) + describe('metaSetApprovalForAll() function', () => { + const initBalance = 100 + let isGasReimbursed = true + const approved = true + const nonce = BigNumber.from(0) + const id = 66 + + let approvalObj: ApprovalSignature + let gasReceipt: GasReceipt | null + let domainHash: string + let data: string + + let isGasReceipt: boolean = true + const feeTokenInitBalance = BigNumber.from(100000000) + + const feeType = 0 + const feeTokenID = 666 + let feeTokenAddress: string + let feeTokenDataERC1155: string | Uint8Array + + const conditions = [ + [true, 'Gas receipt'], + [false, 'No Gas receipt'] + ] + + conditions.forEach(function(condition) { + context(condition[1] as string, () => { + beforeEach(async () => { + isGasReceipt = condition[0] as boolean - it('should PASS if signature is valid', async () => { - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) - await expect(tx).to.be.fulfilled - }) + feeTokenAddress = erc1155Contract.address - describe('ERC-1271 Receiver', () => { - let erc1271WalletValidationMockContract: ERC1271WalletValidationMock - let ERC1271WalletValidationMockAbstract: AbstractContract + feeTokenDataERC1155 = utils.defaultAbiCoder.encode( + ['address', 'uint256', 'uint8'], + [feeTokenAddress, feeTokenID, feeType] + ) - let erc1271WalletAddress + // Gas Receipt + gasReceipt = { + gasLimitCallback: 125000, + gasFee: 30000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC1155 + } - beforeEach(async () => { - ERC1271WalletValidationMockAbstract = await AbstractContract.fromArtifactName('ERC1271WalletValidationMock') - erc1271WalletValidationMockContract = (await ERC1271WalletValidationMockAbstract.deploy(ownerWallet, [ - domainHash - ])) as ERC1271WalletValidationMock - erc1271WalletAddress = erc1271WalletValidationMockContract.address + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null + isGasReimbursed = isGasReceipt ? true : false + + // Approval Signture Object + approvalObj = { + contractAddress: erc1155Contract.address, + signerWallet: ownerWallet, + operator: operatorAddress, + approved: approved, + isGasFee: isGasReceipt, + nonce: nonce + } - await erc1155Contract.mintMock(erc1271WalletAddress, feeTokenID, feeTokenInitBalance, []) + // Mint tokens + await erc1155Contract.mintMock(ownerAddress, id, initBalance, []) + + // Mint tokens used to pay for gas + await erc1155Contract.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) + + // Domain hash + domainHash = utils.keccak256( + utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, erc1155Contract.address]) + ) + // Data to pass in approval method + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) }) - describe(`EIP-1271 (bytes) signatures (03)`, () => { - it('should return REVERT if signature is invalid', async () => { - approvalObj.owner = erc1271WalletAddress - approvalObj.signerWallet = receiverWallet + it('should PASS if signature is valid', async () => { + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) + await expect(tx).to.be.fulfilled + }) - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt, '03') - const tx = operatorERC1155Contract.metaSetApprovalForAll( - erc1271WalletAddress, - operatorAddress, - approved, - isGasReimbursed, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + describe('ERC-1271 Receiver', () => { + let erc1271WalletValidationMockContract: ERC1271WalletValidationMock + let ERC1271WalletValidationMockAbstract: AbstractContract + + let erc1271WalletAddress + + beforeEach(async () => { + ERC1271WalletValidationMockAbstract = await AbstractContract.fromArtifactName('ERC1271WalletValidationMock') + erc1271WalletValidationMockContract = (await ERC1271WalletValidationMockAbstract.deploy(ownerWallet, [ + domainHash + ])) as ERC1271WalletValidationMock + erc1271WalletAddress = erc1271WalletValidationMockContract.address + + await erc1155Contract.mintMock(erc1271WalletAddress, feeTokenID, feeTokenInitBalance, []) + }) + + describe(`EIP-1271 (bytes) signatures (03)`, () => { + it('should return REVERT if signature is invalid', async () => { + approvalObj.owner = erc1271WalletAddress + approvalObj.signerWallet = receiverWallet + + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt, '03') + const tx = operatorERC1155Contract.metaSetApprovalForAll( + erc1271WalletAddress, + operatorAddress, + approved, + isGasReimbursed, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + }) + }) + + describe(`EIP-1271 (bytes32) signatures (04)`, () => { + it('should return REVERT if signature is invalid', async () => { + approvalObj.owner = erc1271WalletAddress + approvalObj.signerWallet = receiverWallet + + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt, '04') + const tx = operatorERC1155Contract.metaSetApprovalForAll( + erc1271WalletAddress, + operatorAddress, + approved, + isGasReimbursed, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + }) + + it('should PASS if signature is valid', async () => { + approvalObj.owner = erc1271WalletAddress + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt, '04') + const tx = operatorERC1155Contract.metaSetApprovalForAll( + erc1271WalletAddress, + operatorAddress, + approved, + isGasReimbursed, + data + ) + await expect(tx).to.be.fulfilled + }) }) }) - describe(`EIP-1271 (bytes32) signatures (04)`, () => { - it('should return REVERT if signature is invalid', async () => { - approvalObj.owner = erc1271WalletAddress - approvalObj.signerWallet = receiverWallet + describe('When gas is reimbursed', () => { + before(async function() { + if (!condition[0]) { + this.test!.parent!.pending = true + this.skip() + } + }) - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt, '04') - const tx = operatorERC1155Contract.metaSetApprovalForAll( - erc1271WalletAddress, - operatorAddress, - approved, - isGasReimbursed, - data - ) + it('should REVERT if gas receipt is passed, but not claimed', async () => { + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, false, data) await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) }) - it('should PASS if signature is valid', async () => { - approvalObj.owner = erc1271WalletAddress - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt, '04') - const tx = operatorERC1155Contract.metaSetApprovalForAll( - erc1271WalletAddress, - operatorAddress, - approved, - isGasReimbursed, - data - ) - await expect(tx).to.be.fulfilled + it('should REVERT if gas receipt is passed but isGasFee is false', async () => { + approvalObj.isGasFee = false + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, true, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) }) - }) - }) - describe('When gas is reimbursed', () => { - before(async function() { - if (!condition[0]) { - this.test!.parent!.pending = true - this.skip() - } - }) + it('should PASS if gas receipt is passed with isGasFee to false and not claimed', async () => { + approvalObj.isGasFee = false + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - it('should REVERT if gas receipt is passed, but not claimed', async () => { - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, false, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, false, data) + await expect(tx).to.be.fulfilled + }) }) - it('should REVERT if gas receipt is passed but isGasFee is false', async () => { - approvalObj.isGasFee = false - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, true, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) + describe('When gas is NOT reimbursed', () => { + before(async function() { + if (condition[0]) { + this.test!.parent!.pending = true + this.skip() + } + }) - it('should PASS if gas receipt is passed with isGasFee to false and not claimed', async () => { - approvalObj.isGasFee = false - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) + it('should PASS if gas receipt is not passed and not claimed', async () => { + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, false, data) + await expect(tx).to.be.fulfilled + }) - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, false, data) - await expect(tx).to.be.fulfilled - }) - }) + it('should REVER if gas receipt is not passed and claimed', async () => { + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, true, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + }) - describe('When gas is NOT reimbursed', () => { - before(async function() { - if (condition[0]) { - this.test!.parent!.pending = true - this.skip() - } - }) + it('should REVERT if gas receipt is not passed but isGasFee is set to true and is claimed', async () => { + approvalObj.isGasFee = true + data = await encodeMetaApprovalData(approvalObj, domainHash) - it('should PASS if gas receipt is not passed and not claimed', async () => { - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, false, data) - await expect(tx).to.be.fulfilled + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, true, data) + await expect(tx).to.be.rejectedWith(RevertError()) + }) }) - it('should REVER if gas receipt is not passed and claimed', async () => { - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, true, data) + it('should REVERT if contract address is incorrect', async () => { + domainHash = utils.keccak256(utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, receiverAddress])) + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) }) - it('should REVERT if gas receipt is not passed but isGasFee is set to true and is claimed', async () => { - approvalObj.isGasFee = true - data = await encodeMetaApprovalData(approvalObj, domainHash) + it('should REVERT if operator address is incorrect', async () => { + approvalObj.operator = receiverAddress + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, true, data) - await expect(tx).to.be.rejectedWith(RevertError()) + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) }) - }) - - it('should REVERT if contract address is incorrect', async () => { - domainHash = utils.keccak256(utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, receiverAddress])) - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) - it('should REVERT if operator address is incorrect', async () => { - approvalObj.operator = receiverAddress - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) + it('should REVERT if approved value is incorrect', async () => { + approvalObj.approved = false + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) + }) - it('should REVERT if approved value is incorrect', async () => { - approvalObj.approved = false - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) + it('should REVERT if nonce is incorrect', async () => { + approvalObj.nonce = nonce.add(101) + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_SIGNATURE')) - }) + // Nonce higher + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_NONCE')) - it('should REVERT if nonce is incorrect', async () => { - approvalObj.nonce = nonce.add(101) - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - - // Nonce higher - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_NONCE')) - - // Correct nonce - approvalObj.nonce = nonce - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - await operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) - - // Nonce lower - const tx2 = operatorERC1155Contract.metaSetApprovalForAll( - ownerAddress, - operatorAddress, - approved, - isGasReimbursed, - data - ) - await expect(tx2).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_NONCE')) - }) + // Correct nonce + approvalObj.nonce = nonce + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) + await operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) - it('should emit an ApprovalForAll event', async () => { - const tx = await operatorERC1155Contract.metaSetApprovalForAll( - ownerAddress, - operatorAddress, - approved, - isGasReimbursed, - data - ) - const receipt = await tx.wait(1) - - expect(receipt.events![1].event).to.be.eql('ApprovalForAll') - }) + // Nonce lower + const tx2 = operatorERC1155Contract.metaSetApprovalForAll( + ownerAddress, + operatorAddress, + approved, + isGasReimbursed, + data + ) + await expect(tx2).to.be.rejectedWith(RevertError('ERC1155Meta#_signatureValidation: INVALID_NONCE')) + }) - it('should set the operator status to _status argument', async () => { - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) - await expect(tx).to.be.fulfilled + it('should emit an ApprovalForAll event', async () => { + const tx = await operatorERC1155Contract.metaSetApprovalForAll( + ownerAddress, + operatorAddress, + approved, + isGasReimbursed, + data + ) + const receipt = await tx.wait(1) - const status = await erc1155Contract.isApprovedForAll(ownerAddress, operatorAddress) - expect(status).to.be.eql(true) - }) + expect(receipt.events![1].event).to.be.eql('ApprovalForAll') + }) - it('should emit NonceChange event', async () => { - const tx = await operatorERC1155Contract.metaSetApprovalForAll( - ownerAddress, - operatorAddress, - approved, - isGasReimbursed, - data - ) - const receipt = await tx.wait(1) - const ev = receipt.events![0] - expect(ev.event).to.be.eql('NonceChange') - }) + it('should set the operator status to _status argument', async () => { + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) + await expect(tx).to.be.fulfilled - it('should have `_signer` as `signer` in NonceChange', async () => { - const tx = await operatorERC1155Contract.metaSetApprovalForAll( - ownerAddress, - operatorAddress, - approved, - isGasReimbursed, - data - ) - const receipt = await tx.wait(1) - const ev = receipt.events![0] - - const args = ev.args! as any - expect(args.signer).to.be.eql(ownerWallet.address) - }) + const status = await erc1155Contract.isApprovedForAll(ownerAddress, operatorAddress) + expect(status).to.be.eql(true) + }) - it('should have `nonce` as `nonce + 1` in NonceChange', async () => { - const tx = await operatorERC1155Contract.metaSetApprovalForAll( - ownerAddress, - operatorAddress, - approved, - isGasReimbursed, - data - ) - const receipt = await tx.wait(1) - const ev = receipt.events![0] - - const args = ev.args! as any - expect(args.newNonce).to.be.eql(nonce.add(1)) - }) + it('should emit NonceChange event', async () => { + const tx = await operatorERC1155Contract.metaSetApprovalForAll( + ownerAddress, + operatorAddress, + approved, + isGasReimbursed, + data + ) + const receipt = await tx.wait(1) + const ev = receipt.events![0] + expect(ev.event).to.be.eql('NonceChange') + }) - context('When the operator was already an operator', () => { - beforeEach(async () => { + it('should have `_signer` as `signer` in NonceChange', async () => { const tx = await operatorERC1155Contract.metaSetApprovalForAll( ownerAddress, operatorAddress, @@ -2214,36 +2195,68 @@ describe('ERC1155Meta', () => { isGasReimbursed, data ) + const receipt = await tx.wait(1) + const ev = receipt.events![0] - // Update nonce of approval signature object for subsequent tests - approvalObj.nonce = nonce.add(1) + const args = ev.args! as any + expect(args.signer).to.be.eql(ownerWallet.address) }) - it('should leave the operator status to set to true again', async () => { - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSetApprovalForAll( + it('should have `nonce` as `nonce + 1` in NonceChange', async () => { + const tx = await operatorERC1155Contract.metaSetApprovalForAll( ownerAddress, operatorAddress, approved, isGasReimbursed, data ) - await expect(tx).to.be.fulfilled + const receipt = await tx.wait(1) + const ev = receipt.events![0] - const status = await erc1155Contract.isApprovedForAll(ownerAddress, operatorAddress) - expect(status).to.be.eql(true) + const args = ev.args! as any + expect(args.newNonce).to.be.eql(nonce.add(1)) }) - it('should allow the operator status to be set to false', async () => { - approvalObj.approved = false - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) + context('When the operator was already an operator', () => { + beforeEach(async () => { + const tx = await operatorERC1155Contract.metaSetApprovalForAll( + ownerAddress, + operatorAddress, + approved, + isGasReimbursed, + data + ) - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, false, isGasReimbursed, data) - await expect(tx).to.be.fulfilled + // Update nonce of approval signature object for subsequent tests + approvalObj.nonce = nonce.add(1) + }) - const status = await erc1155Contract.isApprovedForAll(operatorAddress, ownerAddress) - expect(status).to.be.eql(false) + it('should leave the operator status to set to true again', async () => { + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSetApprovalForAll( + ownerAddress, + operatorAddress, + approved, + isGasReimbursed, + data + ) + await expect(tx).to.be.fulfilled + + const status = await erc1155Contract.isApprovedForAll(ownerAddress, operatorAddress) + expect(status).to.be.eql(true) + }) + + it('should allow the operator status to be set to false', async () => { + approvalObj.approved = false + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, false, isGasReimbursed, data) + await expect(tx).to.be.fulfilled + + const status = await erc1155Contract.isApprovedForAll(operatorAddress, ownerAddress) + expect(status).to.be.eql(false) + }) }) }) }) diff --git a/tests/ERC1155MetaPackedBalance.spec.ts b/tests/ERC1155MetaPackedBalance.spec.ts index a85ffdd..196021f 100644 --- a/tests/ERC1155MetaPackedBalance.spec.ts +++ b/tests/ERC1155MetaPackedBalance.spec.ts @@ -1,4 +1,4 @@ -import { ethers } from 'ethers' +import { constants, ethers } from 'ethers' import { AbstractContract, @@ -22,7 +22,8 @@ import { ERC1271WalletValidationMock, ERC1155ReceiverMock, ERC1155OperatorMock, - ERC20Mock + ERC20Mock, + ProxyUpgradeableDeployerMock } from 'src' import { GasReceipt, TransferSignature, ApprovalSignature, BatchTransferSignature } from 'src/typings/tx-types' @@ -30,7 +31,7 @@ import { GasReceipt, TransferSignature, ApprovalSignature, BatchTransferSignatur // init test wallets from package.json mnemonic import { web3 } from 'hardhat' -const { wallet: ownerWallet, provider: ownerProvider, signer: ownerSigner } = createTestWallet(web3, 0) +const { wallet: ownerWallet, provider: ownerProvider } = createTestWallet(web3, 0) const { wallet: receiverWallet, provider: receiverProvider, signer: receiverSigner } = createTestWallet(web3, 2) @@ -41,882 +42,701 @@ ownerProvider.pollingInterval = 1000 operatorProvider.pollingInterval = 1000 receiverProvider.pollingInterval = 1000 -describe('ERC1155MetaPackedBalance', () => { - const MAXVAL = BigNumber.from(2) - .pow(32) - .sub(1) // 2**32 - 1 - const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' - const DOMAIN_SEPARATOR_TYPEHASH = '0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749' - - const NAME = 'MyERC1155' - const METADATA_URI = 'https://example.com/' - - let ownerAddress: string - let receiverAddress: string - let operatorAddress: string - let erc1155Abstract: AbstractContract - let operatorAbstract: AbstractContract - - let erc1155Contract: ERC1155MetaMintBurnPackedBalanceMock - let operatorERC1155Contract: ERC1155MetaMintBurnPackedBalanceMock - let receiverERC1155Contract: ERC1155MetaMintBurnPackedBalanceMock - - // load contract abi and deploy to test server - before(async () => { - ownerAddress = await ownerWallet.getAddress() - receiverAddress = await receiverWallet.getAddress() - operatorAddress = await operatorWallet.getAddress() - - erc1155Abstract = await AbstractContract.fromArtifactName('ERC1155MetaMintBurnPackedBalanceMock') - operatorAbstract = await AbstractContract.fromArtifactName('ERC1155OperatorMock') - }) - - // deploy before each test, to reset state of contract - beforeEach(async () => { - erc1155Contract = (await erc1155Abstract.deploy(ownerWallet, [NAME, METADATA_URI])) as ERC1155MetaMintBurnPackedBalanceMock - operatorERC1155Contract = (await erc1155Contract.connect(operatorSigner)) as ERC1155MetaMintBurnPackedBalanceMock - receiverERC1155Contract = (await erc1155Contract.connect(receiverSigner)) as ERC1155MetaMintBurnPackedBalanceMock - }) - - describe('metaSafeTransferFrom() (Meta) Function', () => { - let receiverContract: ERC1155ReceiverMock - let operatorContract: ERC1155OperatorMock - - let transferData: string | null = 'Hello from the other side' - const initBalance = 100 - const amount = 10 - const nonce = BigNumber.from(0) - const id = 66 - - const feeTokenID = 666 - let isGasReceipt: boolean = true - const feeTokenInitBalance = BigNumber.from(100000000) - - const feeType = 0 //ERC-11555 - let feeToken: BigNumber - let feeTokenAddress: string - let feeTokenDataERC1155: string | Uint8Array - - let transferObj: TransferSignature - let domainHash: string - let gasReceipt: GasReceipt | null - let data: string - - const conditions = [ - [transferData, true, 'Gas receipt & transfer data'], - [null, true, 'Gas receipt w/o transfer data'], - [transferData, false, 'Transfer data w/o gas receipt '], - [null, false, 'No Gas receipt & No transfer data'] - ] - - conditions.forEach(function(condition) { - context(condition[2] as string, () => { - beforeEach(async () => { - // Get conditions - transferData = condition[0] as string | null - isGasReceipt = condition[1] as boolean - - // Deploy contracts - const abstract = await AbstractContract.fromArtifactName('ERC1155ReceiverMock') - receiverContract = (await abstract.deploy(ownerWallet)) as ERC1155ReceiverMock - operatorContract = (await operatorAbstract.deploy(operatorWallet)) as ERC1155OperatorMock - - feeTokenAddress = erc1155Contract.address - - feeTokenDataERC1155 = ethers.utils.defaultAbiCoder.encode( - ['address', 'uint256', 'uint8'], - [feeTokenAddress, feeTokenID, feeType] - ) - - // Gas Receipt - gasReceipt = { - gasLimitCallback: 150000, - gasFee: 30000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC1155 - } - - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null - - // Transfer Signature Object - transferObj = { - contractAddress: erc1155Contract.address, - signerWallet: ownerWallet, - receiver: receiverAddress, - id: id, - amount: amount, - isGasFee: isGasReceipt, - transferData: transferData === null ? null : utils.toUtf8Bytes(transferData), - nonce: nonce - } - - // Mint tokens - await erc1155Contract.mintMock(ownerAddress, id, initBalance, []) - - // Mint tokens used to pay for gas - await erc1155Contract.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) - - // Get domain hash - domainHash = ethers.utils.keccak256( - ethers.utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, erc1155Contract.address]) - ) - - // Data to pass in transfer method - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - }) - - it("should REVERT if data is 'random", async () => { - const dataUint8 = utils.toUtf8Bytes('Breakthroughs! over the river! flips and crucifixions! gone down the flood!') - const data = BigNumber.from(dataUint8).toHexString() - - // Check if data lelngth is more than 69 - expect(ethers.utils.arrayify(data).length).to.be.at.least(70) - - const tx = erc1155Contract.metaSafeTransferFrom(ownerAddress, receiverContract.address, id, amount, isGasReceipt, data) - await expect(tx).to.be.rejectedWith(RevertError()) - }) - - it('should REVERT if contract address is incorrect', async () => { - // Domain hash - domainHash = ethers.utils.keccak256( - ethers.utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, receiverContract.address]) - ) - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) - - it('should REVERT if signer address is incorrect', async () => { - transferObj.signerWallet = operatorWallet - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) - - it('should REVERT if receiver address is incorrect', async () => { - transferObj.receiver = ownerAddress - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) +const usingUpgradeable = [false, true] + +usingUpgradeable.forEach(upgradeable => { + describe('ERC1155MetaPackedBalance' + (upgradeable ? 'Upgradeable': ''), () => { + const MAXVAL = BigNumber.from(2) + .pow(32) + .sub(1) // 2**32 - 1 + const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' + const DOMAIN_SEPARATOR_TYPEHASH = '0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749' + + const NAME = 'MyERC1155' + const METADATA_URI = 'https://example.com/' + + let ownerAddress: string + let receiverAddress: string + let operatorAddress: string + let erc1155Abstract: AbstractContract + let operatorAbstract: AbstractContract + + let erc1155Contract: ERC1155MetaMintBurnPackedBalanceMock + let operatorERC1155Contract: ERC1155MetaMintBurnPackedBalanceMock + let receiverERC1155Contract: ERC1155MetaMintBurnPackedBalanceMock + let factoryContract: ProxyUpgradeableDeployerMock + + // load contract abi and deploy to test server + before(async () => { + ownerAddress = await ownerWallet.getAddress() + receiverAddress = await receiverWallet.getAddress() + operatorAddress = await operatorWallet.getAddress() + + if (upgradeable) { + erc1155Abstract = await AbstractContract.fromArtifactName('ERC1155MetaMintBurnPackedBalanceUpgradeableMock') + // Create factory + const factoryAbstract = await AbstractContract.fromArtifactName('ProxyUpgradeableDeployerMock') + factoryContract = (await factoryAbstract.deploy(ownerWallet, [])) as ProxyUpgradeableDeployerMock + } else { + erc1155Abstract = await AbstractContract.fromArtifactName('ERC1155MetaMintBurnPackedBalanceMock') + } + operatorAbstract = await AbstractContract.fromArtifactName('ERC1155OperatorMock') + }) - it('should REVERT if token id is incorrect', async () => { - transferObj.id = id + 1 - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + const deployERC1155Contract = async () => { + let contract: ERC1155MetaMintBurnPackedBalanceMock + if (upgradeable) { + contract = (await erc1155Abstract.deploy(ownerWallet, [])) as ERC1155MetaMintBurnPackedBalanceMock + + // Create proxy + let tx = factoryContract.createProxy(contract.address, constants.HashZero, ownerWallet.address); + await expect(tx).to.be.fulfilled + const proxyAddr = await factoryContract.predictProxyAddress(contract.address, constants.HashZero, ownerWallet.address); + contract = (await erc1155Abstract.connect(ownerWallet, proxyAddr)) as ERC1155MetaMintBurnPackedBalanceMock; + tx = contract.initialize(NAME, METADATA_URI) + await expect(tx).to.be.fulfilled + } else { + contract = (await erc1155Abstract.deploy(ownerWallet, [NAME, METADATA_URI])) as ERC1155MetaMintBurnPackedBalanceMock + } + return contract + } + + // deploy before each test, to reset state of contract + beforeEach(async () => { + erc1155Contract = await deployERC1155Contract() + operatorERC1155Contract = (await erc1155Contract.connect(operatorSigner)) as ERC1155MetaMintBurnPackedBalanceMock + receiverERC1155Contract = (await erc1155Contract.connect(receiverSigner)) as ERC1155MetaMintBurnPackedBalanceMock + }) - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) + describe('metaSafeTransferFrom() (Meta) Function', () => { + let receiverContract: ERC1155ReceiverMock + let operatorContract: ERC1155OperatorMock + + let transferData: string | null = 'Hello from the other side' + const initBalance = 100 + const amount = 10 + const nonce = BigNumber.from(0) + const id = 66 + + const feeTokenID = 666 + let isGasReceipt: boolean = true + const feeTokenInitBalance = BigNumber.from(100000000) + + const feeType = 0 //ERC-11555 + let feeTokenAddress: string + let feeTokenDataERC1155: string | Uint8Array + + let transferObj: TransferSignature + let domainHash: string + let gasReceipt: GasReceipt | null + let data: string + + const conditions = [ + [transferData, true, 'Gas receipt & transfer data'], + [null, true, 'Gas receipt w/o transfer data'], + [transferData, false, 'Transfer data w/o gas receipt '], + [null, false, 'No Gas receipt & No transfer data'] + ] + + conditions.forEach(function(condition) { + context(condition[2] as string, () => { + beforeEach(async () => { + // Get conditions + transferData = condition[0] as string | null + isGasReceipt = condition[1] as boolean - it('should REVERT if token amount is incorrect', async () => { - transferObj.amount = amount + 1 - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + // Deploy contracts + const abstract = await AbstractContract.fromArtifactName('ERC1155ReceiverMock') + receiverContract = (await abstract.deploy(ownerWallet)) as ERC1155ReceiverMock + operatorContract = (await operatorAbstract.deploy(operatorWallet)) as ERC1155OperatorMock - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) + feeTokenAddress = erc1155Contract.address - it('should REVERT if transfer data is incorrect', async () => { - const sigArgTypes = ['address', 'address', 'address', 'uint256', 'uint256', 'uint256'] - const txDataTypes = ['bytes', 'bytes'] - - const signer = await transferObj.signerWallet.getAddress() - - // Packed encoding of transfer signature message - let sigData = ethers.utils.solidityPack(sigArgTypes, [ - transferObj.contractAddress, - signer, - transferObj.receiver, - transferObj.id, - transferObj.amount, - transferObj.nonce - ]) - - const transferData = transferObj.transferData == null ? utils.toUtf8Bytes('') : transferObj.transferData - let goodGasAndTransferData - let badGasAndTransferData - - // Correct and incorrect transferData - if (isGasReceipt) { - goodGasAndTransferData = ethers.utils.defaultAbiCoder.encode([GasReceiptType, 'bytes'], [gasReceipt, transferData]) - badGasAndTransferData = ethers.utils.defaultAbiCoder.encode( - [GasReceiptType, 'bytes'], - [gasReceipt, utils.toUtf8Bytes('Goodbyebyebye')] + feeTokenDataERC1155 = ethers.utils.defaultAbiCoder.encode( + ['address', 'uint256', 'uint8'], + [feeTokenAddress, feeTokenID, feeType] ) - } else { - goodGasAndTransferData = ethers.utils.defaultAbiCoder.encode(['bytes'], [transferData]) - badGasAndTransferData = ethers.utils.defaultAbiCoder.encode(['bytes'], [utils.toUtf8Bytes('Goodbyebyebye')]) - } - - // Encode normally the whole thing - sigData = ethers.utils.solidityPack(['bytes', 'bytes'], [sigData, goodGasAndTransferData]) - - // Get signature - const sig = (await ethSign(transferObj.signerWallet, sigData)).slice(0, -2) - const paddedNonce = ethers.utils.solidityPack(['uint256'], [transferObj.nonce]) - const ethsig_nonce = sig + paddedNonce.slice(2) + '02' // encode packed the nonce - - // PASS BAD DATA - data = ethers.utils.defaultAbiCoder.encode(txDataTypes, [ethsig_nonce, badGasAndTransferData]) - - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) - - it('should REVERT if nonce is incorrect', async () => { - transferObj.nonce = nonce.add(101) - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - - // Nonce higher - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_NONCE')) - - // Correct nonce - transferObj.nonce = nonce - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - await operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - - // Nonce lower - const tx2 = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - await expect(tx2).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_NONCE')) - }) - - it('should PASS if signature is valid', async () => { - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - await expect(tx).to.be.fulfilled - }) - - describe('ERC-1271 Receiver', () => { - let erc1271WalletValidationMockContract: ERC1271WalletValidationMock - let ERC1271WalletValidationMockAbstract: AbstractContract - let erc1271WalletAddress - - beforeEach(async () => { - ERC1271WalletValidationMockAbstract = await AbstractContract.fromArtifactName('ERC1271WalletValidationMock') - erc1271WalletValidationMockContract = (await ERC1271WalletValidationMockAbstract.deploy(ownerWallet, [ - domainHash - ])) as ERC1271WalletValidationMock - erc1271WalletAddress = erc1271WalletValidationMockContract.address - - await erc1155Contract.mintMock(erc1271WalletAddress, id, initBalance, []) - await erc1155Contract.mintMock(erc1271WalletAddress, feeTokenID, feeTokenInitBalance, []) - }) + // Gas Receipt + gasReceipt = { + gasLimitCallback: 150000, + gasFee: 30000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC1155 + } - describe(`EIP-1271 (bytes) signatures (03)`, () => { - it('should return REVERT if signature is invalid', async () => { - transferObj.from = erc1271WalletAddress - transferObj.signerWallet = receiverWallet + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null + + // Transfer Signature Object + transferObj = { + contractAddress: erc1155Contract.address, + signerWallet: ownerWallet, + receiver: receiverAddress, + id: id, + amount: amount, + isGasFee: isGasReceipt, + transferData: transferData === null ? null : utils.toUtf8Bytes(transferData), + nonce: nonce + } - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '03') - const tx = operatorERC1155Contract.metaSafeTransferFrom( - erc1271WalletAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data, - HIGH_GAS_LIMIT - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) + // Mint tokens + await erc1155Contract.mintMock(ownerAddress, id, initBalance, []) - it('should REVERT if token ID is not 66', async () => { - const badID = 77 - await erc1155Contract.mintMock(erc1271WalletAddress, badID, initBalance, []) - transferObj.from = erc1271WalletAddress - transferObj.id = badID - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '03') - const tx = operatorERC1155Contract.metaSafeTransferFrom( - erc1271WalletAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data, - HIGH_GAS_LIMIT - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) + // Mint tokens used to pay for gas + await erc1155Contract.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) - it('should REVERT if amount is more than 100', async () => { - await erc1155Contract.mintMock(erc1271WalletAddress, id, 101, []) - transferObj.from = erc1271WalletAddress - transferObj.amount = 101 - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '03') - const tx = operatorERC1155Contract.metaSafeTransferFrom( - erc1271WalletAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data, - HIGH_GAS_LIMIT - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) + // Get domain hash + domainHash = ethers.utils.keccak256( + ethers.utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, erc1155Contract.address]) + ) - it('should PASS if signature is valid', async () => { - transferObj.from = erc1271WalletAddress - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '03') - const tx = operatorERC1155Contract.metaSafeTransferFrom( - erc1271WalletAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data, - HIGH_GAS_LIMIT - ) - await expect(tx).to.be.fulfilled - }) + // Data to pass in transfer method + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) }) - describe(`EIP-1271 (bytes32) signatures (04)`, () => { - it('should return REVERT if signature is invalid', async () => { - transferObj.from = erc1271WalletAddress - transferObj.signerWallet = receiverWallet + it("should REVERT if data is 'random", async () => { + const dataUint8 = utils.toUtf8Bytes('Breakthroughs! over the river! flips and crucifixions! gone down the flood!') + const data = BigNumber.from(dataUint8).toHexString() - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '04') - const tx = operatorERC1155Contract.metaSafeTransferFrom( - erc1271WalletAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data, - HIGH_GAS_LIMIT - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) + // Check if data lelngth is more than 69 + expect(ethers.utils.arrayify(data).length).to.be.at.least(70) - it('should PASS if signature is valid', async () => { - transferObj.from = erc1271WalletAddress - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '04') - const tx = operatorERC1155Contract.metaSafeTransferFrom( - erc1271WalletAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data, - HIGH_GAS_LIMIT - ) - await expect(tx).to.be.fulfilled - }) + const tx = erc1155Contract.metaSafeTransferFrom(ownerAddress, receiverContract.address, id, amount, isGasReceipt, data) + await expect(tx).to.be.rejectedWith(RevertError()) }) - }) - describe('When signature is valid', () => { - it('should REVERT if insufficient balance', async () => { - transferObj.amount = initBalance + 1 + it('should REVERT if contract address is incorrect', async () => { + // Domain hash + domainHash = ethers.utils.keccak256( + ethers.utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, receiverContract.address]) + ) data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSafeTransferFrom( - ownerAddress, - receiverAddress, - id, - initBalance + 1, - isGasReceipt, - data, - HIGH_GAS_LIMIT - ) - await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - it('should REVERT if sending to 0x0', async () => { - transferObj.receiver = ZERO_ADDRESS + it('should REVERT if signer address is incorrect', async () => { + transferObj.signerWallet = operatorWallet data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, ZERO_ADDRESS, id, amount, isGasReceipt, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#metaSafeTransferFrom: INVALID_RECIPIENT')) - }) - - it('should REVERT if transfer leads to overflow', async () => { - await erc1155Contract.mintMock(receiverAddress, id, MAXVAL, []) const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: OVERFLOW')) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - it('should REVERT when sending to non-receiver contract', async () => { - transferObj.receiver = erc1155Contract.address + it('should REVERT if receiver address is incorrect', async () => { + transferObj.receiver = ownerAddress data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSafeTransferFrom( - ownerAddress, - erc1155Contract.address, - id, - amount, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - it('should REVERT if invalid response from receiver contract', async () => { - transferObj.receiver = receiverContract.address - if (gasReceipt) { - gasReceipt.gasLimitCallback = HIGH_GAS_LIMIT.gasLimit - } + it('should REVERT if token id is incorrect', async () => { + transferObj.id = id + 1 data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - // Force invalid response - await receiverContract.setShouldReject(true) - - const tx = operatorERC1155Contract.metaSafeTransferFrom( - ownerAddress, - receiverContract.address, - id, - amount, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith( - RevertError('ERC1155PackedBalance#_callonERC1155Received: INVALID_ON_RECEIVE_MESSAGE') - ) + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - it('should PASS if valid response from receiver contract', async () => { - transferObj.receiver = receiverContract.address - if (gasReceipt) { - gasReceipt.gasLimitCallback = HIGH_GAS_LIMIT.gasLimit - } + it('should REVERT if token amount is incorrect', async () => { + transferObj.amount = amount + 1 data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSafeTransferFrom( - ownerAddress, - receiverContract.address, - id, - amount, - isGasReceipt, - data - ) - - //await expect(tx).to.be.fulfilled - await expect(tx).to.be.fulfilled + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - describe('When gas is reimbursed', () => { - before(async function() { - if (!condition[1]) { - this.test!.parent!.pending = true - this.skip() - } - }) + it('should REVERT if transfer data is incorrect', async () => { + const sigArgTypes = ['address', 'address', 'address', 'uint256', 'uint256', 'uint256'] + const txDataTypes = ['bytes', 'bytes'] + + const signer = await transferObj.signerWallet.getAddress() + + // Packed encoding of transfer signature message + let sigData = ethers.utils.solidityPack(sigArgTypes, [ + transferObj.contractAddress, + signer, + transferObj.receiver, + transferObj.id, + transferObj.amount, + transferObj.nonce + ]) + + const transferData = transferObj.transferData == null ? utils.toUtf8Bytes('') : transferObj.transferData + let goodGasAndTransferData + let badGasAndTransferData + + // Correct and incorrect transferData + if (isGasReceipt) { + goodGasAndTransferData = ethers.utils.defaultAbiCoder.encode([GasReceiptType, 'bytes'], [gasReceipt, transferData]) + badGasAndTransferData = ethers.utils.defaultAbiCoder.encode( + [GasReceiptType, 'bytes'], + [gasReceipt, utils.toUtf8Bytes('Goodbyebyebye')] + ) + } else { + goodGasAndTransferData = ethers.utils.defaultAbiCoder.encode(['bytes'], [transferData]) + badGasAndTransferData = ethers.utils.defaultAbiCoder.encode(['bytes'], [utils.toUtf8Bytes('Goodbyebyebye')]) + } - it('should send gas fee to msg.sender is fee recipient ix 0x0', async () => { - gasReceipt!.feeRecipient = ZERO_ADDRESS + // Encode normally the whole thing + sigData = ethers.utils.solidityPack(['bytes', 'bytes'], [sigData, goodGasAndTransferData]) - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + // Get signature + const sig = (await ethSign(transferObj.signerWallet, sigData)).slice(0, -2) + const paddedNonce = ethers.utils.solidityPack(['uint256'], [transferObj.nonce]) + const ethsig_nonce = sig + paddedNonce.slice(2) + '02' // encode packed the nonce - await receiverERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + // PASS BAD DATA + data = ethers.utils.defaultAbiCoder.encode(txDataTypes, [ethsig_nonce, badGasAndTransferData]) - const receiverBalance = await erc1155Contract.balanceOf(receiverAddress, feeTokenID) + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) + }) - expect(gasReceipt!.gasFee).to.be.eql(receiverBalance.toNumber()) - }) + it('should REVERT if nonce is incorrect', async () => { + transferObj.nonce = nonce.add(101) + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - it('should send gas fee to specified fee recipient (if not 0x0), not tx.origin', async () => { - await receiverERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - const operatorBalance = await erc1155Contract.balanceOf(operatorAddress, feeTokenID) + // Nonce higher + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_NONCE')) - expect(gasReceipt!.gasFee).to.be.eql(operatorBalance.toNumber()) - }) + // Correct nonce + transferObj.nonce = nonce + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + await operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - it('should REVERT if gasReceipt is incorrect', async () => { - const sigArgTypes = ['address', 'address', 'address', 'uint256', 'uint256', 'uint256'] - const txDataTypes = ['bytes', 'bytes'] + // Nonce lower + const tx2 = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + await expect(tx2).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_NONCE')) + }) - const signer = await transferObj.signerWallet.getAddress() + it('should PASS if signature is valid', async () => { + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + await expect(tx).to.be.fulfilled + }) - // Packed encoding of transfer signature message - let sigData = ethers.utils.solidityPack(sigArgTypes, [ - transferObj.contractAddress, - signer, - transferObj.receiver, - transferObj.id, - transferObj.amount, - transferObj.nonce - ]) + describe('ERC-1271 Receiver', () => { + let erc1271WalletValidationMockContract: ERC1271WalletValidationMock + let ERC1271WalletValidationMockAbstract: AbstractContract - // Form bad gas receipt - const badGasReceipt = { ...gasReceipt, gasPrice: 109284123 } + let erc1271WalletAddress - const transferData = transferObj.transferData == null ? utils.toUtf8Bytes('') : transferObj.transferData + beforeEach(async () => { + ERC1271WalletValidationMockAbstract = await AbstractContract.fromArtifactName('ERC1271WalletValidationMock') + erc1271WalletValidationMockContract = (await ERC1271WalletValidationMockAbstract.deploy(ownerWallet, [ + domainHash + ])) as ERC1271WalletValidationMock + erc1271WalletAddress = erc1271WalletValidationMockContract.address + + await erc1155Contract.mintMock(erc1271WalletAddress, id, initBalance, []) + await erc1155Contract.mintMock(erc1271WalletAddress, feeTokenID, feeTokenInitBalance, []) + }) - // Correct and incorrect transferData - const goodGasAndTransferData = ethers.utils.defaultAbiCoder.encode( - [GasReceiptType, 'bytes'], - [gasReceipt, transferData] - ) - const badGasAndTransferData = ethers.utils.defaultAbiCoder.encode( - [GasReceiptType, 'bytes'], - [badGasReceipt, transferData] - ) + describe(`EIP-1271 (bytes) signatures (03)`, () => { + it('should return REVERT if signature is invalid', async () => { + transferObj.from = erc1271WalletAddress + transferObj.signerWallet = receiverWallet - // Encode normally the whole thing - sigData = ethers.utils.solidityPack(['bytes', 'bytes'], [sigData, goodGasAndTransferData]) + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '03') + const tx = operatorERC1155Contract.metaSafeTransferFrom( + erc1271WalletAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + HIGH_GAS_LIMIT + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) + }) - // Get signature - const sig = (await ethSign(transferObj.signerWallet, sigData)).slice(0, -2) - const paddedNonce = ethers.utils.solidityPack(['uint256'], [transferObj.nonce]) - const ethsig_nonce = sig + paddedNonce.slice(2) + '02' // encode packed the nonce + it('should REVERT if token ID is not 66', async () => { + const badID = 77 + await erc1155Contract.mintMock(erc1271WalletAddress, badID, initBalance, []) + transferObj.from = erc1271WalletAddress + transferObj.id = badID + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '03') + const tx = operatorERC1155Contract.metaSafeTransferFrom( + erc1271WalletAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + HIGH_GAS_LIMIT + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) + }) - // PASS BAD DATA - data = ethers.utils.defaultAbiCoder.encode(txDataTypes, [ethsig_nonce, badGasAndTransferData]) + it('should REVERT if amount is more than 100', async () => { + await erc1155Contract.mintMock(erc1271WalletAddress, id, 101, []) + transferObj.from = erc1271WalletAddress + transferObj.amount = 101 + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '03') + const tx = operatorERC1155Contract.metaSafeTransferFrom( + erc1271WalletAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + HIGH_GAS_LIMIT + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) + }) - const tx = operatorERC1155Contract.metaSafeTransferFrom( - ownerAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) + it('should PASS if signature is valid', async () => { + transferObj.from = erc1271WalletAddress + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '03') + const tx = operatorERC1155Contract.metaSafeTransferFrom( + erc1271WalletAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + HIGH_GAS_LIMIT + ) + await expect(tx).to.be.fulfilled + }) }) - it('should PASS if another approved ERC-1155 is used for fee', async () => { - const erc1155Contract2 = (await erc1155Abstract.deploy(ownerWallet, [ - NAME, - METADATA_URI - ])) as ERC1155MetaMintBurnPackedBalanceMock - await erc1155Contract2.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) - await erc1155Contract2.setApprovalForAll(operatorERC1155Contract.address, true) - - feeTokenDataERC1155 = ethers.utils.defaultAbiCoder.encode( - ['address', 'uint256', 'uint8'], - [erc1155Contract2.address, feeTokenID, 0] - ) + describe(`EIP-1271 (bytes32) signatures (04)`, () => { + it('should return REVERT if signature is invalid', async () => { + transferObj.from = erc1271WalletAddress + transferObj.signerWallet = receiverWallet - gasReceipt = { - gasLimitCallback: 130000, - gasFee: 1000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC1155 - } + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '04') + const tx = operatorERC1155Contract.metaSafeTransferFrom( + erc1271WalletAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + HIGH_GAS_LIMIT + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) + }) - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null + it('should PASS if signature is valid', async () => { + transferObj.from = erc1271WalletAddress + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt, '04') + const tx = operatorERC1155Contract.metaSafeTransferFrom( + erc1271WalletAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + HIGH_GAS_LIMIT + ) + await expect(tx).to.be.fulfilled + }) + }) + }) - // Data to pass in transfer method + describe('When signature is valid', () => { + it('should REVERT if insufficient balance', async () => { + transferObj.amount = initBalance + 1 data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeTransferFrom( ownerAddress, receiverAddress, id, - amount, + initBalance + 1, isGasReceipt, data, HIGH_GAS_LIMIT ) - await expect(tx).to.be.fulfilled + await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) }) - it('should REVERT if NOT approved ERC-1155 is used for fee', async () => { - const erc1155Contract2 = (await erc1155Abstract.deploy(ownerWallet, [ - NAME, - METADATA_URI - ])) as ERC1155MetaMintBurnPackedBalanceMock - await erc1155Contract2.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) - - feeTokenDataERC1155 = ethers.utils.defaultAbiCoder.encode( - ['address', 'uint256', 'uint8'], - [erc1155Contract2.address, feeTokenID, 0] - ) + it('should REVERT if sending to 0x0', async () => { + transferObj.receiver = ZERO_ADDRESS + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - gasReceipt = { - gasLimitCallback: 130000, - gasFee: 1000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC1155 - } + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, ZERO_ADDRESS, id, amount, isGasReceipt, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#metaSafeTransferFrom: INVALID_RECIPIENT')) + }) - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null + it('should REVERT if transfer leads to overflow', async () => { + await erc1155Contract.mintMock(receiverAddress, id, MAXVAL, []) + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: OVERFLOW')) + }) - // Data to pass in transfer method + it('should REVERT when sending to non-receiver contract', async () => { + transferObj.receiver = erc1155Contract.address data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeTransferFrom( ownerAddress, - receiverAddress, + erc1155Contract.address, id, amount, isGasReceipt, - data, - HIGH_GAS_LIMIT + data ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#safeTransferFrom: INVALID_OPERATOR')) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) }) - it('should REVERT if another ERC-1155 is used for fee without sufficient balance', async () => { - const erc1155Contract2 = (await erc1155Abstract.deploy(ownerWallet, [ - NAME, - METADATA_URI - ])) as ERC1155MetaMintBurnPackedBalanceMock - await erc1155Contract2.mintMock(ownerAddress, feeTokenID, 100, []) - await erc1155Contract2.setApprovalForAll(operatorERC1155Contract.address, true) - - feeTokenDataERC1155 = ethers.utils.defaultAbiCoder.encode( - ['address', 'uint256', 'uint8'], - [erc1155Contract2.address, feeTokenID, 0] - ) - - gasReceipt = { - gasLimitCallback: 130000, - gasFee: 1000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC1155 + it('should REVERT if invalid response from receiver contract', async () => { + transferObj.receiver = receiverContract.address + if (gasReceipt) { + gasReceipt.gasLimitCallback = HIGH_GAS_LIMIT.gasLimit } - - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null - - // Data to pass in transfer method data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + // Force invalid response + await receiverContract.setShouldReject(true) + const tx = operatorERC1155Contract.metaSafeTransferFrom( ownerAddress, - receiverAddress, + receiverContract.address, id, amount, isGasReceipt, - data, - HIGH_GAS_LIMIT + data + ) + await expect(tx).to.be.rejectedWith( + RevertError('ERC1155PackedBalance#_callonERC1155Received: INVALID_ON_RECEIVE_MESSAGE') ) - await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) }) - it('should PASS if approved ERC20 is used for fee', async () => { - const erc20Abstract = await AbstractContract.fromArtifactName('ERC20Mock') - const erc20Contract = (await erc20Abstract.deploy(ownerWallet)) as ERC20Mock - await erc20Contract.mockMint(ownerAddress, feeTokenInitBalance) - await erc20Contract.approve(operatorERC1155Contract.address, feeTokenInitBalance) - - const feeTokenDataERC20 = ethers.utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 1]) - - gasReceipt = { - gasLimitCallback: 130000, - gasFee: 1000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC20 + it('should PASS if valid response from receiver contract', async () => { + transferObj.receiver = receiverContract.address + if (gasReceipt) { + gasReceipt.gasLimitCallback = HIGH_GAS_LIMIT.gasLimit } - - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null - - // Data to pass in transfer method data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeTransferFrom( ownerAddress, - receiverAddress, + receiverContract.address, id, amount, isGasReceipt, - data, - HIGH_GAS_LIMIT + data ) + + //await expect(tx).to.be.fulfilled await expect(tx).to.be.fulfilled }) - it('should REVERT if NOT approved ERC20 is used for fee', async () => { - const erc20Abstract = await AbstractContract.fromArtifactName('ERC20Mock') - const erc20Contract = (await erc20Abstract.deploy(ownerWallet)) as ERC20Mock - await erc20Contract.mockMint(ownerAddress, feeTokenInitBalance) + describe('When gas is reimbursed', () => { + before(async function() { + if (!condition[1]) { + this.test!.parent!.pending = true + this.skip() + } + }) - const feeTokenDataERC20 = ethers.utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 1]) + it('should send gas fee to msg.sender is fee recipient ix 0x0', async () => { + gasReceipt!.feeRecipient = ZERO_ADDRESS - gasReceipt = { - gasLimitCallback: 130000, - gasFee: 1000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC20 - } + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null + await receiverERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) - // Data to pass in transfer method - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + const receiverBalance = await erc1155Contract.balanceOf(receiverAddress, feeTokenID) - const tx = operatorERC1155Contract.metaSafeTransferFrom( - ownerAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data, - HIGH_GAS_LIMIT - ) - await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) - }) + expect(gasReceipt!.gasFee).to.be.eql(receiverBalance.toNumber()) + }) - it('should REVERT if approved ERC20 balance is insufficient', async () => { - const erc20Abstract = await AbstractContract.fromArtifactName('ERC20Mock') - const erc20Contract = (await erc20Abstract.deploy(ownerWallet)) as ERC20Mock - await erc20Contract.mockMint(ownerAddress, 100) + it('should send gas fee to specified fee recipient (if not 0x0), not tx.origin', async () => { + await receiverERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, isGasReceipt, data) + const operatorBalance = await erc1155Contract.balanceOf(operatorAddress, feeTokenID) - const feeTokenDataERC20 = ethers.utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 1]) + expect(gasReceipt!.gasFee).to.be.eql(operatorBalance.toNumber()) + }) - gasReceipt = { - gasLimitCallback: 130000, - gasFee: 1000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC20 - } + it('should REVERT if gasReceipt is incorrect', async () => { + const sigArgTypes = ['address', 'address', 'address', 'uint256', 'uint256', 'uint256'] + const txDataTypes = ['bytes', 'bytes'] - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null + const signer = await transferObj.signerWallet.getAddress() - // Data to pass in transfer method - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + // Packed encoding of transfer signature message + let sigData = ethers.utils.solidityPack(sigArgTypes, [ + transferObj.contractAddress, + signer, + transferObj.receiver, + transferObj.id, + transferObj.amount, + transferObj.nonce + ]) - const tx = operatorERC1155Contract.metaSafeTransferFrom( - ownerAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data, - HIGH_GAS_LIMIT - ) - await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) - }) + // Form bad gas receipt + const badGasReceipt = { ...gasReceipt, gasPrice: 109284123 } - it('should REVERT if FeeTokenType is not supported', async () => { - const erc20Abstract = await AbstractContract.fromArtifactName('ERC20Mock') - const erc20Contract = (await erc20Abstract.deploy(ownerWallet)) as ERC20Mock - await erc20Contract.mockMint(ownerAddress, feeTokenInitBalance) - await erc20Contract.approve(operatorERC1155Contract.address, feeTokenInitBalance) + const transferData = transferObj.transferData == null ? utils.toUtf8Bytes('') : transferObj.transferData - let feeTokenDataERC20 = ethers.utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 2]) + // Correct and incorrect transferData + const goodGasAndTransferData = ethers.utils.defaultAbiCoder.encode( + [GasReceiptType, 'bytes'], + [gasReceipt, transferData] + ) + const badGasAndTransferData = ethers.utils.defaultAbiCoder.encode( + [GasReceiptType, 'bytes'], + [badGasReceipt, transferData] + ) - gasReceipt = { - gasLimitCallback: 130000, - gasFee: 1000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC20 - } + // Encode normally the whole thing + sigData = ethers.utils.solidityPack(['bytes', 'bytes'], [sigData, goodGasAndTransferData]) - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null + // Get signature + const sig = (await ethSign(transferObj.signerWallet, sigData)).slice(0, -2) + const paddedNonce = ethers.utils.solidityPack(['uint256'], [transferObj.nonce]) + const ethsig_nonce = sig + paddedNonce.slice(2) + '02' // encode packed the nonce - // Data to pass in transfer method - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + // PASS BAD DATA + data = ethers.utils.defaultAbiCoder.encode(txDataTypes, [ethsig_nonce, badGasAndTransferData]) - const tx = operatorERC1155Contract.metaSafeTransferFrom( - ownerAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data, - HIGH_GAS_LIMIT - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_transferGasFee: UNSUPPORTED_TOKEN')) + const tx = operatorERC1155Contract.metaSafeTransferFrom( + ownerAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) + }) - feeTokenDataERC20 = ethers.utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 3]) + it('should PASS if another approved ERC-1155 is used for fee', async () => { + const erc1155Contract2 = await deployERC1155Contract() + await erc1155Contract2.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) + await erc1155Contract2.setApprovalForAll(operatorERC1155Contract.address, true) - gasReceipt = { - gasLimitCallback: 130000, - gasFee: 1000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC20 - } + feeTokenDataERC1155 = ethers.utils.defaultAbiCoder.encode( + ['address', 'uint256', 'uint8'], + [erc1155Contract2.address, feeTokenID, 0] + ) - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null + gasReceipt = { + gasLimitCallback: 130000, + gasFee: 1000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC1155 + } - // Data to pass in transfer method - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null - const tx2 = operatorERC1155Contract.metaSafeTransferFrom( - ownerAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data, - HIGH_GAS_LIMIT - ) - await expect(tx2).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_transferGasFee: UNSUPPORTED_TOKEN')) - }) + // Data to pass in transfer method + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - it('should REVERT if gas receipt is passed, but not claimed', async () => { - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, false, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) + const tx = operatorERC1155Contract.metaSafeTransferFrom( + ownerAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + HIGH_GAS_LIMIT + ) + await expect(tx).to.be.fulfilled + }) - it('should REVERT if gas receipt is passed but isGasFee is false', async () => { - transferObj.isGasFee = false - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, true, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) + it('should REVERT if NOT approved ERC-1155 is used for fee', async () => { + const erc1155Contract2 = await deployERC1155Contract() + await erc1155Contract2.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) - it('should PASS if gas receipt is passed with isGasFee to false and not claimed', async () => { - transferObj.isGasFee = false - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, false, data) - await expect(tx).to.be.fulfilled - }) + feeTokenDataERC1155 = ethers.utils.defaultAbiCoder.encode( + ['address', 'uint256', 'uint8'], + [erc1155Contract2.address, feeTokenID, 0] + ) + + gasReceipt = { + gasLimitCallback: 130000, + gasFee: 1000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC1155 + } - describe('When receiver is a contract', () => { - it('should REVERT if gas used in onERC1155Received exceeds limit', async () => { - const lowGasLimit = 1000 - gasReceipt!.gasLimitCallback = lowGasLimit - transferObj.receiver = receiverContract.address + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null + // Data to pass in transfer method data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeTransferFrom( ownerAddress, - receiverContract.address, + receiverAddress, id, amount, isGasReceipt, data, HIGH_GAS_LIMIT ) - await expect(tx).to.be.rejectedWith(RevertOutOfGasError()) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#safeTransferFrom: INVALID_OPERATOR')) }) - it('should PASS if gas used in onERC1155Received does not exceed limit', async () => { - const okGasLimit = 12000 - gasReceipt!.gasLimitCallback = okGasLimit - transferObj.receiver = receiverContract.address + it('should REVERT if another ERC-1155 is used for fee without sufficient balance', async () => { + const erc1155Contract2 = await deployERC1155Contract() + await erc1155Contract2.mintMock(ownerAddress, feeTokenID, 100, []) + await erc1155Contract2.setApprovalForAll(operatorERC1155Contract.address, true) + + feeTokenDataERC1155 = ethers.utils.defaultAbiCoder.encode( + ['address', 'uint256', 'uint8'], + [erc1155Contract2.address, feeTokenID, 0] + ) + + gasReceipt = { + gasLimitCallback: 130000, + gasFee: 1000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC1155 + } + + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null + // Data to pass in transfer method data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeTransferFrom( ownerAddress, - receiverContract.address, + receiverAddress, id, amount, isGasReceipt, data, HIGH_GAS_LIMIT ) - await expect(tx).to.be.fulfilled + await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) }) - it('should PASS if gasLimit is higher than gas sent in transaction', async () => { - const highGasLimit = 3000000 - gasReceipt!.gasLimitCallback = highGasLimit - transferObj.receiver = receiverContract.address + it('should PASS if approved ERC20 is used for fee', async () => { + const erc20Abstract = await AbstractContract.fromArtifactName('ERC20Mock') + const erc20Contract = (await erc20Abstract.deploy(ownerWallet)) as ERC20Mock + await erc20Contract.mockMint(ownerAddress, feeTokenInitBalance) + await erc20Contract.approve(operatorERC1155Contract.address, feeTokenInitBalance) + + const feeTokenDataERC20 = ethers.utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 1]) + + gasReceipt = { + gasLimitCallback: 130000, + gasFee: 1000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC20 + } + + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null + // Data to pass in transfer method data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeTransferFrom( ownerAddress, - receiverContract.address, + receiverAddress, id, amount, isGasReceipt, @@ -925,508 +745,481 @@ describe('ERC1155MetaPackedBalance', () => { ) await expect(tx).to.be.fulfilled }) - }) - }) - describe('When gas is NOT reimbursed', () => { - before(async function() { - if (condition[1]) { - this.test!.parent!.pending = true - this.skip() - } - }) + it('should REVERT if NOT approved ERC20 is used for fee', async () => { + const erc20Abstract = await AbstractContract.fromArtifactName('ERC20Mock') + const erc20Contract = (await erc20Abstract.deploy(ownerWallet)) as ERC20Mock + await erc20Contract.mockMint(ownerAddress, feeTokenInitBalance) - it('should PASS if gas receipt is not passed and not claimed', async () => { - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, false, data) - await expect(tx).to.be.fulfilled - }) + const feeTokenDataERC20 = ethers.utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 1]) - it('should REVER if gas receipt is not passed and claimed', async () => { - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, true, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) + gasReceipt = { + gasLimitCallback: 130000, + gasFee: 1000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC20 + } - it('should REVERT if gas receipt is not passed but isGasFee is set to true and is claimed', async () => { - transferObj.isGasFee = true - data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, true, data) - await expect(tx).to.be.rejectedWith(RevertError()) - }) - }) + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null - context('When successful transfer', () => { - let tx: ethers.ContractTransaction + // Data to pass in transfer method + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - beforeEach(async () => { - tx = await operatorERC1155Contract.metaSafeTransferFrom( - ownerAddress, - receiverAddress, - id, - amount, - isGasReceipt, - data - ) - }) + const tx = operatorERC1155Contract.metaSafeTransferFrom( + ownerAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + HIGH_GAS_LIMIT + ) + await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) + }) - it('should correctly update balance of sender', async () => { - const balance = await erc1155Contract.balanceOf(ownerAddress, id) - expect(balance).to.be.eql(BigNumber.from(initBalance - amount)) - }) + it('should REVERT if approved ERC20 balance is insufficient', async () => { + const erc20Abstract = await AbstractContract.fromArtifactName('ERC20Mock') + const erc20Contract = (await erc20Abstract.deploy(ownerWallet)) as ERC20Mock + await erc20Contract.mockMint(ownerAddress, 100) - it('should correctly update balance of receiver', async () => { - const balance = await erc1155Contract.balanceOf(receiverAddress, id) - expect(balance).to.be.eql(BigNumber.from(amount)) - }) + const feeTokenDataERC20 = ethers.utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 1]) - describe('When gas is reimbursed', () => { - before(async function() { - if (!condition[1]) { - this.test!.parent!.pending = true - this.skip() + gasReceipt = { + gasLimitCallback: 130000, + gasFee: 1000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC20 } - }) - it('should update gas token balance of sender', async () => { - const senderBalance = await erc1155Contract.balanceOf(ownerAddress, feeTokenID) - expect(senderBalance).to.be.eql(feeTokenInitBalance.sub(gasReceipt!.gasFee)) - }) + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null + + // Data to pass in transfer method + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - it('should update gas token balance of executor', async () => { - const balance = await erc1155Contract.balanceOf(operatorAddress, feeTokenID) - expect(gasReceipt!.gasFee).to.be.eql(balance.toNumber()) + const tx = operatorERC1155Contract.metaSafeTransferFrom( + ownerAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + HIGH_GAS_LIMIT + ) + await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) }) - }) - describe('TransferSingle event', async () => { - let filterFromOperatorContract: ethers.EventFilter + it('should REVERT if FeeTokenType is not supported', async () => { + const erc20Abstract = await AbstractContract.fromArtifactName('ERC20Mock') + const erc20Contract = (await erc20Abstract.deploy(ownerWallet)) as ERC20Mock + await erc20Contract.mockMint(ownerAddress, feeTokenInitBalance) + await erc20Contract.approve(operatorERC1155Contract.address, feeTokenInitBalance) - it('should emit TransferSingle event', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events!.pop()! - expect(ev.event).to.be.eql('TransferSingle') - }) + let feeTokenDataERC20 = ethers.utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 2]) - it('should have `msg.sender` as `_operator` field, not _from', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events!.pop()! + gasReceipt = { + gasLimitCallback: 130000, + gasFee: 1000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC20 + } - const args = ev.args! as any - expect(args._operator).to.be.eql(operatorAddress) - }) + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null + + // Data to pass in transfer method + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - it('should have `msg.sender` as `_operator` field, not tx.origin', async () => { - // Get event filter to get internal tx event - filterFromOperatorContract = erc1155Contract.filters.TransferSingle( - operatorContract.address, - null, - null, - null, - null + const tx = operatorERC1155Contract.metaSafeTransferFrom( + ownerAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + HIGH_GAS_LIMIT ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_transferGasFee: UNSUPPORTED_TOKEN')) + + feeTokenDataERC20 = ethers.utils.defaultAbiCoder.encode(['address', 'uint8'], [erc20Contract.address, 3]) + + gasReceipt = { + gasLimitCallback: 130000, + gasFee: 1000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC20 + } + + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null - // Increment nonce because it's the second transfer - transferObj.nonce = nonce.add(1) + // Data to pass in transfer method data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) - // Execute transfer from operator contract - // @ts-ignore (https://github.com/ethereum-ts/TypeChain/issues/118) - await operatorContract.metaSafeTransferFrom( - erc1155Contract.address, + const tx2 = operatorERC1155Contract.metaSafeTransferFrom( ownerAddress, receiverAddress, id, amount, isGasReceipt, data, - { gasLimit: 1000000 } // INCORRECT GAS ESTIMATION + HIGH_GAS_LIMIT ) + await expect(tx2).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_transferGasFee: UNSUPPORTED_TOKEN')) + }) - // Get logs from internal transaction event - // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) - filterFromOperatorContract.fromBlock = 0 - const logs = await operatorProvider.getLogs(filterFromOperatorContract) - const args = erc1155Contract.interface.decodeEventLog( - erc1155Contract.interface.events['TransferSingle(address,address,address,uint256,uint256)'], - logs[0].data, - logs[0].topics - ) + it('should REVERT if gas receipt is passed, but not claimed', async () => { + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, false, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) + }) - // operator arg should be equal to msg.sender, not tx.origin - expect(args._operator).to.be.eql(operatorContract.address) + it('should REVERT if gas receipt is passed but isGasFee is false', async () => { + transferObj.isGasFee = false + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, true, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - it('should emit NonceChange event', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events![0] - expect(ev.event).to.be.eql('NonceChange') + it('should PASS if gas receipt is passed with isGasFee to false and not claimed', async () => { + transferObj.isGasFee = false + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, false, data) + await expect(tx).to.be.fulfilled }) - it('should have `_signer` as `signer` in NonceChange', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events![0] + describe('When receiver is a contract', () => { + it('should REVERT if gas used in onERC1155Received exceeds limit', async () => { + const lowGasLimit = 1000 + gasReceipt!.gasLimitCallback = lowGasLimit + transferObj.receiver = receiverContract.address + + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSafeTransferFrom( + ownerAddress, + receiverContract.address, + id, + amount, + isGasReceipt, + data, + HIGH_GAS_LIMIT + ) + await expect(tx).to.be.rejectedWith(RevertOutOfGasError()) + }) + + it('should PASS if gas used in onERC1155Received does not exceed limit', async () => { + const okGasLimit = 12000 + gasReceipt!.gasLimitCallback = okGasLimit + transferObj.receiver = receiverContract.address + + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSafeTransferFrom( + ownerAddress, + receiverContract.address, + id, + amount, + isGasReceipt, + data, + HIGH_GAS_LIMIT + ) + await expect(tx).to.be.fulfilled + }) + + it('should PASS if gasLimit is higher than gas sent in transaction', async () => { + const highGasLimit = 3000000 + gasReceipt!.gasLimitCallback = highGasLimit + transferObj.receiver = receiverContract.address + + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSafeTransferFrom( + ownerAddress, + receiverContract.address, + id, + amount, + isGasReceipt, + data, + HIGH_GAS_LIMIT + ) + await expect(tx).to.be.fulfilled + }) + }) + }) - const args = ev.args! as any - expect(args.signer).to.be.eql(ownerWallet.address) + describe('When gas is NOT reimbursed', () => { + before(async function() { + if (condition[1]) { + this.test!.parent!.pending = true + this.skip() + } }) - it('should have `nonce` as `nonce + 1` in NonceChange', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events![0] + it('should PASS if gas receipt is not passed and not claimed', async () => { + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, false, data) + await expect(tx).to.be.fulfilled + }) - const args = ev.args! as any - expect(args.newNonce).to.be.eql(nonce.add(1)) + it('should REVER if gas receipt is not passed and claimed', async () => { + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, true, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - }) - }) - }) - }) - }) - }) - describe('metaSafeBatchTransferFrom() (Meta) Function', () => { - let receiverContract: ERC1155ReceiverMock - let operatorContract: ERC1155OperatorMock - - let transferData: string | null = 'Hello from the other side' - const initBalance = 100 - const amount = 10 - const nonce = BigNumber.from(0) - - // Parameters for balances - let ids: any[], amounts: any[] - const nTokenTypes = 33 - - let isGasReceipt: boolean = true - const feeTokenInitBalance = BigNumber.from(100000000) - - const feeType = 0 - let feeToken: BigNumber - const feeTokenID = 666 - let feeTokenAddress: string - let feeTokenDataERC1155: string | Uint8Array - - let transferObj: BatchTransferSignature - let gasReceipt: GasReceipt | null - let domainHash: string - let data: string - - const conditions = [ - [transferData, true, 'Gas receipt & transfer data'], - [null, true, 'Gas receipt w/o transfer data'], - [transferData, false, 'Transfer data w/o gas receipt '], - [null, false, 'No Gas receipt & No transfer data'] - ] - - conditions.forEach(function(condition) { - context(condition[2] as string, () => { - beforeEach(async () => { - // Get conditions - transferData = (await condition[0]) as string | null - isGasReceipt = (await condition[1]) as boolean - - // Deploy contracts - const abstract = await AbstractContract.fromArtifactName('ERC1155ReceiverMock') - receiverContract = (await abstract.deploy(ownerWallet)) as ERC1155ReceiverMock - operatorContract = (await operatorAbstract.deploy(operatorWallet)) as ERC1155OperatorMock - - // Mint tokens - ;(ids = []), (amounts = []) - - // Minting enough amounts for transfer for each types - for (let i = 0; i < nTokenTypes; i++) { - await erc1155Contract.mintMock(ownerAddress, i, initBalance, []) - ids.push(i) - amounts.push(amount) - } - - feeTokenAddress = erc1155Contract.address - - feeTokenDataERC1155 = ethers.utils.defaultAbiCoder.encode( - ['address', 'uint256', 'uint8'], - [feeTokenAddress, feeTokenID, feeType] - ) - - // Gas Receipt - gasReceipt = { - gasLimitCallback: 130000, - gasFee: 30000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC1155 - } - - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null - - // Transfer Signature Object - transferObj = { - contractAddress: erc1155Contract.address, - signerWallet: ownerWallet, - receiver: receiverAddress, - ids: ids.slice(0), - amounts: amounts.slice(0), - isGasFee: isGasReceipt, - transferData: transferData === null ? null : utils.toUtf8Bytes(transferData), - nonce: nonce - } - - // Mint tokens used to pay for gas - await erc1155Contract.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) - - // Domain hash - domainHash = ethers.utils.keccak256( - ethers.utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, erc1155Contract.address]) - ) - - // Data to pass in transfer method - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - }) + it('should REVERT if gas receipt is not passed but isGasFee is set to true and is claimed', async () => { + transferObj.isGasFee = true + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + const tx = operatorERC1155Contract.metaSafeTransferFrom(ownerAddress, receiverAddress, id, amount, true, data) + await expect(tx).to.be.rejectedWith(RevertError()) + }) + }) - it('should REVERT if contract address is incorrect', async () => { - domainHash = ethers.utils.keccak256( - ethers.utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, receiverContract.address]) - ) - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) + context('When successful transfer', () => { + let tx: ethers.ContractTransaction - it('should REVERT if signer address is incorrect', async () => { - transferObj.signerWallet = operatorWallet - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) + beforeEach(async () => { + tx = await operatorERC1155Contract.metaSafeTransferFrom( + ownerAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data + ) + }) - it('should REVERT if receiver address is incorrect', async () => { - transferObj.receiver = ownerAddress - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) + it('should correctly update balance of sender', async () => { + const balance = await erc1155Contract.balanceOf(ownerAddress, id) + expect(balance).to.be.eql(BigNumber.from(initBalance - amount)) + }) - it('should REVERT if token id is incorrect', async () => { - transferObj.ids[0] = 6 - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) + it('should correctly update balance of receiver', async () => { + const balance = await erc1155Contract.balanceOf(receiverAddress, id) + expect(balance).to.be.eql(BigNumber.from(amount)) + }) - it('should REVERT if token amount is incorrect', async () => { - transferObj.amounts[0] = amount + 1 - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) + describe('When gas is reimbursed', () => { + before(async function() { + if (!condition[1]) { + this.test!.parent!.pending = true + this.skip() + } + }) + + it('should update gas token balance of sender', async () => { + const senderBalance = await erc1155Contract.balanceOf(ownerAddress, feeTokenID) + expect(senderBalance).to.be.eql(feeTokenInitBalance.sub(gasReceipt!.gasFee)) + }) + + it('should update gas token balance of executor', async () => { + const balance = await erc1155Contract.balanceOf(operatorAddress, feeTokenID) + expect(gasReceipt!.gasFee).to.be.eql(balance.toNumber()) + }) + }) - it('should REVERT if transfer data is incorrect', async () => { - const sigArgTypes = ['address', 'address', 'address', 'uint256[]', 'uint256[]', 'uint256'] - const txDataTypes = ['bytes', 'bytes'] - - const signer = await transferObj.signerWallet.getAddress() - - // Packed encoding of transfer signature message - let sigData = ethers.utils.solidityPack(sigArgTypes, [ - transferObj.contractAddress, - signer, - transferObj.receiver, - transferObj.ids, - transferObj.amounts, - transferObj.nonce - ]) - - const transferData = transferObj.transferData == null ? utils.toUtf8Bytes('') : transferObj.transferData - let goodGasAndTransferData - let badGasAndTransferData - - // Correct and incorrect transferData - if (isGasReceipt) { - goodGasAndTransferData = ethers.utils.defaultAbiCoder.encode([GasReceiptType, 'bytes'], [gasReceipt, transferData]) - badGasAndTransferData = ethers.utils.defaultAbiCoder.encode( - [GasReceiptType, 'bytes'], - [gasReceipt, utils.toUtf8Bytes('Goodbyebyebye')] - ) - } else { - goodGasAndTransferData = ethers.utils.defaultAbiCoder.encode(['bytes'], [transferData]) - badGasAndTransferData = ethers.utils.defaultAbiCoder.encode(['bytes'], [utils.toUtf8Bytes('Goodbyebyebye')]) - } - - // Encode normally the whole thing - sigData = ethers.utils.solidityPack(['bytes', 'bytes'], [sigData, goodGasAndTransferData]) - - // Get signature - const sig = (await ethSign(transferObj.signerWallet, sigData)).slice(0, -2) - const paddedNonce = ethers.utils.solidityPack(['uint256'], [transferObj.nonce]) - const ethsig_nonce = sig + paddedNonce.slice(2) + '02' // encode packed the nonce - - // PASS BAD DATA - data = ethers.utils.defaultAbiCoder.encode(txDataTypes, [ethsig_nonce, badGasAndTransferData]) - - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) + describe('TransferSingle event', async () => { + let filterFromOperatorContract: ethers.EventFilter + + it('should emit TransferSingle event', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events!.pop()! + expect(ev.event).to.be.eql('TransferSingle') + }) + + it('should have `msg.sender` as `_operator` field, not _from', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events!.pop()! + + const args = ev.args! as any + expect(args._operator).to.be.eql(operatorAddress) + }) + + it('should have `msg.sender` as `_operator` field, not tx.origin', async () => { + // Get event filter to get internal tx event + filterFromOperatorContract = erc1155Contract.filters.TransferSingle( + operatorContract.address, + null, + null, + null, + null + ) + + // Increment nonce because it's the second transfer + transferObj.nonce = nonce.add(1) + data = await encodeMetaTransferFromData(transferObj, domainHash, gasReceipt) + + // Execute transfer from operator contract + // @ts-ignore (https://github.com/ethereum-ts/TypeChain/issues/118) + await operatorContract.metaSafeTransferFrom( + erc1155Contract.address, + ownerAddress, + receiverAddress, + id, + amount, + isGasReceipt, + data, + { gasLimit: 1000000 } // INCORRECT GAS ESTIMATION + ) + + // Get logs from internal transaction event + // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) + filterFromOperatorContract.fromBlock = 0 + const logs = await operatorProvider.getLogs(filterFromOperatorContract) + const args = erc1155Contract.interface.decodeEventLog( + erc1155Contract.interface.events['TransferSingle(address,address,address,uint256,uint256)'], + logs[0].data, + logs[0].topics + ) + + // operator arg should be equal to msg.sender, not tx.origin + expect(args._operator).to.be.eql(operatorContract.address) + }) + + it('should emit NonceChange event', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events![0] + expect(ev.event).to.be.eql('NonceChange') + }) + + it('should have `_signer` as `signer` in NonceChange', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events![0] + + const args = ev.args! as any + expect(args.signer).to.be.eql(ownerWallet.address) + }) + + it('should have `nonce` as `nonce + 1` in NonceChange', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events![0] + + const args = ev.args! as any + expect(args.newNonce).to.be.eql(nonce.add(1)) + }) + }) + }) + }) }) + }) + }) - it('should REVERT if nonce is incorrect', async () => { - transferObj.nonce = nonce.add(101) - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_NONCE')) - - // Correct nonce - transferObj.nonce = nonce - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - await operatorERC1155Contract.metaSafeBatchTransferFrom(ownerAddress, receiverAddress, ids, amounts, isGasReceipt, data) - - // Nonce lower - const tx2 = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx2).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_NONCE')) - }) + describe('metaSafeBatchTransferFrom() (Meta) Function', () => { + let receiverContract: ERC1155ReceiverMock + let operatorContract: ERC1155OperatorMock + + let transferData: string | null = 'Hello from the other side' + const initBalance = 100 + const amount = 10 + const nonce = BigNumber.from(0) + + // Parameters for balances + let ids: any[], amounts: any[] + const nTokenTypes = 33 + + let isGasReceipt: boolean = true + const feeTokenInitBalance = BigNumber.from(100000000) + + const feeType = 0 + let feeToken: BigNumber + const feeTokenID = 666 + let feeTokenAddress: string + let feeTokenDataERC1155: string | Uint8Array + + let transferObj: BatchTransferSignature + let gasReceipt: GasReceipt | null + let domainHash: string + let data: string + + const conditions = [ + [transferData, true, 'Gas receipt & transfer data'], + [null, true, 'Gas receipt w/o transfer data'], + [transferData, false, 'Transfer data w/o gas receipt '], + [null, false, 'No Gas receipt & No transfer data'] + ] + + conditions.forEach(function(condition) { + context(condition[2] as string, () => { + beforeEach(async () => { + // Get conditions + transferData = (await condition[0]) as string | null + isGasReceipt = (await condition[1]) as boolean + + // Deploy contracts + const abstract = await AbstractContract.fromArtifactName('ERC1155ReceiverMock') + receiverContract = (await abstract.deploy(ownerWallet)) as ERC1155ReceiverMock + operatorContract = (await operatorAbstract.deploy(operatorWallet)) as ERC1155OperatorMock + + // Mint tokens + ;(ids = []), (amounts = []) + + // Minting enough amounts for transfer for each types + for (let i = 0; i < nTokenTypes; i++) { + await erc1155Contract.mintMock(ownerAddress, i, initBalance, []) + ids.push(i) + amounts.push(amount) + } - it('should PASS if signature is valid', async () => { - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.fulfilled - }) + feeTokenAddress = erc1155Contract.address - describe('ERC-1271 Receiver', () => { - let erc1271WalletValidationMockContract: ERC1271WalletValidationMock - let ERC1271WalletValidationMockAbstract: AbstractContract + feeTokenDataERC1155 = ethers.utils.defaultAbiCoder.encode( + ['address', 'uint256', 'uint8'], + [feeTokenAddress, feeTokenID, feeType] + ) - let erc1271WalletAddress + // Gas Receipt + gasReceipt = { + gasLimitCallback: 130000, + gasFee: 30000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC1155 + } - beforeEach(async () => { - ERC1271WalletValidationMockAbstract = await AbstractContract.fromArtifactName('ERC1271WalletValidationMock') - erc1271WalletValidationMockContract = (await ERC1271WalletValidationMockAbstract.deploy(ownerWallet, [ - domainHash - ])) as ERC1271WalletValidationMock - erc1271WalletAddress = erc1271WalletValidationMockContract.address - - await erc1155Contract.batchMintMock(erc1271WalletAddress, ids, amounts, []) - await erc1155Contract.mintMock(erc1271WalletAddress, feeTokenID, feeTokenInitBalance, []) - }) + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null + + // Transfer Signature Object + transferObj = { + contractAddress: erc1155Contract.address, + signerWallet: ownerWallet, + receiver: receiverAddress, + ids: ids.slice(0), + amounts: amounts.slice(0), + isGasFee: isGasReceipt, + transferData: transferData === null ? null : utils.toUtf8Bytes(transferData), + nonce: nonce + } - describe(`EIP-1271 (bytes) signatures (03)`, () => { - it('should return REVERT if signature is invalid', async () => { - transferObj.from = erc1271WalletAddress - transferObj.signerWallet = receiverWallet + // Mint tokens used to pay for gas + await erc1155Contract.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt, '03') - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - erc1271WalletAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) - }) + // Domain hash + domainHash = ethers.utils.keccak256( + ethers.utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, erc1155Contract.address]) + ) - describe(`EIP-1271 (bytes32) signatures (04)`, () => { - it('should return REVERT if signature is invalid', async () => { - transferObj.from = erc1271WalletAddress - transferObj.signerWallet = receiverWallet + // Data to pass in transfer method + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + }) - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt, '04') - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - erc1271WalletAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) + it('should REVERT if contract address is incorrect', async () => { + domainHash = ethers.utils.keccak256( + ethers.utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, receiverContract.address]) + ) + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - it('should PASS if signature is valid', async () => { - transferObj.from = erc1271WalletAddress - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt, '04') - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - erc1271WalletAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - await expect(tx).to.be.fulfilled - }) + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, + ids, + amounts, + isGasReceipt, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - }) - describe('When signature is valid', () => { - it('should REVERT if insufficient balance', async () => { - transferObj.amounts[0] = initBalance + 1 - amounts[0] = initBalance + 1 + it('should REVERT if signer address is incorrect', async () => { + transferObj.signerWallet = operatorWallet data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( @@ -1435,31 +1228,30 @@ describe('ERC1155MetaPackedBalance', () => { ids, amounts, isGasReceipt, - data, - HIGH_GAS_LIMIT + data ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: UNDERFLOW')) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - it('should REVERT if sending to 0x0', async () => { - transferObj.receiver = ZERO_ADDRESS + it('should REVERT if receiver address is incorrect', async () => { + transferObj.receiver = ownerAddress data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - ZERO_ADDRESS, + receiverAddress, ids, amounts, isGasReceipt, data ) - await expect(tx).to.be.rejectedWith( - RevertError('ERC1155MetaPackedBalance#metaSafeBatchTransferFrom: INVALID_RECIPIENT') - ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - it('should REVERT if transfer leads to overflow', async () => { - await operatorERC1155Contract.mintMock(receiverAddress, ids[0], MAXVAL, []) + it('should REVERT if token id is incorrect', async () => { + transferObj.ids[0] = 6 + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, receiverAddress, @@ -1468,152 +1260,226 @@ describe('ERC1155MetaPackedBalance', () => { isGasReceipt, data ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: OVERFLOW')) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - it('should REVERT when sending to non-receiver contract', async () => { - transferObj.receiver = erc1155Contract.address + it('should REVERT if token amount is incorrect', async () => { + transferObj.amounts[0] = amount + 1 data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - erc1155Contract.address, + receiverAddress, ids, amounts, isGasReceipt, - data, - HIGH_GAS_LIMIT + data ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - it('should REVERT if invalid response from receiver contract', async () => { - transferObj.receiver = receiverContract.address - if (gasReceipt) { - gasReceipt.gasLimitCallback = HIGH_GAS_LIMIT.gasLimit + it('should REVERT if transfer data is incorrect', async () => { + const sigArgTypes = ['address', 'address', 'address', 'uint256[]', 'uint256[]', 'uint256'] + const txDataTypes = ['bytes', 'bytes'] + + const signer = await transferObj.signerWallet.getAddress() + + // Packed encoding of transfer signature message + let sigData = ethers.utils.solidityPack(sigArgTypes, [ + transferObj.contractAddress, + signer, + transferObj.receiver, + transferObj.ids, + transferObj.amounts, + transferObj.nonce + ]) + + const transferData = transferObj.transferData == null ? utils.toUtf8Bytes('') : transferObj.transferData + let goodGasAndTransferData + let badGasAndTransferData + + // Correct and incorrect transferData + if (isGasReceipt) { + goodGasAndTransferData = ethers.utils.defaultAbiCoder.encode([GasReceiptType, 'bytes'], [gasReceipt, transferData]) + badGasAndTransferData = ethers.utils.defaultAbiCoder.encode( + [GasReceiptType, 'bytes'], + [gasReceipt, utils.toUtf8Bytes('Goodbyebyebye')] + ) + } else { + goodGasAndTransferData = ethers.utils.defaultAbiCoder.encode(['bytes'], [transferData]) + badGasAndTransferData = ethers.utils.defaultAbiCoder.encode(['bytes'], [utils.toUtf8Bytes('Goodbyebyebye')]) } - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - // Force invalid response - await receiverContract.setShouldReject(true) + // Encode normally the whole thing + sigData = ethers.utils.solidityPack(['bytes', 'bytes'], [sigData, goodGasAndTransferData]) + + // Get signature + const sig = (await ethSign(transferObj.signerWallet, sigData)).slice(0, -2) + const paddedNonce = ethers.utils.solidityPack(['uint256'], [transferObj.nonce]) + const ethsig_nonce = sig + paddedNonce.slice(2) + '02' // encode packed the nonce + + // PASS BAD DATA + data = ethers.utils.defaultAbiCoder.encode(txDataTypes, [ethsig_nonce, badGasAndTransferData]) const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - receiverContract.address, + receiverAddress, ids, amounts, isGasReceipt, - data, - HIGH_GAS_LIMIT - ) - await expect(tx).to.be.rejectedWith( - RevertError('ERC1155PackedBalance#_callonERC1155BatchReceived: INVALID_ON_RECEIVE_MESSAGE') + data ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - it('should PASS if valid response from receiver contract', async () => { - transferObj.receiver = receiverContract.address - if (gasReceipt) { - gasReceipt.gasLimitCallback = HIGH_GAS_LIMIT.gasLimit - } + it('should REVERT if nonce is incorrect', async () => { + transferObj.nonce = nonce.add(101) data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - receiverContract.address, + receiverAddress, + ids, + amounts, + isGasReceipt, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_NONCE')) + + // Correct nonce + transferObj.nonce = nonce + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + await operatorERC1155Contract.metaSafeBatchTransferFrom(ownerAddress, receiverAddress, ids, amounts, isGasReceipt, data) + + // Nonce lower + const tx2 = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, ids, amounts, isGasReceipt, - data, - HIGH_GAS_LIMIT + data ) + await expect(tx2).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_NONCE')) + }) + it('should PASS if signature is valid', async () => { + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, + ids, + amounts, + isGasReceipt, + data + ) await expect(tx).to.be.fulfilled }) - describe('When gas is reimbursed', () => { - before(async function() { - if (!condition[1]) { - this.test!.parent!.pending = true - this.skip() - } + describe('ERC-1271 Receiver', () => { + let erc1271WalletValidationMockContract: ERC1271WalletValidationMock + let ERC1271WalletValidationMockAbstract: AbstractContract + + let erc1271WalletAddress + + beforeEach(async () => { + ERC1271WalletValidationMockAbstract = await AbstractContract.fromArtifactName('ERC1271WalletValidationMock') + erc1271WalletValidationMockContract = (await ERC1271WalletValidationMockAbstract.deploy(ownerWallet, [ + domainHash + ])) as ERC1271WalletValidationMock + erc1271WalletAddress = erc1271WalletValidationMockContract.address + + await erc1155Contract.batchMintMock(erc1271WalletAddress, ids, amounts, []) + await erc1155Contract.mintMock(erc1271WalletAddress, feeTokenID, feeTokenInitBalance, []) + }) + + describe(`EIP-1271 (bytes) signatures (03)`, () => { + it('should return REVERT if signature is invalid', async () => { + transferObj.from = erc1271WalletAddress + transferObj.signerWallet = receiverWallet + + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt, '03') + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + erc1271WalletAddress, + receiverAddress, + ids, + amounts, + isGasReceipt, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) + }) }) - it('should send gas fee to msg.sender if fee recipient ix 0x0', async () => { - gasReceipt!.feeRecipient = ZERO_ADDRESS + describe(`EIP-1271 (bytes32) signatures (04)`, () => { + it('should return REVERT if signature is invalid', async () => { + transferObj.from = erc1271WalletAddress + transferObj.signerWallet = receiverWallet + + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt, '04') + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + erc1271WalletAddress, + receiverAddress, + ids, + amounts, + isGasReceipt, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) + }) + + it('should PASS if signature is valid', async () => { + transferObj.from = erc1271WalletAddress + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt, '04') + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + erc1271WalletAddress, + receiverAddress, + ids, + amounts, + isGasReceipt, + data + ) + await expect(tx).to.be.fulfilled + }) + }) + }) + describe('When signature is valid', () => { + it('should REVERT if insufficient balance', async () => { + transferObj.amounts[0] = initBalance + 1 + amounts[0] = initBalance + 1 data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - await receiverERC1155Contract.metaSafeBatchTransferFrom( + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, receiverAddress, ids, amounts, isGasReceipt, - data + data, + HIGH_GAS_LIMIT ) - - const receiverBalance = await operatorERC1155Contract.balanceOf(receiverAddress, feeTokenID) - - expect(gasReceipt!.gasFee).to.be.eql(receiverBalance.toNumber()) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: UNDERFLOW')) }) - it('should send gas fee to specified fee recipient (if not 0x0), not tx.origin', async () => { - await receiverERC1155Contract.metaSafeBatchTransferFrom( + it('should REVERT if sending to 0x0', async () => { + transferObj.receiver = ZERO_ADDRESS + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - receiverAddress, + ZERO_ADDRESS, ids, amounts, isGasReceipt, data ) - const operatorBalance = await operatorERC1155Contract.balanceOf(operatorAddress, feeTokenID) - - expect(gasReceipt!.gasFee).to.be.eql(operatorBalance.toNumber()) - }) - - it('should REVERT if gasReceipt is incorrect', async () => { - const sigArgTypes = ['address', 'address', 'address', 'uint256[]', 'uint256[]', 'uint256'] - const txDataTypes = ['bytes', 'bytes'] - - const signer = await transferObj.signerWallet.getAddress() - - // Packed encoding of transfer signature message - let sigData = ethers.utils.solidityPack(sigArgTypes, [ - transferObj.contractAddress, - signer, - transferObj.receiver, - transferObj.ids, - transferObj.amounts, - transferObj.nonce - ]) - - // Form bad gas receipt - const badGasReceipt = { ...gasReceipt, gasPrice: 109284123 } - - const transferData = transferObj.transferData == null ? utils.toUtf8Bytes('') : transferObj.transferData - - // Correct and incorrect transferData - const goodGasAndTransferData = ethers.utils.defaultAbiCoder.encode( - [GasReceiptType, 'bytes'], - [gasReceipt, transferData] - ) - const badGasAndTransferData = ethers.utils.defaultAbiCoder.encode( - [GasReceiptType, 'bytes'], - [badGasReceipt, transferData] + await expect(tx).to.be.rejectedWith( + RevertError('ERC1155MetaPackedBalance#metaSafeBatchTransferFrom: INVALID_RECIPIENT') ) + }) - // Encode normally the whole thing - sigData = ethers.utils.solidityPack(['bytes', 'bytes'], [sigData, goodGasAndTransferData]) - - // Get signature - const sig = (await ethSign(transferObj.signerWallet, sigData)).slice(0, -2) - const paddedNonce = ethers.utils.solidityPack(['uint256'], [transferObj.nonce]) - const ethsig_nonce = sig + paddedNonce.slice(2) + '02' // encode packed the nonce - - // PASS BAD DATA - data = ethers.utils.defaultAbiCoder.encode(txDataTypes, [ethsig_nonce, badGasAndTransferData]) - + it('should REVERT if transfer leads to overflow', async () => { + await operatorERC1155Contract.mintMock(receiverAddress, ids[0], MAXVAL, []) const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, receiverAddress, @@ -1622,613 +1488,735 @@ describe('ERC1155MetaPackedBalance', () => { isGasReceipt, data ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: OVERFLOW')) }) - it('should REVERT if gas receipt is passed, but not claimed', async () => { + it('should REVERT when sending to non-receiver contract', async () => { + transferObj.receiver = erc1155Contract.address + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - receiverAddress, + erc1155Contract.address, ids, amounts, - false, - data + isGasReceipt, + data, + HIGH_GAS_LIMIT ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) }) - it('should REVERT if gas receipt is passed but isGasFee is false', async () => { - transferObj.isGasFee = false + it('should REVERT if invalid response from receiver contract', async () => { + transferObj.receiver = receiverContract.address + if (gasReceipt) { + gasReceipt.gasLimitCallback = HIGH_GAS_LIMIT.gasLimit + } data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + + // Force invalid response + await receiverContract.setShouldReject(true) + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - receiverAddress, + receiverContract.address, ids, amounts, - true, - data + isGasReceipt, + data, + HIGH_GAS_LIMIT + ) + await expect(tx).to.be.rejectedWith( + RevertError('ERC1155PackedBalance#_callonERC1155BatchReceived: INVALID_ON_RECEIVE_MESSAGE') ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - it('should PASS if gas receipt is passed with isGasFee to false and not claimed', async () => { - transferObj.isGasFee = false + it('should PASS if valid response from receiver contract', async () => { + transferObj.receiver = receiverContract.address + if (gasReceipt) { + gasReceipt.gasLimitCallback = HIGH_GAS_LIMIT.gasLimit + } data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - receiverAddress, + receiverContract.address, ids, amounts, - false, - data + isGasReceipt, + data, + HIGH_GAS_LIMIT ) + await expect(tx).to.be.fulfilled }) - describe('When receiver is a contract', () => { - it('should REVERT if gas used in onERC1155BatchReceived exceeds limit', async () => { - const lowGasLimit = 1000 - gasReceipt!.gasLimitCallback = lowGasLimit - transferObj.receiver = receiverContract.address - - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverContract.address, - ids, - amounts, - isGasReceipt, - data, - HIGH_GAS_LIMIT - ) - await expect(tx).to.be.rejectedWith(RevertOutOfGasError()) + describe('When gas is reimbursed', () => { + before(async function() { + if (!condition[1]) { + this.test!.parent!.pending = true + this.skip() + } }) - it('should PASS if gas used in onERC1155BatchReceived does not exceed limit', async () => { - const okGasLimit = 160000 - gasReceipt!.gasLimitCallback = okGasLimit - transferObj.receiver = receiverContract.address + it('should send gas fee to msg.sender if fee recipient ix 0x0', async () => { + gasReceipt!.feeRecipient = ZERO_ADDRESS data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + await receiverERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - receiverContract.address, + receiverAddress, ids, amounts, isGasReceipt, - data, - HIGH_GAS_LIMIT + data ) - await expect(tx).to.be.fulfilled - }) - it('should PASS if gasLimit is higher than gas sent in transaction', async () => { - const highGasLimit = 3000000 - gasReceipt!.gasLimitCallback = highGasLimit - transferObj.receiver = receiverContract.address + const receiverBalance = await operatorERC1155Contract.balanceOf(receiverAddress, feeTokenID) - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + expect(gasReceipt!.gasFee).to.be.eql(receiverBalance.toNumber()) + }) - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + it('should send gas fee to specified fee recipient (if not 0x0), not tx.origin', async () => { + await receiverERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, - receiverContract.address, + receiverAddress, ids, amounts, isGasReceipt, - data, - HIGH_GAS_LIMIT + data ) - await expect(tx).to.be.fulfilled + const operatorBalance = await operatorERC1155Contract.balanceOf(operatorAddress, feeTokenID) + + expect(gasReceipt!.gasFee).to.be.eql(operatorBalance.toNumber()) }) - }) - }) - describe('When gas is NOT reimbursed', () => { - before(async function() { - if (condition[1]) { - this.test!.parent!.pending = true - this.skip() - } - }) + it('should REVERT if gasReceipt is incorrect', async () => { + const sigArgTypes = ['address', 'address', 'address', 'uint256[]', 'uint256[]', 'uint256'] + const txDataTypes = ['bytes', 'bytes'] - it('should PASS if gas receipt is not passed and not claimed', async () => { - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - false, - data - ) - await expect(tx).to.be.fulfilled - }) + const signer = await transferObj.signerWallet.getAddress() - it('should REVER if gas receipt is not passed and claimed', async () => { - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - true, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) + // Packed encoding of transfer signature message + let sigData = ethers.utils.solidityPack(sigArgTypes, [ + transferObj.contractAddress, + signer, + transferObj.receiver, + transferObj.ids, + transferObj.amounts, + transferObj.nonce + ]) - it('should REVERT if gas receipt is not passed but isGasFee is set to true and is claimed', async () => { - transferObj.isGasFee = true - data = await encodeMetaBatchTransferFromData(transferObj, domainHash) - const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - true, - data - ) - await expect(tx).to.be.rejectedWith(RevertError()) - }) - }) + // Form bad gas receipt + const badGasReceipt = { ...gasReceipt, gasPrice: 109284123 } - context('When successful transfer', () => { - let tx: ethers.ContractTransaction + const transferData = transferObj.transferData == null ? utils.toUtf8Bytes('') : transferObj.transferData - beforeEach(async () => { - tx = await operatorERC1155Contract.metaSafeBatchTransferFrom( - ownerAddress, - receiverAddress, - ids, - amounts, - isGasReceipt, - data - ) - }) + // Correct and incorrect transferData + const goodGasAndTransferData = ethers.utils.defaultAbiCoder.encode( + [GasReceiptType, 'bytes'], + [gasReceipt, transferData] + ) + const badGasAndTransferData = ethers.utils.defaultAbiCoder.encode( + [GasReceiptType, 'bytes'], + [badGasReceipt, transferData] + ) - it('should correctly update balance of sender and receiver', async () => { - let balanceFrom: BigNumber - let balanceTo: BigNumber + // Encode normally the whole thing + sigData = ethers.utils.solidityPack(['bytes', 'bytes'], [sigData, goodGasAndTransferData]) - for (let i = 0; i < ids.length; i++) { - balanceFrom = await operatorERC1155Contract.balanceOf(ownerAddress, ids[i]) - balanceTo = await operatorERC1155Contract.balanceOf(receiverAddress, ids[i]) + // Get signature + const sig = (await ethSign(transferObj.signerWallet, sigData)).slice(0, -2) + const paddedNonce = ethers.utils.solidityPack(['uint256'], [transferObj.nonce]) + const ethsig_nonce = sig + paddedNonce.slice(2) + '02' // encode packed the nonce - expect(balanceFrom).to.be.eql(BigNumber.from(initBalance - amounts[i])) - expect(balanceTo).to.be.eql(BigNumber.from(amounts[i])) - } - }) + // PASS BAD DATA + data = ethers.utils.defaultAbiCoder.encode(txDataTypes, [ethsig_nonce, badGasAndTransferData]) - describe('When gas is reimbursed', () => { - before(async function() { - if (!condition[1]) { - this.test!.parent!.pending = true - this.skip() - } + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, + ids, + amounts, + isGasReceipt, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - it('should update gas token balance of sender', async () => { - const senderBalance = await operatorERC1155Contract.balanceOf(ownerAddress, feeTokenID) - expect(senderBalance).to.be.eql(feeTokenInitBalance.sub(gasReceipt!.gasFee)) + it('should REVERT if gas receipt is passed, but not claimed', async () => { + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, + ids, + amounts, + false, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - it('should update gas token balance of executor', async () => { - const balance = await operatorERC1155Contract.balanceOf(operatorAddress, feeTokenID) - expect(gasReceipt!.gasFee).to.be.eql(balance.toNumber()) + it('should REVERT if gas receipt is passed but isGasFee is false', async () => { + transferObj.isGasFee = false + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, + ids, + amounts, + true, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - }) - describe('TransferBatch event', async () => { - let filterFromOperatorContract: ethers.EventFilter - let operatorContract: ERC1155OperatorMock + it('should PASS if gas receipt is passed with isGasFee to false and not claimed', async () => { + transferObj.isGasFee = false + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) - beforeEach(async () => { - operatorContract = (await operatorAbstract.deploy(operatorWallet)) as ERC1155OperatorMock + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, + ids, + amounts, + false, + data + ) + await expect(tx).to.be.fulfilled }) - it('should emit 1 TransferBatch events of N transfers', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events![1] - expect(ev.event).to.be.eql('TransferBatch') + describe('When receiver is a contract', () => { + it('should REVERT if gas used in onERC1155BatchReceived exceeds limit', async () => { + const lowGasLimit = 1000 + gasReceipt!.gasLimitCallback = lowGasLimit + transferObj.receiver = receiverContract.address + + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverContract.address, + ids, + amounts, + isGasReceipt, + data, + HIGH_GAS_LIMIT + ) + await expect(tx).to.be.rejectedWith(RevertOutOfGasError()) + }) + + it('should PASS if gas used in onERC1155BatchReceived does not exceed limit', async () => { + const okGasLimit = 160000 + gasReceipt!.gasLimitCallback = okGasLimit + transferObj.receiver = receiverContract.address + + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverContract.address, + ids, + amounts, + isGasReceipt, + data, + HIGH_GAS_LIMIT + ) + await expect(tx).to.be.fulfilled + }) + + it('should PASS if gasLimit is higher than gas sent in transaction', async () => { + const highGasLimit = 3000000 + gasReceipt!.gasLimitCallback = highGasLimit + transferObj.receiver = receiverContract.address + + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverContract.address, + ids, + amounts, + isGasReceipt, + data, + HIGH_GAS_LIMIT + ) + await expect(tx).to.be.fulfilled + }) + }) + }) - const args = ev.args! as any - expect(args._ids.length).to.be.eql(ids.length) + describe('When gas is NOT reimbursed', () => { + before(async function() { + if (condition[1]) { + this.test!.parent!.pending = true + this.skip() + } }) - it('should have `msg.sender` as `_operator` field, not _from', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events!.pop()! + it('should PASS if gas receipt is not passed and not claimed', async () => { + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, + ids, + amounts, + false, + data + ) + await expect(tx).to.be.fulfilled + }) - const args = ev.args! as any - expect(args._operator).to.be.eql(operatorAddress) + it('should REVER if gas receipt is not passed and claimed', async () => { + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, + ids, + amounts, + true, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - it('should have `msg.sender` as `_operator` field, not tx.origin', async () => { - // Get event filter to get internal tx event - filterFromOperatorContract = erc1155Contract.filters.TransferBatch( - operatorContract.address, - null, - null, - null, - null + it('should REVERT if gas receipt is not passed but isGasFee is set to true and is claimed', async () => { + transferObj.isGasFee = true + data = await encodeMetaBatchTransferFromData(transferObj, domainHash) + const tx = operatorERC1155Contract.metaSafeBatchTransferFrom( + ownerAddress, + receiverAddress, + ids, + amounts, + true, + data ) + await expect(tx).to.be.rejectedWith(RevertError()) + }) + }) - //Increment nonce because it's the second transfer - transferObj.nonce = nonce.add(1) - data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + context('When successful transfer', () => { + let tx: ethers.ContractTransaction - // Execute transfer from operator contract - // @ts-ignore (https://github.com/ethereum-ts/TypeChain/issues/118) - await operatorContract.metaSafeBatchTransferFrom( - erc1155Contract.address, + beforeEach(async () => { + tx = await operatorERC1155Contract.metaSafeBatchTransferFrom( ownerAddress, receiverAddress, ids, amounts, isGasReceipt, - data, - HIGH_GAS_LIMIT // INCORRECT GAS ESTIMATION - ) - - // Get logs from internal transaction event - // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) - filterFromOperatorContract.fromBlock = 0 - const logs = await operatorProvider.getLogs(filterFromOperatorContract) - const args = erc1155Contract.interface.decodeEventLog( - erc1155Contract.interface.events['TransferBatch(address,address,address,uint256[],uint256[])'], - logs[0].data, - logs[0].topics + data ) - - // operator arg should be equal to msg.sender, not tx.origin - expect(args._operator).to.be.eql(operatorContract.address) }) - it('should emit NonceChange event', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events![0] - expect(ev.event).to.be.eql('NonceChange') - }) + it('should correctly update balance of sender and receiver', async () => { + let balanceFrom: BigNumber + let balanceTo: BigNumber - it('should have `_signer` as `signer` in NonceChange', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events![0] + for (let i = 0; i < ids.length; i++) { + balanceFrom = await operatorERC1155Contract.balanceOf(ownerAddress, ids[i]) + balanceTo = await operatorERC1155Contract.balanceOf(receiverAddress, ids[i]) - const args = ev.args! as any - expect(args.signer).to.be.eql(ownerWallet.address) + expect(balanceFrom).to.be.eql(BigNumber.from(initBalance - amounts[i])) + expect(balanceTo).to.be.eql(BigNumber.from(amounts[i])) + } }) - it('should have `nonce` as `nonce + 1` in NonceChange', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events![0] + describe('When gas is reimbursed', () => { + before(async function() { + if (!condition[1]) { + this.test!.parent!.pending = true + this.skip() + } + }) + + it('should update gas token balance of sender', async () => { + const senderBalance = await operatorERC1155Contract.balanceOf(ownerAddress, feeTokenID) + expect(senderBalance).to.be.eql(feeTokenInitBalance.sub(gasReceipt!.gasFee)) + }) + + it('should update gas token balance of executor', async () => { + const balance = await operatorERC1155Contract.balanceOf(operatorAddress, feeTokenID) + expect(gasReceipt!.gasFee).to.be.eql(balance.toNumber()) + }) + }) - const args = ev.args! as any - expect(args.newNonce).to.be.eql(nonce.add(1)) + describe('TransferBatch event', async () => { + let filterFromOperatorContract: ethers.EventFilter + let operatorContract: ERC1155OperatorMock + + beforeEach(async () => { + operatorContract = (await operatorAbstract.deploy(operatorWallet)) as ERC1155OperatorMock + }) + + it('should emit 1 TransferBatch events of N transfers', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events![1] + expect(ev.event).to.be.eql('TransferBatch') + + const args = ev.args! as any + expect(args._ids.length).to.be.eql(ids.length) + }) + + it('should have `msg.sender` as `_operator` field, not _from', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events!.pop()! + + const args = ev.args! as any + expect(args._operator).to.be.eql(operatorAddress) + }) + + it('should have `msg.sender` as `_operator` field, not tx.origin', async () => { + // Get event filter to get internal tx event + filterFromOperatorContract = erc1155Contract.filters.TransferBatch( + operatorContract.address, + null, + null, + null, + null + ) + + //Increment nonce because it's the second transfer + transferObj.nonce = nonce.add(1) + data = await encodeMetaBatchTransferFromData(transferObj, domainHash, gasReceipt) + + // Execute transfer from operator contract + // @ts-ignore (https://github.com/ethereum-ts/TypeChain/issues/118) + await operatorContract.metaSafeBatchTransferFrom( + erc1155Contract.address, + ownerAddress, + receiverAddress, + ids, + amounts, + isGasReceipt, + data, + HIGH_GAS_LIMIT // INCORRECT GAS ESTIMATION + ) + + // Get logs from internal transaction event + // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) + filterFromOperatorContract.fromBlock = 0 + const logs = await operatorProvider.getLogs(filterFromOperatorContract) + const args = erc1155Contract.interface.decodeEventLog( + erc1155Contract.interface.events['TransferBatch(address,address,address,uint256[],uint256[])'], + logs[0].data, + logs[0].topics + ) + + // operator arg should be equal to msg.sender, not tx.origin + expect(args._operator).to.be.eql(operatorContract.address) + }) + + it('should emit NonceChange event', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events![0] + expect(ev.event).to.be.eql('NonceChange') + }) + + it('should have `_signer` as `signer` in NonceChange', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events![0] + + const args = ev.args! as any + expect(args.signer).to.be.eql(ownerWallet.address) + }) + + it('should have `nonce` as `nonce + 1` in NonceChange', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events![0] + + const args = ev.args! as any + expect(args.newNonce).to.be.eql(nonce.add(1)) + }) }) }) }) }) }) }) - }) - describe('metaSetApprovalForAll() function', () => { - const initBalance = 100 - let isGasReimbursed = true - const approved = true - const nonce = BigNumber.from(0) - const id = 66 - - let approvalObj: ApprovalSignature - let gasReceipt: GasReceipt | null - let domainHash: string - let data: string - - let isGasReceipt: boolean = true - const feeTokenInitBalance = BigNumber.from(100000000) - - const feeType = 0 - const feeTokenID = 666 - let feeTokenAddress: string - let feeTokenDataERC1155: string | Uint8Array - - const conditions = [ - [true, 'Gas receipt'], - [false, 'No Gas receipt'] - ] - - conditions.forEach(function(condition) { - context(condition[1] as string, () => { - beforeEach(async () => { - isGasReceipt = condition[0] as boolean - - feeTokenAddress = erc1155Contract.address - - feeTokenDataERC1155 = ethers.utils.defaultAbiCoder.encode( - ['address', 'uint256', 'uint8'], - [feeTokenAddress, feeTokenID, feeType] - ) - - // Gas Receipt - gasReceipt = { - gasLimitCallback: 130000, - gasFee: 30000, - feeRecipient: operatorAddress, - feeTokenData: feeTokenDataERC1155 - } - - // Check if gas receipt is included - gasReceipt = isGasReceipt ? gasReceipt : null - isGasReimbursed = isGasReceipt ? true : false - - // Approval Signture Object - approvalObj = { - contractAddress: erc1155Contract.address, - signerWallet: ownerWallet, - operator: operatorAddress, - approved: approved, - isGasFee: isGasReceipt, - nonce: nonce - } - - // Mint tokens - await erc1155Contract.mintMock(ownerAddress, id, initBalance, []) - - // Mint tokens used to pay for gas - await erc1155Contract.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) - - // Domain hash - domainHash = ethers.utils.keccak256( - ethers.utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, erc1155Contract.address]) - ) - - // Data to pass in approval method - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - }) + describe('metaSetApprovalForAll() function', () => { + const initBalance = 100 + let isGasReimbursed = true + const approved = true + const nonce = BigNumber.from(0) + const id = 66 + + let approvalObj: ApprovalSignature + let gasReceipt: GasReceipt | null + let domainHash: string + let data: string + + let isGasReceipt: boolean = true + const feeTokenInitBalance = BigNumber.from(100000000) + + const feeType = 0 + const feeTokenID = 666 + let feeTokenAddress: string + let feeTokenDataERC1155: string | Uint8Array + + const conditions = [ + [true, 'Gas receipt'], + [false, 'No Gas receipt'] + ] + + conditions.forEach(function(condition) { + context(condition[1] as string, () => { + beforeEach(async () => { + isGasReceipt = condition[0] as boolean - it('should PASS if signature is valid', async () => { - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) - await expect(tx).to.be.fulfilled - }) + feeTokenAddress = erc1155Contract.address + + feeTokenDataERC1155 = ethers.utils.defaultAbiCoder.encode( + ['address', 'uint256', 'uint8'], + [feeTokenAddress, feeTokenID, feeType] + ) - describe('ERC-1271 Receiver', () => { - let erc1271WalletValidationMockContract: ERC1271WalletValidationMock - let ERC1271WalletValidationMockAbstract: AbstractContract + // Gas Receipt + gasReceipt = { + gasLimitCallback: 130000, + gasFee: 30000, + feeRecipient: operatorAddress, + feeTokenData: feeTokenDataERC1155 + } - let erc1271WalletAddress + // Check if gas receipt is included + gasReceipt = isGasReceipt ? gasReceipt : null + isGasReimbursed = isGasReceipt ? true : false + + // Approval Signture Object + approvalObj = { + contractAddress: erc1155Contract.address, + signerWallet: ownerWallet, + operator: operatorAddress, + approved: approved, + isGasFee: isGasReceipt, + nonce: nonce + } - beforeEach(async () => { - ERC1271WalletValidationMockAbstract = await AbstractContract.fromArtifactName('ERC1271WalletValidationMock') - erc1271WalletValidationMockContract = (await ERC1271WalletValidationMockAbstract.deploy(ownerWallet, [ - domainHash - ])) as ERC1271WalletValidationMock - erc1271WalletAddress = erc1271WalletValidationMockContract.address + // Mint tokens + await erc1155Contract.mintMock(ownerAddress, id, initBalance, []) - await erc1155Contract.mintMock(erc1271WalletAddress, feeTokenID, feeTokenInitBalance, []) + // Mint tokens used to pay for gas + await erc1155Contract.mintMock(ownerAddress, feeTokenID, feeTokenInitBalance, []) + + // Domain hash + domainHash = ethers.utils.keccak256( + ethers.utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, erc1155Contract.address]) + ) + + // Data to pass in approval method + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) }) - describe(`EIP-1271 (bytes) signatures (03)`, () => { - it('should return REVERT if signature is invalid', async () => { - approvalObj.owner = erc1271WalletAddress - approvalObj.signerWallet = receiverWallet + it('should PASS if signature is valid', async () => { + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) + await expect(tx).to.be.fulfilled + }) - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt, '03') - const tx = operatorERC1155Contract.metaSetApprovalForAll( - erc1271WalletAddress, - operatorAddress, - approved, - isGasReimbursed, - data - ) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) + describe('ERC-1271 Receiver', () => { + let erc1271WalletValidationMockContract: ERC1271WalletValidationMock + let ERC1271WalletValidationMockAbstract: AbstractContract + + let erc1271WalletAddress + + beforeEach(async () => { + ERC1271WalletValidationMockAbstract = await AbstractContract.fromArtifactName('ERC1271WalletValidationMock') + erc1271WalletValidationMockContract = (await ERC1271WalletValidationMockAbstract.deploy(ownerWallet, [ + domainHash + ])) as ERC1271WalletValidationMock + erc1271WalletAddress = erc1271WalletValidationMockContract.address + + await erc1155Contract.mintMock(erc1271WalletAddress, feeTokenID, feeTokenInitBalance, []) + }) + + describe(`EIP-1271 (bytes) signatures (03)`, () => { + it('should return REVERT if signature is invalid', async () => { + approvalObj.owner = erc1271WalletAddress + approvalObj.signerWallet = receiverWallet + + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt, '03') + const tx = operatorERC1155Contract.metaSetApprovalForAll( + erc1271WalletAddress, + operatorAddress, + approved, + isGasReimbursed, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) + }) + }) + + describe(`EIP-1271 (bytes32) signatures (04)`, () => { + it('should return REVERT if signature is invalid', async () => { + approvalObj.owner = erc1271WalletAddress + approvalObj.signerWallet = receiverWallet + + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt, '04') + const tx = operatorERC1155Contract.metaSetApprovalForAll( + erc1271WalletAddress, + operatorAddress, + approved, + isGasReimbursed, + data + ) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) + }) + + it('should PASS if signature is valid', async () => { + approvalObj.owner = erc1271WalletAddress + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt, '04') + const tx = operatorERC1155Contract.metaSetApprovalForAll( + erc1271WalletAddress, + operatorAddress, + approved, + isGasReimbursed, + data + ) + await expect(tx).to.be.fulfilled + }) }) }) - describe(`EIP-1271 (bytes32) signatures (04)`, () => { - it('should return REVERT if signature is invalid', async () => { - approvalObj.owner = erc1271WalletAddress - approvalObj.signerWallet = receiverWallet + describe('When gas is reimbursed', () => { + before(async function() { + if (!condition[0]) { + this.test!.parent!.pending = true + this.skip() + } + }) - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt, '04') - const tx = operatorERC1155Contract.metaSetApprovalForAll( - erc1271WalletAddress, - operatorAddress, - approved, - isGasReimbursed, - data - ) + it('should REVERT if gas receipt is passed, but not claimed', async () => { + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, false, data) await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - it('should PASS if signature is valid', async () => { - approvalObj.owner = erc1271WalletAddress - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt, '04') - const tx = operatorERC1155Contract.metaSetApprovalForAll( - erc1271WalletAddress, - operatorAddress, - approved, - isGasReimbursed, - data - ) - await expect(tx).to.be.fulfilled + it('should REVERT if gas receipt is passed but isGasFee is false', async () => { + approvalObj.isGasFee = false + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, true, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - }) - }) - describe('When gas is reimbursed', () => { - before(async function() { - if (!condition[0]) { - this.test!.parent!.pending = true - this.skip() - } - }) + it('should PASS if gas receipt is passed with isGasFee to false and not claimed', async () => { + approvalObj.isGasFee = false + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - it('should REVERT if gas receipt is passed, but not claimed', async () => { - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, false, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, false, data) + await expect(tx).to.be.fulfilled + }) }) - it('should REVERT if gas receipt is passed but isGasFee is false', async () => { - approvalObj.isGasFee = false - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, true, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) + describe('When gas is NOT reimbursed', () => { + before(async function() { + if (condition[0]) { + this.test!.parent!.pending = true + this.skip() + } + }) - it('should PASS if gas receipt is passed with isGasFee to false and not claimed', async () => { - approvalObj.isGasFee = false - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) + it('should PASS if gas receipt is not passed and not claimed', async () => { + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, false, data) + await expect(tx).to.be.fulfilled + }) - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, false, data) - await expect(tx).to.be.fulfilled - }) - }) + it('should REVER if gas receipt is not passed and claimed', async () => { + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, true, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) + }) - describe('When gas is NOT reimbursed', () => { - before(async function() { - if (condition[0]) { - this.test!.parent!.pending = true - this.skip() - } - }) + it('should REVERT if gas receipt is not passed but isGasFee is set to true and is claimed', async () => { + approvalObj.isGasFee = true + data = await encodeMetaApprovalData(approvalObj, domainHash) - it('should PASS if gas receipt is not passed and not claimed', async () => { - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, false, data) - await expect(tx).to.be.fulfilled + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, true, data) + await expect(tx).to.be.rejectedWith(RevertError()) + }) }) - it('should REVER if gas receipt is not passed and claimed', async () => { - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, true, data) + it('should REVERT if contract address is incorrect', async () => { + domainHash = ethers.utils.keccak256( + ethers.utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, receiverAddress]) + ) + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - it('should REVERT if gas receipt is not passed but isGasFee is set to true and is claimed', async () => { - approvalObj.isGasFee = true - data = await encodeMetaApprovalData(approvalObj, domainHash) + it('should REVERT if operator address is incorrect', async () => { + approvalObj.operator = receiverAddress + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, true, data) - await expect(tx).to.be.rejectedWith(RevertError()) + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) }) - }) - - it('should REVERT if contract address is incorrect', async () => { - domainHash = ethers.utils.keccak256( - ethers.utils.solidityPack(['bytes32', 'uint256'], [DOMAIN_SEPARATOR_TYPEHASH, receiverAddress]) - ) - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) - - it('should REVERT if operator address is incorrect', async () => { - approvalObj.operator = receiverAddress - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) - - it('should REVERT if approved value is incorrect', async () => { - approvalObj.approved = false - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) + it('should REVERT if approved value is incorrect', async () => { + approvalObj.approved = false + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) - }) + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_SIGNATURE')) + }) - it('should REVERT if nonce is incorrect', async () => { - approvalObj.nonce = nonce.add(101) - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - - // Nonce higher - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_NONCE')) - - // Correct nonce - approvalObj.nonce = nonce - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - await operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) - - // Nonce lower - const tx2 = operatorERC1155Contract.metaSetApprovalForAll( - ownerAddress, - operatorAddress, - approved, - isGasReimbursed, - data - ) - await expect(tx2).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_NONCE')) - }) + it('should REVERT if nonce is incorrect', async () => { + approvalObj.nonce = nonce.add(101) + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) - it('should emit an ApprovalForAll event', async () => { - const tx = await operatorERC1155Contract.metaSetApprovalForAll( - ownerAddress, - operatorAddress, - approved, - isGasReimbursed, - data - ) - const receipt = await tx.wait(1) - - expect(receipt.events![1].event).to.be.eql('ApprovalForAll') - }) + // Nonce higher + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_NONCE')) - it('should set the operator status to _status argument', async () => { - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) - await expect(tx).to.be.fulfilled + // Correct nonce + approvalObj.nonce = nonce + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) + await operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) - const status = await erc1155Contract.isApprovedForAll(ownerAddress, operatorAddress) - expect(status).to.be.eql(true) - }) + // Nonce lower + const tx2 = operatorERC1155Contract.metaSetApprovalForAll( + ownerAddress, + operatorAddress, + approved, + isGasReimbursed, + data + ) + await expect(tx2).to.be.rejectedWith(RevertError('ERC1155MetaPackedBalance#_signatureValidation: INVALID_NONCE')) + }) - it('should emit NonceChange event', async () => { - const tx = await operatorERC1155Contract.metaSetApprovalForAll( - ownerAddress, - operatorAddress, - approved, - isGasReimbursed, - data - ) - const receipt = await tx.wait(1) - const ev = receipt.events![0] - expect(ev.event).to.be.eql('NonceChange') - }) + it('should emit an ApprovalForAll event', async () => { + const tx = await operatorERC1155Contract.metaSetApprovalForAll( + ownerAddress, + operatorAddress, + approved, + isGasReimbursed, + data + ) + const receipt = await tx.wait(1) - it('should have `_signer` as `signer` in NonceChange', async () => { - const tx = await operatorERC1155Contract.metaSetApprovalForAll( - ownerAddress, - operatorAddress, - approved, - isGasReimbursed, - data - ) + expect(receipt.events![1].event).to.be.eql('ApprovalForAll') + }) - const receipt = await tx.wait(1) - const ev = receipt.events![0] + it('should set the operator status to _status argument', async () => { + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, approved, isGasReimbursed, data) + await expect(tx).to.be.fulfilled - const args = ev.args! as any - expect(args.signer).to.be.eql(ownerWallet.address) - }) + const status = await erc1155Contract.isApprovedForAll(ownerAddress, operatorAddress) + expect(status).to.be.eql(true) + }) - it('should have `nonce` as `nonce + 1` in NonceChange', async () => { - const tx = await operatorERC1155Contract.metaSetApprovalForAll( - ownerAddress, - operatorAddress, - approved, - isGasReimbursed, - data - ) - const receipt = await tx.wait(1) - const ev = receipt.events![0] - - const args = ev.args! as any - expect(args.newNonce).to.be.eql(nonce.add(1)) - }) + it('should emit NonceChange event', async () => { + const tx = await operatorERC1155Contract.metaSetApprovalForAll( + ownerAddress, + operatorAddress, + approved, + isGasReimbursed, + data + ) + const receipt = await tx.wait(1) + const ev = receipt.events![0] + expect(ev.event).to.be.eql('NonceChange') + }) - context('When the operator was already an operator', () => { - beforeEach(async () => { + it('should have `_signer` as `signer` in NonceChange', async () => { const tx = await operatorERC1155Contract.metaSetApprovalForAll( ownerAddress, operatorAddress, @@ -2237,35 +2225,68 @@ describe('ERC1155MetaPackedBalance', () => { data ) - // Update nonce of approval signature object for subsequent tests - approvalObj.nonce = nonce.add(1) - }) + const receipt = await tx.wait(1) + const ev = receipt.events![0] - it('should leave the operator status to set to true again', async () => { - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) + const args = ev.args! as any + expect(args.signer).to.be.eql(ownerWallet.address) + }) - const tx = operatorERC1155Contract.metaSetApprovalForAll( + it('should have `nonce` as `nonce + 1` in NonceChange', async () => { + const tx = await operatorERC1155Contract.metaSetApprovalForAll( ownerAddress, operatorAddress, approved, isGasReimbursed, data ) - await expect(tx).to.be.fulfilled + const receipt = await tx.wait(1) + const ev = receipt.events![0] - const status = await erc1155Contract.isApprovedForAll(ownerAddress, operatorAddress) - expect(status).to.be.eql(true) + const args = ev.args! as any + expect(args.newNonce).to.be.eql(nonce.add(1)) }) - it('should allow the operator status to be set to false', async () => { - approvalObj.approved = false - data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) + context('When the operator was already an operator', () => { + beforeEach(async () => { + const tx = await operatorERC1155Contract.metaSetApprovalForAll( + ownerAddress, + operatorAddress, + approved, + isGasReimbursed, + data + ) - const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, false, isGasReimbursed, data) - await expect(tx).to.be.fulfilled + // Update nonce of approval signature object for subsequent tests + approvalObj.nonce = nonce.add(1) + }) - const status = await erc1155Contract.isApprovedForAll(operatorAddress, ownerAddress) - expect(status).to.be.eql(false) + it('should leave the operator status to set to true again', async () => { + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSetApprovalForAll( + ownerAddress, + operatorAddress, + approved, + isGasReimbursed, + data + ) + await expect(tx).to.be.fulfilled + + const status = await erc1155Contract.isApprovedForAll(ownerAddress, operatorAddress) + expect(status).to.be.eql(true) + }) + + it('should allow the operator status to be set to false', async () => { + approvalObj.approved = false + data = await encodeMetaApprovalData(approvalObj, domainHash, gasReceipt) + + const tx = operatorERC1155Contract.metaSetApprovalForAll(ownerAddress, operatorAddress, false, isGasReimbursed, data) + await expect(tx).to.be.fulfilled + + const status = await erc1155Contract.isApprovedForAll(operatorAddress, ownerAddress) + expect(status).to.be.eql(false) + }) }) }) }) diff --git a/tests/ERC1155Metadata.spec.ts b/tests/ERC1155Metadata.spec.ts index 2310b50..3771332 100644 --- a/tests/ERC1155Metadata.spec.ts +++ b/tests/ERC1155Metadata.spec.ts @@ -3,7 +3,7 @@ import { ethers } from 'ethers' import { AbstractContract, expect, RevertError, HIGH_GAS_LIMIT } from './utils' import * as utils from './utils' -import { ERC1155MetadataMock, ERC1155MetadataUpgradeableMockV2, ProxyUpgradeableDeployerMock, ProxyUpgradeable } from 'src' +import { ERC1155MetadataMock, ERC1155MetadataUpgradeableMockV2, ProxyUpgradeableDeployerMock } from 'src' // init test wallets from package.json mnemonic import { web3 } from 'hardhat' @@ -31,6 +31,9 @@ usingUpgradeable.forEach(upgradeable => { if (upgradeable) { abstract = await AbstractContract.fromArtifactName('ERC1155MetadataUpgradeableMock') + // Create factory + const factoryAbstract = await AbstractContract.fromArtifactName('ProxyUpgradeableDeployerMock') + factoryContract = (await factoryAbstract.deploy(ownerWallet, [])) as ProxyUpgradeableDeployerMock } else { abstract = await AbstractContract.fromArtifactName('ERC1155MetadataMock') } @@ -40,10 +43,6 @@ usingUpgradeable.forEach(upgradeable => { if (upgradeable) { erc1155MetadataContract = (await abstract.deploy(ownerWallet, [])) as ERC1155MetadataMock - // Create factory - const factoryAbstract = await AbstractContract.fromArtifactName('ProxyUpgradeableDeployerMock') - factoryContract = (await factoryAbstract.deploy(ownerWallet, [])) as ProxyUpgradeableDeployerMock - // Create proxy let tx = factoryContract.createProxy(erc1155MetadataContract.address, ethers.constants.HashZero, ownerWallet.address); await expect(tx).to.be.fulfilled diff --git a/tests/ERC1155MintBurn.spec.ts b/tests/ERC1155MintBurn.spec.ts index bb70143..e457d3f 100644 --- a/tests/ERC1155MintBurn.spec.ts +++ b/tests/ERC1155MintBurn.spec.ts @@ -1,450 +1,464 @@ -import { ethers } from 'ethers' +import { constants, ethers } from 'ethers' import { AbstractContract, expect, RevertError, BigNumber, RevertUnsafeMathError, HIGH_GAS_LIMIT } from './utils' import * as utils from './utils' -import { ERC1155MetaMintBurnMock, ERC1155ReceiverMock } from 'src' +import { ERC1155MetaMintBurnMock, ERC1155ReceiverMock, ProxyUpgradeableDeployerMock } from 'src' // init test wallets from package.json mnemonic import { web3 } from 'hardhat' -const { wallet: ownerWallet, provider: ownerProvider, signer: ownerSigner } = utils.createTestWallet(web3, 1) - -const { wallet: receiverWallet, provider: receiverProvider, signer: receiverSigner } = utils.createTestWallet(web3, 2) - -const { wallet: anyoneWallet, provider: anyoneProvider, signer: anyoneSigner } = utils.createTestWallet(web3, 3) - -const { wallet: operatorWallet, provider: operatorProvider, signer: operatorSigner } = utils.createTestWallet(web3, 4) - -describe('ERC1155MintBurn', () => { - const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' - const NAME = 'MyERC1155' - const METADATA_URI = 'https://example.com/' - - let ownerAddress: string - let receiverAddress: string - let anyoneAddress: string - let operatorAddress: string - - let erc1155MintBurnContract: ERC1155MetaMintBurnMock - let anyoneERC1155MintBurnContract: ERC1155MetaMintBurnMock - let receiverContract: ERC1155ReceiverMock - - context('When ERC1155MintBurn contract is deployed', () => { - before(async () => { - ownerAddress = await ownerWallet.getAddress() - receiverAddress = await receiverWallet.getAddress() - anyoneAddress = await anyoneWallet.getAddress() - operatorAddress = await operatorWallet.getAddress() - }) - - beforeEach(async () => { - const abstractReceiver = await AbstractContract.fromArtifactName('ERC1155ReceiverMock') - receiverContract = (await abstractReceiver.deploy(ownerWallet)) as ERC1155ReceiverMock - - const abstract = await AbstractContract.fromArtifactName('ERC1155MetaMintBurnMock') - erc1155MintBurnContract = (await abstract.deploy(ownerWallet, [NAME, METADATA_URI])) as ERC1155MetaMintBurnMock - anyoneERC1155MintBurnContract = (await erc1155MintBurnContract.connect(anyoneSigner)) as ERC1155MetaMintBurnMock - }) - - describe('_mint() function', () => { - const tokenID = 666 - const amount = 11 - - it('should ALLOW inheriting contract to call _mint()', async () => { - const tx = erc1155MintBurnContract.mintMock(receiverAddress, tokenID, amount, []) - await expect(tx).to.be.fulfilled +const { wallet: ownerWallet, provider: ownerProvider } = utils.createTestWallet(web3, 1) +const { wallet: receiverWallet } = utils.createTestWallet(web3, 2) +const { wallet: anyoneWallet, signer: anyoneSigner } = utils.createTestWallet(web3, 3) + +const usingUpgradeable = [false, true] + +usingUpgradeable.forEach(upgradeable => { + describe('ERC1155MintBurn' + (upgradeable ? 'Upgradeable': ''), () => { + const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' + const NAME = 'MyERC1155' + const METADATA_URI = 'https://example.com/' + + let receiverAddress: string + + let abstract: AbstractContract + let erc1155MintBurnContract: ERC1155MetaMintBurnMock + let anyoneERC1155MintBurnContract: ERC1155MetaMintBurnMock + let receiverContract: ERC1155ReceiverMock + let factoryContract: ProxyUpgradeableDeployerMock + + context('When ERC1155MintBurn contract is deployed', () => { + before(async () => { + receiverAddress = await receiverWallet.getAddress() + if (upgradeable) { + abstract = await AbstractContract.fromArtifactName('ERC1155MetaMintBurnUpgradeableMock') + // Create factory + const factoryAbstract = await AbstractContract.fromArtifactName('ProxyUpgradeableDeployerMock') + factoryContract = (await factoryAbstract.deploy(ownerWallet, [])) as ProxyUpgradeableDeployerMock + } else { + abstract = await AbstractContract.fromArtifactName('ERC1155MetaMintBurnMock') + } }) - it('should NOT allow anyone to call _mint()', async () => { - const transaction = { - to: erc1155MintBurnContract.address, - data: - '0x7776afa0000000000000000000000000b87213121fb89cbd8b877cb1bb3ff84dd2869cfa' + - '000000000000000000000000000000000000000000000000000000000000029a0000000000000000' + - '00000000000000000000000000000000000000000000000b' + beforeEach(async () => { + const abstractReceiver = await AbstractContract.fromArtifactName('ERC1155ReceiverMock') + receiverContract = (await abstractReceiver.deploy(ownerWallet)) as ERC1155ReceiverMock + + if (upgradeable) { + erc1155MintBurnContract = (await abstract.deploy(ownerWallet, [])) as ERC1155MetaMintBurnMock + // Create proxy + let tx = factoryContract.createProxy(erc1155MintBurnContract.address, constants.HashZero, ownerWallet.address); + await expect(tx).to.be.fulfilled + const proxyAddr = await factoryContract.predictProxyAddress(erc1155MintBurnContract.address, constants.HashZero, ownerWallet.address); + erc1155MintBurnContract = (await abstract.connect(ownerWallet, proxyAddr)) as ERC1155MetaMintBurnMock; + tx = erc1155MintBurnContract.initialize(NAME, METADATA_URI) + await expect(tx).to.be.fulfilled + } else { + erc1155MintBurnContract = (await abstract.deploy(ownerWallet, [NAME, METADATA_URI])) as ERC1155MetaMintBurnMock } - const tx = anyoneWallet.sendTransaction(transaction) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnMock: INVALID_METHOD')) + anyoneERC1155MintBurnContract = (await erc1155MintBurnContract.connect(anyoneSigner)) as ERC1155MetaMintBurnMock }) - it('should increase the balance of receiver by the right amount', async () => { - const recipientBalanceA = await erc1155MintBurnContract.balanceOf(receiverAddress, tokenID) - await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, amount, []) + describe('_mint() function', () => { + const tokenID = 666 + const amount = 11 - const recipientBalanceB = await erc1155MintBurnContract.balanceOf(receiverAddress, tokenID) + it('should ALLOW inheriting contract to call _mint()', async () => { + const tx = erc1155MintBurnContract.mintMock(receiverAddress, tokenID, amount, []) + await expect(tx).to.be.fulfilled + }) - expect(recipientBalanceB).to.be.eql(recipientBalanceA.add(amount)) - }) + it('should NOT allow anyone to call _mint()', async () => { + const transaction = { + to: erc1155MintBurnContract.address, + data: + '0x7776afa0000000000000000000000000b87213121fb89cbd8b877cb1bb3ff84dd2869cfa' + + '000000000000000000000000000000000000000000000000000000000000029a0000000000000000' + + '00000000000000000000000000000000000000000000000b' + } + const tx = anyoneWallet.sendTransaction(transaction) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnMock: INVALID_METHOD')) + }) - it('should REVERT if added amount leads to overflow', async () => { - const val = BigNumber.from(2) - .pow(256) - .sub(1) - await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, val, []) - const tx = erc1155MintBurnContract.mintMock(receiverAddress, tokenID, 1, [], HIGH_GAS_LIMIT) - await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) - }) + it('should increase the balance of receiver by the right amount', async () => { + const recipientBalanceA = await erc1155MintBurnContract.balanceOf(receiverAddress, tokenID) + await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, amount, []) - it('should REVERT when sending to non-receiver contract', async () => { - const tx = erc1155MintBurnContract.mintMock(erc1155MintBurnContract.address, tokenID, amount, []) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnMock: INVALID_METHOD')) - }) + const recipientBalanceB = await erc1155MintBurnContract.balanceOf(receiverAddress, tokenID) - it('should REVERT if invalid response from receiver contract', async () => { - await receiverContract.setShouldReject(true) + expect(recipientBalanceB).to.be.eql(recipientBalanceA.add(amount)) + }) - const tx = erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, []) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155#_callonERC1155Received: INVALID_ON_RECEIVE_MESSAGE')) - }) + it('should REVERT if added amount leads to overflow', async () => { + const val = BigNumber.from(2) + .pow(256) + .sub(1) + await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, val, []) + const tx = erc1155MintBurnContract.mintMock(receiverAddress, tokenID, 1, [], HIGH_GAS_LIMIT) + await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) + }) - it('should pass if valid response from receiver contract', async () => { - const tx = erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, []) - await expect(tx).to.be.fulfilled - }) + it('should REVERT when sending to non-receiver contract', async () => { + const tx = erc1155MintBurnContract.mintMock(erc1155MintBurnContract.address, tokenID, amount, []) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnMock: INVALID_METHOD')) + }) - it('should pass if data is not null to receiver contract', async () => { - const data = ethers.utils.toUtf8Bytes('Hello from the other side') + it('should REVERT if invalid response from receiver contract', async () => { + await receiverContract.setShouldReject(true) - // NOTE: typechain generates the wrong type for `bytes` type at this time - // see https://github.com/ethereum-ts/TypeChain/issues/123 - // @ts-ignore - const tx = erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, data) - await expect(tx).to.be.fulfilled - }) + const tx = erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, []) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155#_callonERC1155Received: INVALID_ON_RECEIVE_MESSAGE')) + }) + + it('should pass if valid response from receiver contract', async () => { + const tx = erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, []) + await expect(tx).to.be.fulfilled + }) - it('should have balances updated before onERC1155Received is called', async () => { - const toPreBalance = await erc1155MintBurnContract.balanceOf(receiverContract.address, tokenID) + it('should pass if data is not null to receiver contract', async () => { + const data = ethers.utils.toUtf8Bytes('Hello from the other side') - // Get event filter to get internal tx event - const filterFromReceiverContract = receiverContract.filters.TransferSingleReceiver(null, null, null, null) + // NOTE: typechain generates the wrong type for `bytes` type at this time + // see https://github.com/ethereum-ts/TypeChain/issues/123 + // @ts-ignore + const tx = erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, data) + await expect(tx).to.be.fulfilled + }) - await erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, []) + it('should have balances updated before onERC1155Received is called', async () => { + const toPreBalance = await erc1155MintBurnContract.balanceOf(receiverContract.address, tokenID) - // Get logs from internal transaction event - // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) - filterFromReceiverContract.fromBlock = 0 + // Get event filter to get internal tx event + const filterFromReceiverContract = receiverContract.filters.TransferSingleReceiver(null, null, null, null) - const logs = await ownerProvider.getLogs(filterFromReceiverContract) - const args = receiverContract.interface.decodeEventLog( - receiverContract.interface.events['TransferSingleReceiver(address,address,uint256,uint256)'], - logs[0].data, - logs[0].topics - ) + await erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, []) - expect(args._from).to.be.eql(ZERO_ADDRESS) - expect(args._to).to.be.eql(receiverContract.address) - expect(args._toBalance).to.be.eql(toPreBalance.add(amount)) - }) + // Get logs from internal transaction event + // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) + filterFromReceiverContract.fromBlock = 0 - it('should have TransferSingle event emitted before onERC1155Received is called', async () => { - // Get event filter to get internal tx event - const tx = await erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, []) - const receipt = await tx.wait(1) + const logs = await ownerProvider.getLogs(filterFromReceiverContract) + const args = receiverContract.interface.decodeEventLog( + receiverContract.interface.events['TransferSingleReceiver(address,address,uint256,uint256)'], + logs[0].data, + logs[0].topics + ) + + expect(args._from).to.be.eql(ZERO_ADDRESS) + expect(args._to).to.be.eql(receiverContract.address) + expect(args._toBalance).to.be.eql(toPreBalance.add(amount)) + }) - const firstEventTopic = receipt.logs![0].topics[0] - const secondEventTopic = receipt.logs![1].topics[0] + it('should have TransferSingle event emitted before onERC1155Received is called', async () => { + // Get event filter to get internal tx event + const tx = await erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, []) + const receipt = await tx.wait(1) - expect(firstEventTopic).to.be.equal( - erc1155MintBurnContract.interface.getEventTopic( - erc1155MintBurnContract.interface.events['TransferSingle(address,address,address,uint256,uint256)'] + const firstEventTopic = receipt.logs![0].topics[0] + const secondEventTopic = receipt.logs![1].topics[0] + + expect(firstEventTopic).to.be.equal( + erc1155MintBurnContract.interface.getEventTopic( + erc1155MintBurnContract.interface.events['TransferSingle(address,address,address,uint256,uint256)'] + ) ) - ) - expect(secondEventTopic).to.be.equal( - receiverContract.interface.getEventTopic( - receiverContract.interface.events['TransferSingleReceiver(address,address,uint256,uint256)'] + expect(secondEventTopic).to.be.equal( + receiverContract.interface.getEventTopic( + receiverContract.interface.events['TransferSingleReceiver(address,address,uint256,uint256)'] + ) ) - ) - }) + }) - it('should emit a Transfer event', async () => { - const tx = await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, amount, []) - const receipt = await tx.wait(1) + it('should emit a Transfer event', async () => { + const tx = await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, amount, []) + const receipt = await tx.wait(1) - const ev = receipt.events![0] - expect(ev.event).to.be.eql('TransferSingle') - }) + const ev = receipt.events![0] + expect(ev.event).to.be.eql('TransferSingle') + }) - it('should have 0x0 as `from` argument in Transfer event', async () => { - const tx = await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, amount, []) - const receipt = await tx.wait(1) + it('should have 0x0 as `from` argument in Transfer event', async () => { + const tx = await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, amount, []) + const receipt = await tx.wait(1) - // TODO: this form can be improved eventually as ethers improves its api - // or we write a wrapper function to parse the tx - const ev = receipt.events![0] - const args = ev.args! as any + // TODO: this form can be improved eventually as ethers improves its api + // or we write a wrapper function to parse the tx + const ev = receipt.events![0] + const args = ev.args! as any - expect(args._from).to.be.eql(ZERO_ADDRESS) + expect(args._from).to.be.eql(ZERO_ADDRESS) + }) }) - }) - - describe('_batchMint() function', () => { - const Ntypes = 32 - const amountToMint = 10 - const typesArray = Array.apply(null, { length: Ntypes }).map(Number.call, Number) - const amountArray = Array.apply(null, Array(Ntypes)).map(Number.prototype.valueOf, amountToMint) - it('should ALLOW inheriting contract to call _batchMint()', async () => { - const req = erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, amountArray, []) - await expect(req).to.be.fulfilled - }) + describe('_batchMint() function', () => { + const Ntypes = 32 + const amountToMint = 10 + const typesArray = Array.apply(null, { length: Ntypes }).map(Number.call, Number) + const amountArray = Array.apply(null, Array(Ntypes)).map(Number.prototype.valueOf, amountToMint) - it('should PASS if arrays are empty', async () => { - const tx = erc1155MintBurnContract.batchMintMock(receiverAddress, [], [], []) - await expect(tx).to.be.fulfilled - }) + it('should ALLOW inheriting contract to call _batchMint()', async () => { + const req = erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, amountArray, []) + await expect(req).to.be.fulfilled + }) - it('should NOT allow anyone to call _batchMint()', async () => { - const transaction = { - to: erc1155MintBurnContract.address, - data: - '0x2589aeae00000000000000000000000035ef07393b57464e93deb59175ff72e6499450cf' + - '00000000000000000000000000000000000000000000000000000000000000600000000000000000' + - '0000000000000000000000000000000000000000000000c000000000000000000000000000000000' + - '00000000000000000000000000000002000000000000000000000000000000000000000000000000' + - '00000000000000010000000000000000000000000000000000000000000000000000000000000002' + - '00000000000000000000000000000000000000000000000000000000000000020000000000000000' + - '00000000000000000000000000000000000000000000000a00000000000000000000000000000000' + - '0000000000000000000000000000000a' - } + it('should PASS if arrays are empty', async () => { + const tx = erc1155MintBurnContract.batchMintMock(receiverAddress, [], [], []) + await expect(tx).to.be.fulfilled + }) - const tx = anyoneWallet.sendTransaction(transaction) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnMock: INVALID_METHOD')) - }) + it('should NOT allow anyone to call _batchMint()', async () => { + const transaction = { + to: erc1155MintBurnContract.address, + data: + '0x2589aeae00000000000000000000000035ef07393b57464e93deb59175ff72e6499450cf' + + '00000000000000000000000000000000000000000000000000000000000000600000000000000000' + + '0000000000000000000000000000000000000000000000c000000000000000000000000000000000' + + '00000000000000000000000000000002000000000000000000000000000000000000000000000000' + + '00000000000000010000000000000000000000000000000000000000000000000000000000000002' + + '00000000000000000000000000000000000000000000000000000000000000020000000000000000' + + '00000000000000000000000000000000000000000000000a00000000000000000000000000000000' + + '0000000000000000000000000000000a' + } + + const tx = anyoneWallet.sendTransaction(transaction) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnMock: INVALID_METHOD')) + }) - it('should increase the balances of receiver by the right amounts', async () => { - await erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, amountArray, []) + it('should increase the balances of receiver by the right amounts', async () => { + await erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, amountArray, []) - for (let i = 0; i < typesArray.length; i++) { - const balanceTo = await erc1155MintBurnContract.balanceOf(receiverAddress, typesArray[i]) - expect(balanceTo).to.be.eql(BigNumber.from(amountArray[i])) - } - }) + for (let i = 0; i < typesArray.length; i++) { + const balanceTo = await erc1155MintBurnContract.balanceOf(receiverAddress, typesArray[i]) + expect(balanceTo).to.be.eql(BigNumber.from(amountArray[i])) + } + }) - it('should REVERT when sending to non-receiver contract', async () => { - const tx = erc1155MintBurnContract.batchMintMock(erc1155MintBurnContract.address, typesArray, amountArray, [], { - gasLimit: 2000000 + it('should REVERT when sending to non-receiver contract', async () => { + const tx = erc1155MintBurnContract.batchMintMock(erc1155MintBurnContract.address, typesArray, amountArray, [], { + gasLimit: 2000000 + }) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnMock: INVALID_METHOD')) }) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnMock: INVALID_METHOD')) - }) - it('should REVERT if invalid response from receiver contract', async () => { - await receiverContract.setShouldReject(true) - const tx = erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, [], { - gasLimit: 2000000 + it('should REVERT if invalid response from receiver contract', async () => { + await receiverContract.setShouldReject(true) + const tx = erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, [], { + gasLimit: 2000000 + }) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155#_callonERC1155BatchReceived: INVALID_ON_RECEIVE_MESSAGE')) }) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155#_callonERC1155BatchReceived: INVALID_ON_RECEIVE_MESSAGE')) - }) - it('should pass if valid response from receiver contract', async () => { - const tx = erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, [], { - gasLimit: 2000000 + it('should pass if valid response from receiver contract', async () => { + const tx = erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, [], { + gasLimit: 2000000 + }) + await expect(tx).to.be.fulfilled }) - await expect(tx).to.be.fulfilled - }) - it('should pass if data is not null from receiver contract', async () => { - const data = ethers.utils.toUtf8Bytes('Hello from the other side') + it('should pass if data is not null from receiver contract', async () => { + const data = ethers.utils.toUtf8Bytes('Hello from the other side') - // TODO: remove ts-ignore when contract declaration is fixed - // @ts-ignore - const tx = erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, data, { - gasLimit: 2000000 + // TODO: remove ts-ignore when contract declaration is fixed + // @ts-ignore + const tx = erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, data, { + gasLimit: 2000000 + }) + await expect(tx).to.be.fulfilled }) - await expect(tx).to.be.fulfilled - }) - - it('should have balances updated before onERC1155BatchReceived is called', async () => { - const toAddresses = Array(typesArray.length).fill(receiverContract.address) - const toPreBalances = await erc1155MintBurnContract.balanceOfBatch(toAddresses, typesArray) + it('should have balances updated before onERC1155BatchReceived is called', async () => { + const toAddresses = Array(typesArray.length).fill(receiverContract.address) - // Get event filter to get internal tx event - const filterFromReceiverContract = receiverContract.filters.TransferBatchReceiver(null, null, null, null) + const toPreBalances = await erc1155MintBurnContract.balanceOfBatch(toAddresses, typesArray) - await erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, [], { gasLimit: 2000000 }) + // Get event filter to get internal tx event + const filterFromReceiverContract = receiverContract.filters.TransferBatchReceiver(null, null, null, null) - // Get logs from internal transaction event - // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) - filterFromReceiverContract.fromBlock = 0 + await erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, [], { gasLimit: 2000000 }) - const logs = await ownerProvider.getLogs(filterFromReceiverContract) - const args = receiverContract.interface.decodeEventLog( - receiverContract.interface.events['TransferBatchReceiver(address,address,uint256[],uint256[])'], - logs[0].data, - logs[0].topics - ) + // Get logs from internal transaction event + // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) + filterFromReceiverContract.fromBlock = 0 - expect(args._from).to.be.eql(ZERO_ADDRESS) - expect(args._to).to.be.eql(receiverContract.address) - for (let i = 0; i < typesArray.length; i++) { - expect(args._toBalances[i]).to.be.eql(toPreBalances[i].add(amountArray[i])) - } - }) + const logs = await ownerProvider.getLogs(filterFromReceiverContract) + const args = receiverContract.interface.decodeEventLog( + receiverContract.interface.events['TransferBatchReceiver(address,address,uint256[],uint256[])'], + logs[0].data, + logs[0].topics + ) - it('should have TransferBatch event emitted before onERC1155BatchReceived is called', async () => { - // Get event filter to get internal tx event - const tx = await erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, [], { - gasLimit: 2000000 + expect(args._from).to.be.eql(ZERO_ADDRESS) + expect(args._to).to.be.eql(receiverContract.address) + for (let i = 0; i < typesArray.length; i++) { + expect(args._toBalances[i]).to.be.eql(toPreBalances[i].add(amountArray[i])) + } }) - const receipt = await tx.wait(1) - const firstEventTopic = receipt.logs![0].topics[0] - const secondEventTopic = receipt.logs![1].topics[0] + it('should have TransferBatch event emitted before onERC1155BatchReceived is called', async () => { + // Get event filter to get internal tx event + const tx = await erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, [], { + gasLimit: 2000000 + }) + const receipt = await tx.wait(1) + + const firstEventTopic = receipt.logs![0].topics[0] + const secondEventTopic = receipt.logs![1].topics[0] - expect(firstEventTopic).to.be.equal( - erc1155MintBurnContract.interface.getEventTopic( - erc1155MintBurnContract.interface.events['TransferBatch(address,address,address,uint256[],uint256[])'] + expect(firstEventTopic).to.be.equal( + erc1155MintBurnContract.interface.getEventTopic( + erc1155MintBurnContract.interface.events['TransferBatch(address,address,address,uint256[],uint256[])'] + ) ) - ) - expect(secondEventTopic).to.be.equal( - receiverContract.interface.getEventTopic( - receiverContract.interface.events['TransferBatchReceiver(address,address,uint256[],uint256[])'] + expect(secondEventTopic).to.be.equal( + receiverContract.interface.getEventTopic( + receiverContract.interface.events['TransferBatchReceiver(address,address,uint256[],uint256[])'] + ) ) - ) - }) + }) - it('should emit 1 Transfer events of N transfers', async () => { - const tx = await erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, amountArray, []) - const receipt = await tx.wait() - const ev = receipt.events![0] - expect(ev.event).to.be.eql('TransferBatch') + it('should emit 1 Transfer events of N transfers', async () => { + const tx = await erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, amountArray, []) + const receipt = await tx.wait() + const ev = receipt.events![0] + expect(ev.event).to.be.eql('TransferBatch') - const args = ev.args! as any - expect(args._ids.length).to.be.eql(typesArray.length) - }) + const args = ev.args! as any + expect(args._ids.length).to.be.eql(typesArray.length) + }) - it('should have 0x0 as `from` argument in Transfer events', async () => { - const tx = await erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, amountArray, []) - const receipt = await tx.wait() - const args = receipt.events![0].args! as any - expect(args._from).to.be.eql(ZERO_ADDRESS) + it('should have 0x0 as `from` argument in Transfer events', async () => { + const tx = await erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, amountArray, []) + const receipt = await tx.wait() + const args = receipt.events![0].args! as any + expect(args._from).to.be.eql(ZERO_ADDRESS) + }) }) - }) - describe('_burn() function', () => { - const tokenID = 666 - const initBalance = 100 - const amountToBurn = 10 + describe('_burn() function', () => { + const tokenID = 666 + const initBalance = 100 + const amountToBurn = 10 - beforeEach(async () => { - await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, initBalance, []) - }) + beforeEach(async () => { + await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, initBalance, []) + }) - it('should ALLOW inheriting contract to call _burn()', async () => { - const tx = erc1155MintBurnContract.burnMock(receiverAddress, tokenID, amountToBurn) - await expect(tx).to.be.fulfilled - }) + it('should ALLOW inheriting contract to call _burn()', async () => { + const tx = erc1155MintBurnContract.burnMock(receiverAddress, tokenID, amountToBurn) + await expect(tx).to.be.fulfilled + }) - it('should NOT allow anyone to call _burn()', async () => { - const transaction = { - to: erc1155MintBurnContract.address, - data: - '0x464a5ffb00000000000000000000000008970fed061e7747cd9a38d680a601510cb659fb' + - '000000000000000000000000000000000000000000000000000000000000029a0000000000000000' + - '00000000000000000000000000000000000000000000000a' - } - const tx = anyoneWallet.sendTransaction(transaction) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnMock: INVALID_METHOD')) - }) + it('should NOT allow anyone to call _burn()', async () => { + const transaction = { + to: erc1155MintBurnContract.address, + data: + '0x464a5ffb00000000000000000000000008970fed061e7747cd9a38d680a601510cb659fb' + + '000000000000000000000000000000000000000000000000000000000000029a0000000000000000' + + '00000000000000000000000000000000000000000000000a' + } + const tx = anyoneWallet.sendTransaction(transaction) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnMock: INVALID_METHOD')) + }) - it('should decrease the balance of receiver by the right amount', async () => { - const recipientBalanceA = await erc1155MintBurnContract.balanceOf(receiverAddress, tokenID) - await erc1155MintBurnContract.burnMock(receiverAddress, tokenID, amountToBurn) + it('should decrease the balance of receiver by the right amount', async () => { + const recipientBalanceA = await erc1155MintBurnContract.balanceOf(receiverAddress, tokenID) + await erc1155MintBurnContract.burnMock(receiverAddress, tokenID, amountToBurn) - const recipientBalanceB = await erc1155MintBurnContract.balanceOf(receiverAddress, tokenID) + const recipientBalanceB = await erc1155MintBurnContract.balanceOf(receiverAddress, tokenID) - expect(recipientBalanceB).to.be.eql(recipientBalanceA.sub(amountToBurn)) - }) + expect(recipientBalanceB).to.be.eql(recipientBalanceA.sub(amountToBurn)) + }) - it('should REVERT if amount is hgher than balance', async () => { - const invalidVal = initBalance + 1 - const tx = erc1155MintBurnContract.burnMock(receiverAddress, tokenID, invalidVal, HIGH_GAS_LIMIT) - await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) - }) + it('should REVERT if amount is hgher than balance', async () => { + const invalidVal = initBalance + 1 + const tx = erc1155MintBurnContract.burnMock(receiverAddress, tokenID, invalidVal, HIGH_GAS_LIMIT) + await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) + }) - it('should emit a Transfer event', async () => { - const tx = await erc1155MintBurnContract.burnMock(receiverAddress, tokenID, amountToBurn) - const receipt = await tx.wait(1) + it('should emit a Transfer event', async () => { + const tx = await erc1155MintBurnContract.burnMock(receiverAddress, tokenID, amountToBurn) + const receipt = await tx.wait(1) - const ev = receipt.events![0] - expect(ev.event).to.be.eql('TransferSingle') - }) + const ev = receipt.events![0] + expect(ev.event).to.be.eql('TransferSingle') + }) - it('should have 0x0 as `to` argument in Transfer event', async () => { - const tx = await erc1155MintBurnContract.burnMock(receiverAddress, tokenID, amountToBurn) - const receipt = await tx.wait(1) + it('should have 0x0 as `to` argument in Transfer event', async () => { + const tx = await erc1155MintBurnContract.burnMock(receiverAddress, tokenID, amountToBurn) + const receipt = await tx.wait(1) - // TODO: this form can be improved eventually as ethers improves its api - // or we write a wrapper function to parse the tx - const ev = receipt.events![0] - const args = ev.args! as any + // TODO: this form can be improved eventually as ethers improves its api + // or we write a wrapper function to parse the tx + const ev = receipt.events![0] + const args = ev.args! as any - expect(args._to).to.be.eql(ZERO_ADDRESS) + expect(args._to).to.be.eql(ZERO_ADDRESS) + }) }) - }) - describe('batchBurn() function', () => { - const Ntypes = 32 - const initBalance = 100 - const amountToBurn = 30 - const typesArray = Array.apply(null, { length: Ntypes }).map(Number.call, Number) - const burnAmountArray = Array.apply(null, Array(Ntypes)).map(Number.prototype.valueOf, amountToBurn) - const initBalanceArray = Array.apply(null, Array(Ntypes)).map(Number.prototype.valueOf, initBalance) + describe('batchBurn() function', () => { + const Ntypes = 32 + const initBalance = 100 + const amountToBurn = 30 + const typesArray = Array.apply(null, { length: Ntypes }).map(Number.call, Number) + const burnAmountArray = Array.apply(null, Array(Ntypes)).map(Number.prototype.valueOf, amountToBurn) + const initBalanceArray = Array.apply(null, Array(Ntypes)).map(Number.prototype.valueOf, initBalance) - beforeEach(async () => { - await erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, initBalanceArray, []) - }) + beforeEach(async () => { + await erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, initBalanceArray, []) + }) - it('should ALLOW inheriting contract to call _batchBurn()', async () => { - const req = erc1155MintBurnContract.batchBurnMock(receiverAddress, typesArray, burnAmountArray) - const tx = (await expect(req).to.be.fulfilled) as ethers.ContractTransaction - // const receipt = await tx.wait() - // console.log('Batch mint :' + receipt.gasUsed) - }) + it('should ALLOW inheriting contract to call _batchBurn()', async () => { + const req = erc1155MintBurnContract.batchBurnMock(receiverAddress, typesArray, burnAmountArray) + const tx = (await expect(req).to.be.fulfilled) as ethers.ContractTransaction + // const receipt = await tx.wait() + // console.log('Batch mint :' + receipt.gasUsed) + }) - // Should call mock's fallback function - it('should NOT allow anyone to call _batchBurn()', async () => { - const transaction = { - to: erc1155MintBurnContract.address, - data: - '0xb389c3bb000000000000000000000000dc04977a2078c8ffdf086d618d1f961b6c546222' + - '00000000000000000000000000000000000000000000000000000000000000600000000000000000' + - '0000000000000000000000000000000000000000000000c000000000000000000000000000000000' + - '00000000000000000000000000000002000000000000000000000000000000000000000000000000' + - '00000000000000010000000000000000000000000000000000000000000000000000000000000003' + - '00000000000000000000000000000000000000000000000000000000000000020000000000000000' + - '00000000000000000000000000000000000000000000001e00000000000000000000000000000000' + - '0000000000000000000000000000001e' - } - const tx = anyoneWallet.sendTransaction(transaction) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnMock: INVALID_METHOD')) - }) + // Should call mock's fallback function + it('should NOT allow anyone to call _batchBurn()', async () => { + const transaction = { + to: erc1155MintBurnContract.address, + data: + '0xb389c3bb000000000000000000000000dc04977a2078c8ffdf086d618d1f961b6c546222' + + '00000000000000000000000000000000000000000000000000000000000000600000000000000000' + + '0000000000000000000000000000000000000000000000c000000000000000000000000000000000' + + '00000000000000000000000000000002000000000000000000000000000000000000000000000000' + + '00000000000000010000000000000000000000000000000000000000000000000000000000000003' + + '00000000000000000000000000000000000000000000000000000000000000020000000000000000' + + '00000000000000000000000000000000000000000000001e00000000000000000000000000000000' + + '0000000000000000000000000000001e' + } + const tx = anyoneWallet.sendTransaction(transaction) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnMock: INVALID_METHOD')) + }) - it('should decrease the balances of receiver by the right amounts', async () => { - await erc1155MintBurnContract.batchBurnMock(receiverAddress, typesArray, burnAmountArray) + it('should decrease the balances of receiver by the right amounts', async () => { + await erc1155MintBurnContract.batchBurnMock(receiverAddress, typesArray, burnAmountArray) - for (let i = 0; i < typesArray.length; i++) { - const balanceTo = await erc1155MintBurnContract.balanceOf(receiverAddress, typesArray[i]) - expect(balanceTo).to.be.eql(BigNumber.from(initBalance - burnAmountArray[i])) - } - }) + for (let i = 0; i < typesArray.length; i++) { + const balanceTo = await erc1155MintBurnContract.balanceOf(receiverAddress, typesArray[i]) + expect(balanceTo).to.be.eql(BigNumber.from(initBalance - burnAmountArray[i])) + } + }) - it('should emit 1 Transfer events of N transfers', async () => { - const tx = await erc1155MintBurnContract.batchBurnMock(receiverAddress, typesArray, burnAmountArray) - const receipt = await tx.wait() - const ev = receipt.events![0] - expect(ev.event).to.be.eql('TransferBatch') + it('should emit 1 Transfer events of N transfers', async () => { + const tx = await erc1155MintBurnContract.batchBurnMock(receiverAddress, typesArray, burnAmountArray) + const receipt = await tx.wait() + const ev = receipt.events![0] + expect(ev.event).to.be.eql('TransferBatch') - const args = ev.args! as any - expect(args._ids.length).to.be.eql(typesArray.length) - }) + const args = ev.args! as any + expect(args._ids.length).to.be.eql(typesArray.length) + }) - it('should have 0x0 as `to` argument in Transfer events', async () => { - const tx = await erc1155MintBurnContract.batchBurnMock(receiverAddress, typesArray, burnAmountArray) - const receipt = await tx.wait() - const args = receipt.events![0].args! as any - expect(args._to).to.be.eql(ZERO_ADDRESS) + it('should have 0x0 as `to` argument in Transfer events', async () => { + const tx = await erc1155MintBurnContract.batchBurnMock(receiverAddress, typesArray, burnAmountArray) + const receipt = await tx.wait() + const args = receipt.events![0].args! as any + expect(args._to).to.be.eql(ZERO_ADDRESS) + }) }) }) }) diff --git a/tests/ERC1155MintBurnPackedBalance.spec.ts b/tests/ERC1155MintBurnPackedBalance.spec.ts index b8bec9f..045d1c4 100644 --- a/tests/ERC1155MintBurnPackedBalance.spec.ts +++ b/tests/ERC1155MintBurnPackedBalance.spec.ts @@ -1,554 +1,574 @@ -import { ethers } from 'ethers' +import { constants, ethers } from 'ethers' import { AbstractContract, RevertError, expect, BigNumber, HIGH_GAS_LIMIT, RevertUnsafeMathError } from './utils' import * as utils from './utils' -import { ERC1155MetaMintBurnPackedBalanceMock, ERC1155ReceiverMock } from 'src' +import { ERC1155MetaMintBurnPackedBalanceMock, ERC1155ReceiverMock, ProxyUpgradeableDeployerMock } from 'src' // init test wallets from package.json mnemonic import { web3 } from 'hardhat' -const { wallet: ownerWallet, provider: ownerProvider, signer: ownerSigner } = utils.createTestWallet(web3, 1) - -const { wallet: receiverWallet, provider: receiverProvider, signer: receiverSigner } = utils.createTestWallet(web3, 2) - -const { wallet: anyoneWallet, provider: anyoneProvider, signer: anyoneSigner } = utils.createTestWallet(web3, 3) - -const { wallet: operatorWallet, provider: operatorProvider, signer: operatorSigner } = utils.createTestWallet(web3, 4) - -describe('ERC1155MintBurnPackedBalance', () => { - const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' - const NAME = 'MyERC1155' - const METADATA_URI = 'https://example.com/' - - let ownerAddress: string - let receiverAddress: string - let anyoneAddress: string - let operatorAddress: string - - let erc1155MintBurnContract: ERC1155MetaMintBurnPackedBalanceMock - let anyoneERC1155MintBurnContract: ERC1155MetaMintBurnPackedBalanceMock - let receiverContract: ERC1155ReceiverMock - - context('When ERC1155MintBurn contract is deployed', () => { - before(async () => { - ownerAddress = await ownerWallet.getAddress() - receiverAddress = await receiverWallet.getAddress() - anyoneAddress = await anyoneWallet.getAddress() - operatorAddress = await operatorWallet.getAddress() - }) - - beforeEach(async () => { - const abstractReceiver = await AbstractContract.fromArtifactName('ERC1155ReceiverMock') - receiverContract = (await abstractReceiver.deploy(ownerWallet)) as ERC1155ReceiverMock - - const abstract = await AbstractContract.fromArtifactName('ERC1155MetaMintBurnPackedBalanceMock') - erc1155MintBurnContract = (await abstract.deploy(ownerWallet, [NAME, METADATA_URI])) as ERC1155MetaMintBurnPackedBalanceMock - anyoneERC1155MintBurnContract = (await erc1155MintBurnContract.connect( - anyoneSigner - )) as ERC1155MetaMintBurnPackedBalanceMock - }) - - describe('_mint() function', () => { - const tokenID = 666 - const amount = 11 - - it('should ALLOW inheriting contract to call mint()', async () => { - const tx = erc1155MintBurnContract.mintMock(receiverAddress, tokenID, amount, []) - await expect(tx).to.be.fulfilled +const { wallet: ownerWallet, provider: ownerProvider } = utils.createTestWallet(web3, 1) +const { wallet: receiverWallet } = utils.createTestWallet(web3, 2) +const { wallet: anyoneWallet } = utils.createTestWallet(web3, 3) +const { wallet: operatorWallet } = utils.createTestWallet(web3, 4) + + +const usingUpgradeable = [false, true] + +usingUpgradeable.forEach(upgradeable => { + describe('ERC1155MintBurnPackedBalance' + (upgradeable ? 'Upgradeable': ''), () => { + const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' + const NAME = 'MyERC1155' + const METADATA_URI = 'https://example.com/' + + let ownerAddress: string + let receiverAddress: string + let anyoneAddress: string + let operatorAddress: string + + let abstract: AbstractContract + let erc1155MintBurnContract: ERC1155MetaMintBurnPackedBalanceMock + let receiverContract: ERC1155ReceiverMock + let factoryContract: ProxyUpgradeableDeployerMock + + context('When ERC1155MintBurn contract is deployed', () => { + before(async () => { + ownerAddress = await ownerWallet.getAddress() + receiverAddress = await receiverWallet.getAddress() + anyoneAddress = await anyoneWallet.getAddress() + operatorAddress = await operatorWallet.getAddress() + + if (upgradeable) { + abstract = await AbstractContract.fromArtifactName('ERC1155MetaMintBurnPackedBalanceUpgradeableMock') + // Create factory + const factoryAbstract = await AbstractContract.fromArtifactName('ProxyUpgradeableDeployerMock') + factoryContract = (await factoryAbstract.deploy(ownerWallet, [])) as ProxyUpgradeableDeployerMock + } else { + abstract = await AbstractContract.fromArtifactName('ERC1155MetaMintBurnPackedBalanceMock') + } }) - it('should NOT allow anyone to call _mint()', async () => { - const transaction = { - to: erc1155MintBurnContract.address, - data: - '0x7776afa0000000000000000000000000b87213121fb89cbd8b877cb1bb3ff84dd2869cfa' + - '000000000000000000000000000000000000000000000000000000000000029a0000000000000000' + - '00000000000000000000000000000000000000000000000b' + beforeEach(async () => { + const abstractReceiver = await AbstractContract.fromArtifactName('ERC1155ReceiverMock') + receiverContract = (await abstractReceiver.deploy(ownerWallet)) as ERC1155ReceiverMock + + if (upgradeable) { + erc1155MintBurnContract = (await abstract.deploy(ownerWallet, [])) as ERC1155MetaMintBurnPackedBalanceMock + + // Create proxy + let tx = factoryContract.createProxy(erc1155MintBurnContract.address, constants.HashZero, ownerWallet.address); + await expect(tx).to.be.fulfilled + const proxyAddr = await factoryContract.predictProxyAddress(erc1155MintBurnContract.address, constants.HashZero, ownerWallet.address); + erc1155MintBurnContract = (await abstract.connect(ownerWallet, proxyAddr)) as ERC1155MetaMintBurnPackedBalanceMock; + tx = erc1155MintBurnContract.initialize(NAME, METADATA_URI) + await expect(tx).to.be.fulfilled + } else { + erc1155MintBurnContract = (await abstract.deploy(ownerWallet, [NAME, METADATA_URI])) as ERC1155MetaMintBurnPackedBalanceMock } - const tx = anyoneWallet.sendTransaction(transaction) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) }) - it('should increase the balance of receiver by the right amount', async () => { - const recipientBalanceA = await erc1155MintBurnContract.balanceOf(receiverAddress, tokenID) - await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, amount, []) + describe('_mint() function', () => { + const tokenID = 666 + const amount = 11 - const recipientBalanceB = await erc1155MintBurnContract.balanceOf(receiverAddress, tokenID) + it('should ALLOW inheriting contract to call mint()', async () => { + const tx = erc1155MintBurnContract.mintMock(receiverAddress, tokenID, amount, []) + await expect(tx).to.be.fulfilled + }) - expect(recipientBalanceB).to.be.eql(recipientBalanceA.add(amount)) - }) + it('should NOT allow anyone to call _mint()', async () => { + const transaction = { + to: erc1155MintBurnContract.address, + data: + '0x7776afa0000000000000000000000000b87213121fb89cbd8b877cb1bb3ff84dd2869cfa' + + '000000000000000000000000000000000000000000000000000000000000029a0000000000000000' + + '00000000000000000000000000000000000000000000000b' + } + const tx = anyoneWallet.sendTransaction(transaction) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) + }) - it('should REVERT if amount is larger than limit (overflow 1)', async () => { - await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, amount, []) - const maxVal0 = BigNumber.from(2) - .pow(32) - .sub(amount) - const tx0 = erc1155MintBurnContract.mintMock(receiverAddress, tokenID, maxVal0, []) - await expect(tx0).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: OVERFLOW')) - }) + it('should increase the balance of receiver by the right amount', async () => { + const recipientBalanceA = await erc1155MintBurnContract.balanceOf(receiverAddress, tokenID) + await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, amount, []) - it('should REVERT if amount is larger than limit (invalid amount by 1)', async () => { - const maxVal = BigNumber.from(2).pow(32) - const tx = erc1155MintBurnContract.mintMock(anyoneWallet.address, 0, maxVal, []) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: OVERFLOW')) - }) + const recipientBalanceB = await erc1155MintBurnContract.balanceOf(receiverAddress, tokenID) - it('should REVERT if amount is larger than limit (invalid amount min overflow)', async () => { - const maxVal = BigNumber.from(2).pow(32) - // Set balance to max acceptable value - await erc1155MintBurnContract.mintMock(anyoneWallet.address, 0, maxVal.sub(1), []) - const balance = await erc1155MintBurnContract.balanceOf(anyoneWallet.address, 0) - await expect(balance).to.be.eql(maxVal.sub(1)) - - // Value that overflows solidity, but result is < maxVal - // Minimum overflow - const maxVal2 = BigNumber.from(2) - .pow(256) - .sub(maxVal.sub(1)) - const tx = erc1155MintBurnContract.mintMock(anyoneWallet.address, 0, maxVal2, [], HIGH_GAS_LIMIT) - await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) - }) + expect(recipientBalanceB).to.be.eql(recipientBalanceA.add(amount)) + }) - it('should REVERT if amount is larger than limit (invalid amount max overflow)', async () => { - // Maximum overflow - const maxVal = BigNumber.from(2) - .pow(256) - .sub(1) - const tx = erc1155MintBurnContract.mintMock(anyoneWallet.address, 0, maxVal, [], HIGH_GAS_LIMIT) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: OVERFLOW')) - }) + it('should REVERT if amount is larger than limit (overflow 1)', async () => { + await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, amount, []) + const maxVal0 = BigNumber.from(2) + .pow(32) + .sub(amount) + const tx0 = erc1155MintBurnContract.mintMock(receiverAddress, tokenID, maxVal0, []) + await expect(tx0).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: OVERFLOW')) + }) - it('should REVERT when sending to non-receiver contract', async () => { - const tx = erc1155MintBurnContract.mintMock(erc1155MintBurnContract.address, tokenID, amount, []) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) - }) + it('should REVERT if amount is larger than limit (invalid amount by 1)', async () => { + const maxVal = BigNumber.from(2).pow(32) + const tx = erc1155MintBurnContract.mintMock(anyoneWallet.address, 0, maxVal, []) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: OVERFLOW')) + }) - it('should REVERT if invalid response from receiver contract', async () => { - await receiverContract.setShouldReject(true) + it('should REVERT if amount is larger than limit (invalid amount min overflow)', async () => { + const maxVal = BigNumber.from(2).pow(32) + // Set balance to max acceptable value + await erc1155MintBurnContract.mintMock(anyoneWallet.address, 0, maxVal.sub(1), []) + const balance = await erc1155MintBurnContract.balanceOf(anyoneWallet.address, 0) + await expect(balance).to.be.eql(maxVal.sub(1)) + + // Value that overflows solidity, but result is < maxVal + // Minimum overflow + const maxVal2 = BigNumber.from(2) + .pow(256) + .sub(maxVal.sub(1)) + const tx = erc1155MintBurnContract.mintMock(anyoneWallet.address, 0, maxVal2, [], HIGH_GAS_LIMIT) + await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) + }) - const tx = erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, []) - await expect(tx).to.be.rejectedWith( - RevertError('ERC1155PackedBalance#_callonERC1155Received: INVALID_ON_RECEIVE_MESSAGE') - ) - }) + it('should REVERT if amount is larger than limit (invalid amount max overflow)', async () => { + // Maximum overflow + const maxVal = BigNumber.from(2) + .pow(256) + .sub(1) + const tx = erc1155MintBurnContract.mintMock(anyoneWallet.address, 0, maxVal, [], HIGH_GAS_LIMIT) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: OVERFLOW')) + }) - it('should pass if valid response from receiver contract', async () => { - const tx = erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, []) - await expect(tx).to.be.fulfilled - }) + it('should REVERT when sending to non-receiver contract', async () => { + const tx = erc1155MintBurnContract.mintMock(erc1155MintBurnContract.address, tokenID, amount, []) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) + }) - it('should pass if data is not null to receiver contract', async () => { - const data = ethers.utils.toUtf8Bytes('Hello from the other side') + it('should REVERT if invalid response from receiver contract', async () => { + await receiverContract.setShouldReject(true) - // NOTE: typechain generates the wrong type for `bytes` type at this time - // see https://github.com/ethereum-ts/TypeChain/issues/123 - // @ts-ignore - const tx = erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, data) - await expect(tx).to.be.fulfilled - }) + const tx = erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, []) + await expect(tx).to.be.rejectedWith( + RevertError('ERC1155PackedBalance#_callonERC1155Received: INVALID_ON_RECEIVE_MESSAGE') + ) + }) - it('should have balances updated before onERC1155Received is called', async () => { - const toPreBalance = await erc1155MintBurnContract.balanceOf(receiverContract.address, tokenID) + it('should pass if valid response from receiver contract', async () => { + const tx = erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, []) + await expect(tx).to.be.fulfilled + }) - // Get event filter to get internal tx event - const filterFromReceiverContract = receiverContract.filters.TransferSingleReceiver(null, null, null, null) + it('should pass if data is not null to receiver contract', async () => { + const data = ethers.utils.toUtf8Bytes('Hello from the other side') - await erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, []) + // NOTE: typechain generates the wrong type for `bytes` type at this time + // see https://github.com/ethereum-ts/TypeChain/issues/123 + // @ts-ignore + const tx = erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, data) + await expect(tx).to.be.fulfilled + }) - // Get logs from internal transaction event - // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) - filterFromReceiverContract.fromBlock = 0 + it('should have balances updated before onERC1155Received is called', async () => { + const toPreBalance = await erc1155MintBurnContract.balanceOf(receiverContract.address, tokenID) - const logs = await ownerProvider.getLogs(filterFromReceiverContract) - const args = receiverContract.interface.decodeEventLog( - receiverContract.interface.events['TransferSingleReceiver(address,address,uint256,uint256)'], - logs[0].data, - logs[0].topics - ) + // Get event filter to get internal tx event + const filterFromReceiverContract = receiverContract.filters.TransferSingleReceiver(null, null, null, null) - expect(args._from).to.be.eql(ZERO_ADDRESS) - expect(args._to).to.be.eql(receiverContract.address) - expect(args._toBalance).to.be.eql(toPreBalance.add(amount)) - }) + await erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, []) - it('should have TransferSingle event emitted before onERC1155Received is called', async () => { - // Get event filter to get internal tx event - const tx = await erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, []) - const receipt = await tx.wait(1) + // Get logs from internal transaction event + // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) + filterFromReceiverContract.fromBlock = 0 - const firstEventTopic = receipt.logs![0].topics[0] - const secondEventTopic = receipt.logs![1].topics[0] + const logs = await ownerProvider.getLogs(filterFromReceiverContract) + const args = receiverContract.interface.decodeEventLog( + receiverContract.interface.events['TransferSingleReceiver(address,address,uint256,uint256)'], + logs[0].data, + logs[0].topics + ) + + expect(args._from).to.be.eql(ZERO_ADDRESS) + expect(args._to).to.be.eql(receiverContract.address) + expect(args._toBalance).to.be.eql(toPreBalance.add(amount)) + }) + + it('should have TransferSingle event emitted before onERC1155Received is called', async () => { + // Get event filter to get internal tx event + const tx = await erc1155MintBurnContract.mintMock(receiverContract.address, tokenID, amount, []) + const receipt = await tx.wait(1) - expect(firstEventTopic).to.be.equal( - erc1155MintBurnContract.interface.getEventTopic( - erc1155MintBurnContract.interface.events['TransferSingle(address,address,address,uint256,uint256)'] + const firstEventTopic = receipt.logs![0].topics[0] + const secondEventTopic = receipt.logs![1].topics[0] + + expect(firstEventTopic).to.be.equal( + erc1155MintBurnContract.interface.getEventTopic( + erc1155MintBurnContract.interface.events['TransferSingle(address,address,address,uint256,uint256)'] + ) ) - ) - expect(secondEventTopic).to.be.equal( - receiverContract.interface.getEventTopic( - receiverContract.interface.events['TransferSingleReceiver(address,address,uint256,uint256)'] + expect(secondEventTopic).to.be.equal( + receiverContract.interface.getEventTopic( + receiverContract.interface.events['TransferSingleReceiver(address,address,uint256,uint256)'] + ) ) - ) - }) + }) - it('should emit a Transfer event', async () => { - const tx = await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, amount, []) - const receipt = await tx.wait(1) + it('should emit a Transfer event', async () => { + const tx = await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, amount, []) + const receipt = await tx.wait(1) - const ev = receipt.events![0] - expect(ev.event).to.be.eql('TransferSingle') - }) + const ev = receipt.events![0] + expect(ev.event).to.be.eql('TransferSingle') + }) - it('should have 0x0 as `from` argument in Transfer event', async () => { - const tx = await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, amount, []) - const receipt = await tx.wait(1) + it('should have 0x0 as `from` argument in Transfer event', async () => { + const tx = await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, amount, []) + const receipt = await tx.wait(1) - // TODO: this form can be improved eventually as ethers improves its api - // or we write a wrapper function to parse the tx - const ev = receipt.events![0] - const args = ev.args! as any + // TODO: this form can be improved eventually as ethers improves its api + // or we write a wrapper function to parse the tx + const ev = receipt.events![0] + const args = ev.args! as any - expect(args._from).to.be.eql(ZERO_ADDRESS) + expect(args._from).to.be.eql(ZERO_ADDRESS) + }) }) - }) - describe('_batchMint() function', () => { - const Ntypes = 123 - const amountToMint = 10 - const typesArray = Array.apply(null, { length: Ntypes }).map(Number.call, Number) - const amountArray = Array.apply(null, Array(Ntypes)).map(Number.prototype.valueOf, amountToMint) + describe('_batchMint() function', () => { + const Ntypes = 123 + const amountToMint = 10 + const typesArray = Array.apply(null, { length: Ntypes }).map(Number.call, Number) + const amountArray = Array.apply(null, Array(Ntypes)).map(Number.prototype.valueOf, amountToMint) - it('should ALLOW inheriting contract to call _batchMint()', async () => { - const req = erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, amountArray, []) - ;(await expect(req).to.be.fulfilled) as ethers.ContractTransaction - }) - - it('should PASS if arrays are empty', async () => { - const tx = erc1155MintBurnContract.batchMintMock(receiverAddress, [], [], []) - await expect(tx).to.be.fulfilled - }) + it('should ALLOW inheriting contract to call _batchMint()', async () => { + const req = erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, amountArray, []) + ;(await expect(req).to.be.fulfilled) as ethers.ContractTransaction + }) - it('should NOT allow anyone to call _batchMint()', async () => { - const transaction = { - to: erc1155MintBurnContract.address, - data: - '0x2589aeae00000000000000000000000035ef07393b57464e93deb59175ff72e6499450cf' + - '00000000000000000000000000000000000000000000000000000000000000600000000000000000' + - '0000000000000000000000000000000000000000000000c000000000000000000000000000000000' + - '00000000000000000000000000000002000000000000000000000000000000000000000000000000' + - '00000000000000010000000000000000000000000000000000000000000000000000000000000002' + - '00000000000000000000000000000000000000000000000000000000000000020000000000000000' + - '00000000000000000000000000000000000000000000000a00000000000000000000000000000000' + - '0000000000000000000000000000000a' - } + it('should PASS if arrays are empty', async () => { + const tx = erc1155MintBurnContract.batchMintMock(receiverAddress, [], [], []) + await expect(tx).to.be.fulfilled + }) - const tx = anyoneWallet.sendTransaction(transaction) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) - }) + it('should NOT allow anyone to call _batchMint()', async () => { + const transaction = { + to: erc1155MintBurnContract.address, + data: + '0x2589aeae00000000000000000000000035ef07393b57464e93deb59175ff72e6499450cf' + + '00000000000000000000000000000000000000000000000000000000000000600000000000000000' + + '0000000000000000000000000000000000000000000000c000000000000000000000000000000000' + + '00000000000000000000000000000002000000000000000000000000000000000000000000000000' + + '00000000000000010000000000000000000000000000000000000000000000000000000000000002' + + '00000000000000000000000000000000000000000000000000000000000000020000000000000000' + + '00000000000000000000000000000000000000000000000a00000000000000000000000000000000' + + '0000000000000000000000000000000a' + } + + const tx = anyoneWallet.sendTransaction(transaction) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) + }) - it('should increase the balances of receiver by the right amounts', async () => { - await erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, amountArray, []) + it('should increase the balances of receiver by the right amounts', async () => { + await erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, amountArray, []) - for (let i = 0; i < typesArray.length; i++) { - const balanceTo = await erc1155MintBurnContract.balanceOf(receiverAddress, typesArray[i]) - expect(balanceTo).to.be.eql(BigNumber.from(amountArray[i])) - } - }) + for (let i = 0; i < typesArray.length; i++) { + const balanceTo = await erc1155MintBurnContract.balanceOf(receiverAddress, typesArray[i]) + expect(balanceTo).to.be.eql(BigNumber.from(amountArray[i])) + } + }) - it('should REVERT when sending to non-receiver contract', async () => { - const tx = erc1155MintBurnContract.batchMintMock(erc1155MintBurnContract.address, typesArray, amountArray, [], { - gasLimit: 2000000 + it('should REVERT when sending to non-receiver contract', async () => { + const tx = erc1155MintBurnContract.batchMintMock(erc1155MintBurnContract.address, typesArray, amountArray, [], { + gasLimit: 2000000 + }) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) }) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) - }) - it('should REVERT if invalid response from receiver contract', async () => { - await receiverContract.setShouldReject(true) - const tx = erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, [], { - gasLimit: 2000000 + it('should REVERT if invalid response from receiver contract', async () => { + await receiverContract.setShouldReject(true) + const tx = erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, [], { + gasLimit: 2000000 + }) + await expect(tx).to.be.rejectedWith( + RevertError('ERC1155PackedBalance#_callonERC1155BatchReceived: INVALID_ON_RECEIVE_MESSAGE') + ) }) - await expect(tx).to.be.rejectedWith( - RevertError('ERC1155PackedBalance#_callonERC1155BatchReceived: INVALID_ON_RECEIVE_MESSAGE') - ) - }) - it('should REVERT if amount is larger than limit (overflow 1)', async () => { - await erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, amountArray, [], { gasLimit: 2000000 }) - // Overflow by 1 - const maxVal0 = BigNumber.from(2) - .pow(32) - .sub(amountToMint) - const tx0 = erc1155MintBurnContract.batchMintMock( - receiverAddress, - [typesArray[0], typesArray[1]], - [maxVal0, amountArray[1]], - [] - ) - await expect(tx0).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: OVERFLOW')) - }) + it('should REVERT if amount is larger than limit (overflow 1)', async () => { + await erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, amountArray, [], { gasLimit: 2000000 }) + // Overflow by 1 + const maxVal0 = BigNumber.from(2) + .pow(32) + .sub(amountToMint) + const tx0 = erc1155MintBurnContract.batchMintMock( + receiverAddress, + [typesArray[0], typesArray[1]], + [maxVal0, amountArray[1]], + [] + ) + await expect(tx0).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: OVERFLOW')) + }) - it('should REVERT if amount is larger than limit (invalid amount 1)', async () => { - const maxVal = BigNumber.from(2).pow(32) - const tx2 = erc1155MintBurnContract.batchMintMock(anyoneWallet.address, [0, 1], [maxVal, 1], []) - await expect(tx2).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: OVERFLOW')) - }) + it('should REVERT if amount is larger than limit (invalid amount 1)', async () => { + const maxVal = BigNumber.from(2).pow(32) + const tx2 = erc1155MintBurnContract.batchMintMock(anyoneWallet.address, [0, 1], [maxVal, 1], []) + await expect(tx2).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: OVERFLOW')) + }) - it('should REVERT if amount is larger than limit (invalid amount min overflow)', async () => { - const maxVal = BigNumber.from(2).pow(32) - // Set balance to max acceptable value - await erc1155MintBurnContract.batchMintMock(anyoneWallet.address, [0], [maxVal.sub(1)], []) - const balance = await erc1155MintBurnContract.balanceOf(anyoneWallet.address, 0) - await expect(balance).to.be.eql(maxVal.sub(1)) - - // Value that overflows solidity, but result is < maxVal - const maxVal2 = BigNumber.from(2) - .pow(256) - .sub(maxVal.sub(1)) - const tx3 = erc1155MintBurnContract.batchMintMock(anyoneWallet.address, [0], [maxVal2], [], HIGH_GAS_LIMIT) - await expect(tx3).to.be.rejectedWith(RevertUnsafeMathError()) - }) + it('should REVERT if amount is larger than limit (invalid amount min overflow)', async () => { + const maxVal = BigNumber.from(2).pow(32) + // Set balance to max acceptable value + await erc1155MintBurnContract.batchMintMock(anyoneWallet.address, [0], [maxVal.sub(1)], []) + const balance = await erc1155MintBurnContract.balanceOf(anyoneWallet.address, 0) + await expect(balance).to.be.eql(maxVal.sub(1)) + + // Value that overflows solidity, but result is < maxVal + const maxVal2 = BigNumber.from(2) + .pow(256) + .sub(maxVal.sub(1)) + const tx3 = erc1155MintBurnContract.batchMintMock(anyoneWallet.address, [0], [maxVal2], [], HIGH_GAS_LIMIT) + await expect(tx3).to.be.rejectedWith(RevertUnsafeMathError()) + }) - it('should REVERT if amount is larger than limit (invalid amount max overflow)', async () => { - const maxVal = BigNumber.from(2).pow(32) - // Set balance to max acceptable value - await erc1155MintBurnContract.batchMintMock(anyoneWallet.address, [0], [maxVal.sub(1)], []) - const balance = await erc1155MintBurnContract.balanceOf(anyoneWallet.address, 0) - - await expect(balance).to.be.eql(maxVal.sub(1)) - const maxVal3 = BigNumber.from(2) - .pow(256) - .sub(1) - const tx4 = erc1155MintBurnContract.batchMintMock(anyoneWallet.address, [0], [maxVal3], [], HIGH_GAS_LIMIT) - await expect(tx4).to.be.rejectedWith(RevertUnsafeMathError()) - }) + it('should REVERT if amount is larger than limit (invalid amount max overflow)', async () => { + const maxVal = BigNumber.from(2).pow(32) + // Set balance to max acceptable value + await erc1155MintBurnContract.batchMintMock(anyoneWallet.address, [0], [maxVal.sub(1)], []) + const balance = await erc1155MintBurnContract.balanceOf(anyoneWallet.address, 0) + + await expect(balance).to.be.eql(maxVal.sub(1)) + const maxVal3 = BigNumber.from(2) + .pow(256) + .sub(1) + const tx4 = erc1155MintBurnContract.batchMintMock(anyoneWallet.address, [0], [maxVal3], [], HIGH_GAS_LIMIT) + await expect(tx4).to.be.rejectedWith(RevertUnsafeMathError()) + }) - it('should pass if valid response from receiver contract', async () => { - const tx = erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, [], { - gasLimit: 6000000 + it('should pass if valid response from receiver contract', async () => { + const tx = erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, [], { + gasLimit: 6000000 + }) + await expect(tx).to.be.fulfilled }) - await expect(tx).to.be.fulfilled - }) - it('should pass if data is not null from receiver contract', async () => { - const data = ethers.utils.toUtf8Bytes('Hello from the other side') + it('should pass if data is not null from receiver contract', async () => { + const data = ethers.utils.toUtf8Bytes('Hello from the other side') - // TODO: remove ts-ignore when contract declaration is fixed - // @ts-ignore - const tx = erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, data, { - gasLimit: 2000000 + // TODO: remove ts-ignore when contract declaration is fixed + // @ts-ignore + const tx = erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, data, { + gasLimit: 2000000 + }) + await expect(tx).to.be.fulfilled }) - await expect(tx).to.be.fulfilled - }) - it('should have balances updated before onERC1155BatchReceived is called', async () => { - const toAddresses = Array(typesArray.length).fill(receiverContract.address) + it('should have balances updated before onERC1155BatchReceived is called', async () => { + const toAddresses = Array(typesArray.length).fill(receiverContract.address) - const toPreBalances = await erc1155MintBurnContract.balanceOfBatch(toAddresses, typesArray) + const toPreBalances = await erc1155MintBurnContract.balanceOfBatch(toAddresses, typesArray) - // Get event filter to get internal tx event - const filterFromReceiverContract = receiverContract.filters.TransferBatchReceiver(null, null, null, null) + // Get event filter to get internal tx event + const filterFromReceiverContract = receiverContract.filters.TransferBatchReceiver(null, null, null, null) - await erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, [], { gasLimit: 2000000 }) + await erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, [], { gasLimit: 2000000 }) - // Get logs from internal transaction event - // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) - filterFromReceiverContract.fromBlock = 0 + // Get logs from internal transaction event + // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) + filterFromReceiverContract.fromBlock = 0 - const logs = await ownerProvider.getLogs(filterFromReceiverContract) - const args = receiverContract.interface.decodeEventLog( - receiverContract.interface.events['TransferBatchReceiver(address,address,uint256[],uint256[])'], - logs[0].data, - logs[0].topics - ) - - expect(args._from).to.be.eql(ZERO_ADDRESS) - expect(args._to).to.be.eql(receiverContract.address) - for (let i = 0; i < typesArray.length; i++) { - expect(args._toBalances[i]).to.be.eql(toPreBalances[i].add(amountArray[i])) - } - }) + const logs = await ownerProvider.getLogs(filterFromReceiverContract) + const args = receiverContract.interface.decodeEventLog( + receiverContract.interface.events['TransferBatchReceiver(address,address,uint256[],uint256[])'], + logs[0].data, + logs[0].topics + ) - it('should have TransferBatch event emitted before onERC1155BatchReceived is called', async () => { - // Get event filter to get internal tx event - const tx = await erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, [], { - gasLimit: 2000000 + expect(args._from).to.be.eql(ZERO_ADDRESS) + expect(args._to).to.be.eql(receiverContract.address) + for (let i = 0; i < typesArray.length; i++) { + expect(args._toBalances[i]).to.be.eql(toPreBalances[i].add(amountArray[i])) + } }) - const receipt = await tx.wait(1) - const firstEventTopic = receipt.logs![0].topics[0] - const secondEventTopic = receipt.logs![1].topics[0] + it('should have TransferBatch event emitted before onERC1155BatchReceived is called', async () => { + // Get event filter to get internal tx event + const tx = await erc1155MintBurnContract.batchMintMock(receiverContract.address, typesArray, amountArray, [], { + gasLimit: 2000000 + }) + const receipt = await tx.wait(1) - expect(firstEventTopic).to.be.equal( - erc1155MintBurnContract.interface.getEventTopic( - erc1155MintBurnContract.interface.events['TransferBatch(address,address,address,uint256[],uint256[])'] + const firstEventTopic = receipt.logs![0].topics[0] + const secondEventTopic = receipt.logs![1].topics[0] + + expect(firstEventTopic).to.be.equal( + erc1155MintBurnContract.interface.getEventTopic( + erc1155MintBurnContract.interface.events['TransferBatch(address,address,address,uint256[],uint256[])'] + ) ) - ) - expect(secondEventTopic).to.be.equal( - erc1155MintBurnContract.interface.getEventTopic( - receiverContract.interface.events['TransferBatchReceiver(address,address,uint256[],uint256[])'] + expect(secondEventTopic).to.be.equal( + erc1155MintBurnContract.interface.getEventTopic( + receiverContract.interface.events['TransferBatchReceiver(address,address,uint256[],uint256[])'] + ) ) - ) - }) + }) - it('should emit 1 Transfer events of N transfers', async () => { - const tx = await erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, amountArray, []) - const receipt = await tx.wait() - const ev = receipt.events![0] - expect(ev.event).to.be.eql('TransferBatch') + it('should emit 1 Transfer events of N transfers', async () => { + const tx = await erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, amountArray, []) + const receipt = await tx.wait() + const ev = receipt.events![0] + expect(ev.event).to.be.eql('TransferBatch') - const args = ev.args! as any - expect(args._ids.length).to.be.eql(typesArray.length) - }) + const args = ev.args! as any + expect(args._ids.length).to.be.eql(typesArray.length) + }) - it('should have 0x0 as `from` argument in Transfer events', async () => { - const tx = await erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, amountArray, []) - const receipt = await tx.wait() - const args = receipt.events![0].args! as any - expect(args._from).to.be.eql(ZERO_ADDRESS) + it('should have 0x0 as `from` argument in Transfer events', async () => { + const tx = await erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, amountArray, []) + const receipt = await tx.wait() + const args = receipt.events![0].args! as any + expect(args._from).to.be.eql(ZERO_ADDRESS) + }) }) - }) - describe('_burn() function', () => { - const tokenID = 666 - const initBalance = 100 - const amountToBurn = 10 + describe('_burn() function', () => { + const tokenID = 666 + const initBalance = 100 + const amountToBurn = 10 - beforeEach(async () => { - await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, initBalance, []) - }) + beforeEach(async () => { + await erc1155MintBurnContract.mintMock(receiverAddress, tokenID, initBalance, []) + }) - it('should ALLOW inheriting contract to call _burn()', async () => { - const tx = erc1155MintBurnContract.burnMock(receiverAddress, tokenID, amountToBurn) - await expect(tx).to.be.fulfilled - }) + it('should ALLOW inheriting contract to call _burn()', async () => { + const tx = erc1155MintBurnContract.burnMock(receiverAddress, tokenID, amountToBurn) + await expect(tx).to.be.fulfilled + }) - it('should NOT allow anyone to call _burn()', async () => { - const transaction = { - to: erc1155MintBurnContract.address, - data: - '0x464a5ffb00000000000000000000000008970fed061e7747cd9a38d680a601510cb659fb' + - '000000000000000000000000000000000000000000000000000000000000029a0000000000000000' + - '00000000000000000000000000000000000000000000000a' - } - const tx = anyoneWallet.sendTransaction(transaction) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) - }) + it('should NOT allow anyone to call _burn()', async () => { + const transaction = { + to: erc1155MintBurnContract.address, + data: + '0x464a5ffb00000000000000000000000008970fed061e7747cd9a38d680a601510cb659fb' + + '000000000000000000000000000000000000000000000000000000000000029a0000000000000000' + + '00000000000000000000000000000000000000000000000a' + } + const tx = anyoneWallet.sendTransaction(transaction) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) + }) - it('should decrease the balance of receiver by the right amount', async () => { - const recipientBalanceA = await erc1155MintBurnContract.balanceOf(receiverAddress, tokenID) - await erc1155MintBurnContract.burnMock(receiverAddress, tokenID, amountToBurn) + it('should decrease the balance of receiver by the right amount', async () => { + const recipientBalanceA = await erc1155MintBurnContract.balanceOf(receiverAddress, tokenID) + await erc1155MintBurnContract.burnMock(receiverAddress, tokenID, amountToBurn) - const recipientBalanceB = await erc1155MintBurnContract.balanceOf(receiverAddress, tokenID) - expect(recipientBalanceB).to.be.eql(recipientBalanceA.sub(amountToBurn)) - }) + const recipientBalanceB = await erc1155MintBurnContract.balanceOf(receiverAddress, tokenID) + expect(recipientBalanceB).to.be.eql(recipientBalanceA.sub(amountToBurn)) + }) - it('should REVERT if amount is higher than balance', async () => { - // Sanity check - const balance = await erc1155MintBurnContract.balanceOf(receiverAddress, tokenID) - expect(balance).to.be.eql(BigNumber.from(initBalance)) + it('should REVERT if amount is higher than balance', async () => { + // Sanity check + const balance = await erc1155MintBurnContract.balanceOf(receiverAddress, tokenID) + expect(balance).to.be.eql(BigNumber.from(initBalance)) - // Invalid amount to burn that would cause underflow - const invalidVal = initBalance + 1 + // Invalid amount to burn that would cause underflow + const invalidVal = initBalance + 1 - const tx = erc1155MintBurnContract.burnMock(receiverAddress, tokenID, invalidVal, HIGH_GAS_LIMIT) - await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) - }) + const tx = erc1155MintBurnContract.burnMock(receiverAddress, tokenID, invalidVal, HIGH_GAS_LIMIT) + await expect(tx).to.be.rejectedWith(RevertUnsafeMathError()) + }) - it('should emit a Transfer event', async () => { - const tx = await erc1155MintBurnContract.burnMock(receiverAddress, tokenID, amountToBurn) - const receipt = await tx.wait(1) + it('should emit a Transfer event', async () => { + const tx = await erc1155MintBurnContract.burnMock(receiverAddress, tokenID, amountToBurn) + const receipt = await tx.wait(1) - const ev = receipt.events![0] - expect(ev.event).to.be.eql('TransferSingle') - }) + const ev = receipt.events![0] + expect(ev.event).to.be.eql('TransferSingle') + }) - it('should have 0x0 as `to` argument in Transfer event', async () => { - const tx = await erc1155MintBurnContract.burnMock(receiverAddress, tokenID, amountToBurn) - const receipt = await tx.wait(1) + it('should have 0x0 as `to` argument in Transfer event', async () => { + const tx = await erc1155MintBurnContract.burnMock(receiverAddress, tokenID, amountToBurn) + const receipt = await tx.wait(1) - // TODO: this form can be improved eventually as ethers improves its api - // or we write a wrapper function to parse the tx - const ev = receipt.events![0] - const args = ev.args! as any + // TODO: this form can be improved eventually as ethers improves its api + // or we write a wrapper function to parse the tx + const ev = receipt.events![0] + const args = ev.args! as any - expect(args._to).to.be.eql(ZERO_ADDRESS) + expect(args._to).to.be.eql(ZERO_ADDRESS) + }) }) - }) - describe('_batchBurn() function', () => { - const Ntypes = 32 - const initBalance = 100 - const amountToBurn = 30 - const typesArray = Array.apply(null, { length: Ntypes }).map(Number.call, Number) - const burnAmountArray = Array.apply(null, Array(Ntypes)).map(Number.prototype.valueOf, amountToBurn) - const initBalanceArray = Array.apply(null, Array(Ntypes)).map(Number.prototype.valueOf, initBalance) + describe('_batchBurn() function', () => { + const Ntypes = 32 + const initBalance = 100 + const amountToBurn = 30 + const typesArray = Array.apply(null, { length: Ntypes }).map(Number.call, Number) + const burnAmountArray = Array.apply(null, Array(Ntypes)).map(Number.prototype.valueOf, amountToBurn) + const initBalanceArray = Array.apply(null, Array(Ntypes)).map(Number.prototype.valueOf, initBalance) - beforeEach(async () => { - await erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, initBalanceArray, []) - }) + beforeEach(async () => { + await erc1155MintBurnContract.batchMintMock(receiverAddress, typesArray, initBalanceArray, []) + }) - it('should ALLOW inheriting contract to call _batchBurn()', async () => { - const req = erc1155MintBurnContract.batchBurnMock(receiverAddress, typesArray, burnAmountArray) - const tx = (await expect(req).to.be.fulfilled) as ethers.ContractTransaction - // const receipt = await tx.wait() - // console.log('Batch mint :' + receipt.gasUsed) - }) + it('should ALLOW inheriting contract to call _batchBurn()', async () => { + const req = erc1155MintBurnContract.batchBurnMock(receiverAddress, typesArray, burnAmountArray) + const tx = (await expect(req).to.be.fulfilled) as ethers.ContractTransaction + // const receipt = await tx.wait() + // console.log('Batch mint :' + receipt.gasUsed) + }) - // Should call mock's fallback function - it('should NOT allow anyone to call _batchBurn()', async () => { - const transaction = { - to: erc1155MintBurnContract.address, - data: - '0xb389c3bb000000000000000000000000dc04977a2078c8ffdf086d618d1f961b6c546222' + - '00000000000000000000000000000000000000000000000000000000000000600000000000000000' + - '0000000000000000000000000000000000000000000000c000000000000000000000000000000000' + - '00000000000000000000000000000002000000000000000000000000000000000000000000000000' + - '00000000000000010000000000000000000000000000000000000000000000000000000000000003' + - '00000000000000000000000000000000000000000000000000000000000000020000000000000000' + - '00000000000000000000000000000000000000000000001e00000000000000000000000000000000' + - '0000000000000000000000000000001e' - } - const tx = anyoneWallet.sendTransaction(transaction) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) - }) + // Should call mock's fallback function + it('should NOT allow anyone to call _batchBurn()', async () => { + const transaction = { + to: erc1155MintBurnContract.address, + data: + '0xb389c3bb000000000000000000000000dc04977a2078c8ffdf086d618d1f961b6c546222' + + '00000000000000000000000000000000000000000000000000000000000000600000000000000000' + + '0000000000000000000000000000000000000000000000c000000000000000000000000000000000' + + '00000000000000000000000000000002000000000000000000000000000000000000000000000000' + + '00000000000000010000000000000000000000000000000000000000000000000000000000000003' + + '00000000000000000000000000000000000000000000000000000000000000020000000000000000' + + '00000000000000000000000000000000000000000000001e00000000000000000000000000000000' + + '0000000000000000000000000000001e' + } + const tx = anyoneWallet.sendTransaction(transaction) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) + }) - it('should decrease the balances of receiver by the right amounts', async () => { - await erc1155MintBurnContract.batchBurnMock(receiverAddress, typesArray, burnAmountArray) + it('should decrease the balances of receiver by the right amounts', async () => { + await erc1155MintBurnContract.batchBurnMock(receiverAddress, typesArray, burnAmountArray) - for (let i = 0; i < typesArray.length; i++) { - const balanceTo = await erc1155MintBurnContract.balanceOf(receiverAddress, typesArray[i]) - expect(balanceTo).to.be.eql(BigNumber.from(initBalance - burnAmountArray[i])) - } - }) + for (let i = 0; i < typesArray.length; i++) { + const balanceTo = await erc1155MintBurnContract.balanceOf(receiverAddress, typesArray[i]) + expect(balanceTo).to.be.eql(BigNumber.from(initBalance - burnAmountArray[i])) + } + }) - it('should REVERT if amount is higher than balance', async () => { - // Sanity check - const balance = await erc1155MintBurnContract.balanceOf(receiverAddress, typesArray[0]) - expect(balance).to.be.eql(BigNumber.from(initBalance)) + it('should REVERT if amount is higher than balance', async () => { + // Sanity check + const balance = await erc1155MintBurnContract.balanceOf(receiverAddress, typesArray[0]) + expect(balance).to.be.eql(BigNumber.from(initBalance)) - // Invalid amount to burn that would cause underflow - const invalidVal = initBalance + 1 + // Invalid amount to burn that would cause underflow + const invalidVal = initBalance + 1 - const tx = erc1155MintBurnContract.batchBurnMock(receiverAddress, [typesArray[0]], [invalidVal], HIGH_GAS_LIMIT) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: UNDERFLOW')) - }) + const tx = erc1155MintBurnContract.batchBurnMock(receiverAddress, [typesArray[0]], [invalidVal], HIGH_GAS_LIMIT) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: UNDERFLOW')) + }) - it('should emit 1 Transfer events of N transfers', async () => { - const tx = await erc1155MintBurnContract.batchBurnMock(receiverAddress, typesArray, burnAmountArray) - const receipt = await tx.wait() - const ev = receipt.events![0] - expect(ev.event).to.be.eql('TransferBatch') + it('should emit 1 Transfer events of N transfers', async () => { + const tx = await erc1155MintBurnContract.batchBurnMock(receiverAddress, typesArray, burnAmountArray) + const receipt = await tx.wait() + const ev = receipt.events![0] + expect(ev.event).to.be.eql('TransferBatch') - const args = ev.args! as any - expect(args._ids.length).to.be.eql(typesArray.length) - }) + const args = ev.args! as any + expect(args._ids.length).to.be.eql(typesArray.length) + }) - it('should have 0x0 as `to` argument in Transfer events', async () => { - const tx = await erc1155MintBurnContract.batchBurnMock(receiverAddress, typesArray, burnAmountArray) - const receipt = await tx.wait() - const args = receipt.events![0].args! as any - expect(args._to).to.be.eql(ZERO_ADDRESS) + it('should have 0x0 as `to` argument in Transfer events', async () => { + const tx = await erc1155MintBurnContract.batchBurnMock(receiverAddress, typesArray, burnAmountArray) + const receipt = await tx.wait() + const args = receipt.events![0].args! as any + expect(args._to).to.be.eql(ZERO_ADDRESS) + }) }) }) }) diff --git a/tests/ERC1155PackedBalance.spec.ts b/tests/ERC1155PackedBalance.spec.ts index 0d944a0..b4af4c7 100644 --- a/tests/ERC1155PackedBalance.spec.ts +++ b/tests/ERC1155PackedBalance.spec.ts @@ -1,643 +1,652 @@ -import { ethers } from 'ethers' +import { constants, ethers } from 'ethers' import { AbstractContract, expect, BigNumber, RevertError } from './utils' import * as utils from './utils' -import { ERC1155MetaMintBurnPackedBalanceMock, ERC1155ReceiverMock, ERC1155OperatorMock } from 'src' +import { ERC1155MetaMintBurnPackedBalanceMock, ERC1155ReceiverMock, ERC1155OperatorMock, ProxyUpgradeableDeployerMock } from 'src' // init test wallets from package.json mnemonic import { web3 } from 'hardhat' -const { wallet: ownerWallet, provider: ownerProvider, signer: ownerSigner } = utils.createTestWallet(web3, 1) - -const { wallet: receiverWallet, provider: receiverProvider, signer: receiverSigner } = utils.createTestWallet(web3, 2) - +const { wallet: ownerWallet, provider: ownerProvider } = utils.createTestWallet(web3, 1) +const { wallet: receiverWallet } = utils.createTestWallet(web3, 2) const { wallet: operatorWallet, provider: operatorProvider, signer: operatorSigner } = utils.createTestWallet(web3, 4) -describe('ERC1155PackedBalance', () => { - const LARGEVAL = BigNumber.from(2) - .pow(256) - .sub(2) // 2**256 - 2 - const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' - const NAME = 'MyERC1155' - const METADATA_URI = 'https://example.com/' - - let ownerAddress: string - let receiverAddress: string - let operatorAddress: string - let erc1155Abstract: AbstractContract - let operatorAbstract: AbstractContract - - let erc1155Contract: ERC1155MetaMintBurnPackedBalanceMock - let operatorERC1155Contract: ERC1155MetaMintBurnPackedBalanceMock - - // load contract abi and deploy to test server - before(async () => { - ownerAddress = await ownerWallet.getAddress() - receiverAddress = await receiverWallet.getAddress() - operatorAddress = await operatorWallet.getAddress() - - erc1155Abstract = await AbstractContract.fromArtifactName('ERC1155MetaMintBurnPackedBalanceMock') - operatorAbstract = await AbstractContract.fromArtifactName('ERC1155OperatorMock') - }) - - // deploy before each test, to reset state of contract - beforeEach(async () => { - erc1155Contract = (await erc1155Abstract.deploy(ownerWallet, [NAME, METADATA_URI])) as ERC1155MetaMintBurnPackedBalanceMock - operatorERC1155Contract = (await erc1155Contract.connect(operatorSigner)) as ERC1155MetaMintBurnPackedBalanceMock - }) - - describe('Bitwise functions', () => { - it('getValueInBin should return expected balance for given types', async () => { - const expected = BigNumber.from(2) - .pow(32) - .sub(2) // 2**32-2 - const balance = await erc1155Contract.getValueInBin(LARGEVAL.toString(), 0) - expect(balance).to.be.eql(expected) - }) - - // it('viewUpdateIDBalance should revert if overflow', async () => { - // let targetVal = 666 - // let tx = erc1155Contract.viewUpdateIDBalance() - // await expect(tx).to.be.rejectedWith( RevertError("ERC1155PackedBalance#safeTransferFrom: INVALID_OPERATOR") ) - // }) - - // it('writeValueInBin should throw if value is above 2**32-1', async () => { - // let targetVal = BigNumber.from(2).pow(32) - // let writtenBin = erc1155Contract.writeValueInBin(LARGEVAL.toString(), 0, targetVal.toString()) - // await expect(writtenBin).to.be.rejected - // }) - - it('getIDBinIndex should return the correct bin and respective index', async () => { - const { bin: bin0, index: index0 } = await erc1155Contract.getIDBinIndex(0) - expect(bin0).to.be.eql(BigNumber.from(0)) - expect(index0).to.be.eql(BigNumber.from(0)) - - const { bin: bin3, index: index3 } = await erc1155Contract.getIDBinIndex(3) - expect(bin3).to.be.eql(BigNumber.from(0)) - expect(index3).to.be.eql(BigNumber.from(3)) - - const { bin: bin9, index: index9 } = await erc1155Contract.getIDBinIndex(8) - expect(bin9).to.be.eql(BigNumber.from(1)) - expect(index9).to.be.eql(BigNumber.from(0)) - - const { bin: bin15, index: index15 } = await erc1155Contract.getIDBinIndex(15) - expect(bin15).to.be.eql(BigNumber.from(1)) - expect(index15).to.be.eql(BigNumber.from(7)) +const usingUpgradeable = [false, true] + +usingUpgradeable.forEach(upgradeable => { + describe('ERC1155PackedBalance' + (upgradeable ? 'Upgradeable': ''), () => { + + const LARGEVAL = BigNumber.from(2) + .pow(256) + .sub(2) // 2**256 - 2 + const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000' + const NAME = 'MyERC1155' + const METADATA_URI = 'https://example.com/' + + let ownerAddress: string + let receiverAddress: string + let operatorAddress: string + let erc1155Abstract: AbstractContract + let operatorAbstract: AbstractContract + + let erc1155Contract: ERC1155MetaMintBurnPackedBalanceMock + let operatorERC1155Contract: ERC1155MetaMintBurnPackedBalanceMock + let factoryContract: ProxyUpgradeableDeployerMock + + // load contract abi and deploy to test server + before(async () => { + ownerAddress = await ownerWallet.getAddress() + receiverAddress = await receiverWallet.getAddress() + operatorAddress = await operatorWallet.getAddress() + + if (upgradeable) { + erc1155Abstract = await AbstractContract.fromArtifactName('ERC1155MetaMintBurnPackedBalanceUpgradeableMock') + // Create factory + const factoryAbstract = await AbstractContract.fromArtifactName('ProxyUpgradeableDeployerMock') + factoryContract = (await factoryAbstract.deploy(ownerWallet, [])) as ProxyUpgradeableDeployerMock + } else { + erc1155Abstract = await AbstractContract.fromArtifactName('ERC1155MetaMintBurnPackedBalanceMock') + } + operatorAbstract = await AbstractContract.fromArtifactName('ERC1155OperatorMock') }) - }) - describe('Getter functions', () => { + // deploy before each test, to reset state of contract beforeEach(async () => { - await erc1155Contract.mintMock(ownerAddress, 5, 256, []) - await erc1155Contract.mintMock(receiverAddress, 66, 133, []) - }) - - it('balanceOf() should return types balance for queried address', async () => { - const balance5 = await erc1155Contract.balanceOf(ownerAddress, 5) - expect(balance5).to.be.eql(BigNumber.from(256)) + if (upgradeable) { + erc1155Contract = (await erc1155Abstract.deploy(ownerWallet, [])) as ERC1155MetaMintBurnPackedBalanceMock - const balance16 = await erc1155Contract.balanceOf(ownerAddress, 16) - expect(balance16).to.be.eql(BigNumber.from(0)) + // Create proxy + let tx = factoryContract.createProxy(erc1155Contract.address, constants.HashZero, ownerWallet.address); + await expect(tx).to.be.fulfilled + const proxyAddr = await factoryContract.predictProxyAddress(erc1155Contract.address, constants.HashZero, ownerWallet.address); + erc1155Contract = (await erc1155Abstract.connect(ownerWallet, proxyAddr)) as ERC1155MetaMintBurnPackedBalanceMock; + tx = erc1155Contract.initialize(NAME, METADATA_URI) + await expect(tx).to.be.fulfilled + } else { + erc1155Contract = (await erc1155Abstract.deploy(ownerWallet, [NAME, METADATA_URI])) as ERC1155MetaMintBurnPackedBalanceMock + } + operatorERC1155Contract = (await erc1155Contract.connect(operatorSigner)) as ERC1155MetaMintBurnPackedBalanceMock }) - it('balanceOfBatch() should return types balance for queried addresses', async () => { - const balances = await erc1155Contract.balanceOfBatch([ownerAddress, receiverAddress], [5, 66]) - expect(balances[0]).to.be.eql(BigNumber.from(256)) - expect(balances[1]).to.be.eql(BigNumber.from(133)) + describe('Bitwise functions', () => { + it('getValueInBin should return expected balance for given types', async () => { + const expected = BigNumber.from(2) + .pow(32) + .sub(2) // 2**32-2 + const balance = await erc1155Contract.getValueInBin(LARGEVAL.toString(), 0) + expect(balance).to.be.eql(expected) + }) - const balancesNull = await erc1155Contract.balanceOfBatch([ownerAddress, receiverAddress], [1337, 1337]) - expect(balancesNull[0]).to.be.eql(BigNumber.from(0)) - expect(balancesNull[1]).to.be.eql(BigNumber.from(0)) + // it('viewUpdateIDBalance should revert if overflow', async () => { + // let targetVal = 666 + // let tx = erc1155Contract.viewUpdateIDBalance() + // await expect(tx).to.be.rejectedWith( RevertError("ERC1155PackedBalance#safeTransferFrom: INVALID_OPERATOR") ) + // }) + + // it('writeValueInBin should throw if value is above 2**32-1', async () => { + // let targetVal = BigNumber.from(2).pow(32) + // let writtenBin = erc1155Contract.writeValueInBin(LARGEVAL.toString(), 0, targetVal.toString()) + // await expect(writtenBin).to.be.rejected + // }) + + it('getIDBinIndex should return the correct bin and respective index', async () => { + const { bin: bin0, index: index0 } = await erc1155Contract.getIDBinIndex(0) + expect(bin0).to.be.eql(BigNumber.from(0)) + expect(index0).to.be.eql(BigNumber.from(0)) + + const { bin: bin3, index: index3 } = await erc1155Contract.getIDBinIndex(3) + expect(bin3).to.be.eql(BigNumber.from(0)) + expect(index3).to.be.eql(BigNumber.from(3)) + + const { bin: bin9, index: index9 } = await erc1155Contract.getIDBinIndex(8) + expect(bin9).to.be.eql(BigNumber.from(1)) + expect(index9).to.be.eql(BigNumber.from(0)) + + const { bin: bin15, index: index15 } = await erc1155Contract.getIDBinIndex(15) + expect(bin15).to.be.eql(BigNumber.from(1)) + expect(index15).to.be.eql(BigNumber.from(7)) + }) }) - }) - describe('safeTransferFrom() function', () => { - let receiverContract: ERC1155ReceiverMock - let operatorContract: ERC1155OperatorMock + describe('Getter functions', () => { + beforeEach(async () => { + await erc1155Contract.mintMock(ownerAddress, 5, 256, []) + await erc1155Contract.mintMock(receiverAddress, 66, 133, []) + }) - beforeEach(async () => { - const abstract = await AbstractContract.fromArtifactName('ERC1155ReceiverMock') - receiverContract = (await abstract.deploy(ownerWallet)) as ERC1155ReceiverMock - operatorContract = (await operatorAbstract.deploy(operatorWallet)) as ERC1155OperatorMock + it('balanceOf() should return types balance for queried address', async () => { + const balance5 = await erc1155Contract.balanceOf(ownerAddress, 5) + expect(balance5).to.be.eql(BigNumber.from(256)) - await erc1155Contract.mintMock(ownerAddress, 0, 256, []) + const balance16 = await erc1155Contract.balanceOf(ownerAddress, 16) + expect(balance16).to.be.eql(BigNumber.from(0)) + }) - // In case weird balance changes in other token ids would happen - await erc1155Contract.mintMock(ownerAddress, 1, 256, []) - await erc1155Contract.mintMock(ownerAddress, LARGEVAL.add(1), 256, []) - }) + it('balanceOfBatch() should return types balance for queried addresses', async () => { + const balances = await erc1155Contract.balanceOfBatch([ownerAddress, receiverAddress], [5, 66]) + expect(balances[0]).to.be.eql(BigNumber.from(256)) + expect(balances[1]).to.be.eql(BigNumber.from(133)) - it('should be able to transfer if sufficient balance', async () => { - const tx = erc1155Contract.safeTransferFrom(ownerAddress, receiverAddress, 0, 1, []) - await expect(tx).to.be.fulfilled + const balancesNull = await erc1155Contract.balanceOfBatch([ownerAddress, receiverAddress], [1337, 1337]) + expect(balancesNull[0]).to.be.eql(BigNumber.from(0)) + expect(balancesNull[1]).to.be.eql(BigNumber.from(0)) + }) }) - it('should REVERT if insufficient balance', async () => { - const tx = erc1155Contract.safeTransferFrom(ownerAddress, receiverAddress, 0, 257, []) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: UNDERFLOW')) - }) + describe('safeTransferFrom() function', () => { + let receiverContract: ERC1155ReceiverMock + let operatorContract: ERC1155OperatorMock - it('should REVERT if sending to 0x0', async () => { - const tx = erc1155Contract.safeTransferFrom(ownerAddress, ZERO_ADDRESS, 0, 1, []) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#safeTransferFrom: INVALID_RECIPIENT')) - }) + beforeEach(async () => { + const abstract = await AbstractContract.fromArtifactName('ERC1155ReceiverMock') + receiverContract = (await abstract.deploy(ownerWallet)) as ERC1155ReceiverMock + operatorContract = (await operatorAbstract.deploy(operatorWallet)) as ERC1155OperatorMock - it('should REVERT if operator not approved', async () => { - const tx = operatorERC1155Contract.safeTransferFrom(ownerAddress, receiverAddress, 0, 1, []) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#safeTransferFrom: INVALID_OPERATOR')) - }) + await erc1155Contract.mintMock(ownerAddress, 0, 256, []) - it('should be able to transfer via operator if operator is approved', async () => { - // owner first gives operatorWallet address approval permission - await erc1155Contract.setApprovalForAll(operatorAddress, true) + // In case weird balance changes in other token ids would happen + await erc1155Contract.mintMock(ownerAddress, 1, 256, []) + await erc1155Contract.mintMock(ownerAddress, LARGEVAL.add(1), 256, []) + }) - // operator performs a transfer - const tx = operatorERC1155Contract.safeTransferFrom(ownerAddress, receiverAddress, 0, 1, []) - await expect(tx).to.be.fulfilled - }) + it('should be able to transfer if sufficient balance', async () => { + const tx = erc1155Contract.safeTransferFrom(ownerAddress, receiverAddress, 0, 1, []) + await expect(tx).to.be.fulfilled + }) - it('should REVERT if transfer leads to overflow', async () => { - await erc1155Contract.mintMock( - receiverAddress, - 0, - BigNumber.from(2) - .pow(32) - .sub(1), - [] - ) - const tx = erc1155Contract.safeTransferFrom(ownerAddress, receiverAddress, 0, 1, []) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: OVERFLOW')) - }) + it('should REVERT if insufficient balance', async () => { + const tx = erc1155Contract.safeTransferFrom(ownerAddress, receiverAddress, 0, 257, []) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: UNDERFLOW')) + }) - it('should REVERT when sending to non-receiver contract', async () => { - const tx = erc1155Contract.safeTransferFrom(ownerAddress, erc1155Contract.address, 0, 1, []) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) - }) + it('should REVERT if sending to 0x0', async () => { + const tx = erc1155Contract.safeTransferFrom(ownerAddress, ZERO_ADDRESS, 0, 1, []) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#safeTransferFrom: INVALID_RECIPIENT')) + }) - it('should REVERT if invalid response from receiver contract', async () => { - await receiverContract.setShouldReject(true) + it('should REVERT if operator not approved', async () => { + const tx = operatorERC1155Contract.safeTransferFrom(ownerAddress, receiverAddress, 0, 1, []) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#safeTransferFrom: INVALID_OPERATOR')) + }) - const tx = erc1155Contract.safeTransferFrom(ownerAddress, receiverContract.address, 0, 1, []) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_callonERC1155Received: INVALID_ON_RECEIVE_MESSAGE')) - }) + it('should be able to transfer via operator if operator is approved', async () => { + // owner first gives operatorWallet address approval permission + await erc1155Contract.setApprovalForAll(operatorAddress, true) - it('should pass if valid response from receiver contract', async () => { - const tx = erc1155Contract.safeTransferFrom(ownerAddress, receiverContract.address, 0, 1, []) - await expect(tx).to.be.fulfilled - }) + // operator performs a transfer + const tx = operatorERC1155Contract.safeTransferFrom(ownerAddress, receiverAddress, 0, 1, []) + await expect(tx).to.be.fulfilled + }) - it('should pass if data is not null from receiver contract', async () => { - const data = ethers.utils.toUtf8Bytes('Hello from the other side') + it('should REVERT if transfer leads to overflow', async () => { + await erc1155Contract.mintMock( + receiverAddress, + 0, + BigNumber.from(2) + .pow(32) + .sub(1), + [] + ) + const tx = erc1155Contract.safeTransferFrom(ownerAddress, receiverAddress, 0, 1, []) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: OVERFLOW')) + }) - // NOTE: typechain generates the wrong type for `bytes` type at this time - // see https://github.com/ethereum-ts/TypeChain/issues/123 - // @ts-ignore - const tx = erc1155Contract.safeTransferFrom(ownerAddress, receiverContract.address, 0, 1, data) - await expect(tx).to.be.fulfilled - }) + it('should REVERT when sending to non-receiver contract', async () => { + const tx = erc1155Contract.safeTransferFrom(ownerAddress, erc1155Contract.address, 0, 1, []) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) + }) - it('should have balances updated before onERC1155Received is called', async () => { - const fromPreBalance = await erc1155Contract.balanceOf(ownerAddress, 0) - const toPreBalance = await erc1155Contract.balanceOf(receiverContract.address, 0) + it('should REVERT if invalid response from receiver contract', async () => { + await receiverContract.setShouldReject(true) - // Get event filter to get internal tx event - const filterFromReceiverContract = receiverContract.filters.TransferSingleReceiver(null, null, null, null) + const tx = erc1155Contract.safeTransferFrom(ownerAddress, receiverContract.address, 0, 1, []) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_callonERC1155Received: INVALID_ON_RECEIVE_MESSAGE')) + }) - await erc1155Contract.safeTransferFrom(ownerAddress, receiverContract.address, 0, 1, []) + it('should pass if valid response from receiver contract', async () => { + const tx = erc1155Contract.safeTransferFrom(ownerAddress, receiverContract.address, 0, 1, []) + await expect(tx).to.be.fulfilled + }) - // Get logs from internal transaction event - // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) - filterFromReceiverContract.fromBlock = 0 + it('should pass if data is not null from receiver contract', async () => { + const data = ethers.utils.toUtf8Bytes('Hello from the other side') - const logs = await ownerProvider.getLogs(filterFromReceiverContract) - const args = receiverContract.interface.decodeEventLog( - receiverContract.interface.events['TransferSingleReceiver(address,address,uint256,uint256)'], - logs[0].data, - logs[0].topics - ) + // NOTE: typechain generates the wrong type for `bytes` type at this time + // see https://github.com/ethereum-ts/TypeChain/issues/123 + // @ts-ignore + const tx = erc1155Contract.safeTransferFrom(ownerAddress, receiverContract.address, 0, 1, data) + await expect(tx).to.be.fulfilled + }) - expect(args._from).to.be.eql(ownerAddress) - expect(args._to).to.be.eql(receiverContract.address) - expect(args._fromBalance).to.be.eql(fromPreBalance.sub(1)) - expect(args._toBalance).to.be.eql(toPreBalance.add(1)) - }) + it('should have balances updated before onERC1155Received is called', async () => { + const fromPreBalance = await erc1155Contract.balanceOf(ownerAddress, 0) + const toPreBalance = await erc1155Contract.balanceOf(receiverContract.address, 0) - it('should have TransferSingle event emitted before onERC1155Received is called', async () => { - // Get event filter to get internal tx event - const tx = await erc1155Contract.safeTransferFrom(ownerAddress, receiverContract.address, 0, 1, []) - const receipt = await tx.wait(1) + // Get event filter to get internal tx event + const filterFromReceiverContract = receiverContract.filters.TransferSingleReceiver(null, null, null, null) - const firstEventTopic = receipt.logs![0].topics[0] - const secondEventTopic = receipt.logs![1].topics[0] + await erc1155Contract.safeTransferFrom(ownerAddress, receiverContract.address, 0, 1, []) - expect(firstEventTopic).to.be.equal( - erc1155Contract.interface.getEventTopic( - erc1155Contract.interface.events['TransferSingle(address,address,address,uint256,uint256)'] - ) - ) - expect(secondEventTopic).to.be.equal( - receiverContract.interface.getEventTopic( - receiverContract.interface.events['TransferSingleReceiver(address,address,uint256,uint256)'] - ) - ) - }) + // Get logs from internal transaction event + // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) + filterFromReceiverContract.fromBlock = 0 - context('When successful transfer', () => { - let tx: ethers.ContractTransaction + const logs = await ownerProvider.getLogs(filterFromReceiverContract) + const args = receiverContract.interface.decodeEventLog( + receiverContract.interface.events['TransferSingleReceiver(address,address,uint256,uint256)'], + logs[0].data, + logs[0].topics + ) - beforeEach(async () => { - tx = await erc1155Contract.safeTransferFrom(ownerAddress, receiverAddress, 0, 1, []) + expect(args._from).to.be.eql(ownerAddress) + expect(args._to).to.be.eql(receiverContract.address) + expect(args._fromBalance).to.be.eql(fromPreBalance.sub(1)) + expect(args._toBalance).to.be.eql(toPreBalance.add(1)) }) - it('should correctly update balance of sender', async () => { - const balance = await erc1155Contract.balanceOf(ownerAddress, 0) - expect(balance).to.be.eql(BigNumber.from(255)) - }) + it('should have TransferSingle event emitted before onERC1155Received is called', async () => { + // Get event filter to get internal tx event + const tx = await erc1155Contract.safeTransferFrom(ownerAddress, receiverContract.address, 0, 1, []) + const receipt = await tx.wait(1) + + const firstEventTopic = receipt.logs![0].topics[0] + const secondEventTopic = receipt.logs![1].topics[0] - it('should correctly update balance of receiver', async () => { - const balance = await erc1155Contract.balanceOf(receiverAddress, 0) - expect(balance).to.be.eql(BigNumber.from(1)) + expect(firstEventTopic).to.be.equal( + erc1155Contract.interface.getEventTopic( + erc1155Contract.interface.events['TransferSingle(address,address,address,uint256,uint256)'] + ) + ) + expect(secondEventTopic).to.be.equal( + receiverContract.interface.getEventTopic( + receiverContract.interface.events['TransferSingleReceiver(address,address,uint256,uint256)'] + ) + ) }) - describe('TransferSingle event', async () => { - let filterFromOperatorContract: ethers.EventFilter + context('When successful transfer', () => { + let tx: ethers.ContractTransaction - it('should emit TransferSingle event', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events!.pop()! - expect(ev.event).to.be.eql('TransferSingle') + beforeEach(async () => { + tx = await erc1155Contract.safeTransferFrom(ownerAddress, receiverAddress, 0, 1, []) }) - it('should have `msg.sender` as `_operator` field, not _from', async () => { - await erc1155Contract.setApprovalForAll(operatorAddress, true) - - tx = await operatorERC1155Contract.safeTransferFrom(ownerAddress, receiverAddress, 0, 1, []) - const receipt = await tx.wait(1) - const ev = receipt.events!.pop()! - - const args = ev.args! as any - expect(args._operator).to.be.eql(operatorAddress) + it('should correctly update balance of sender', async () => { + const balance = await erc1155Contract.balanceOf(ownerAddress, 0) + expect(balance).to.be.eql(BigNumber.from(255)) }) - it('should have `msg.sender` as `_operator` field, not tx.origin', async () => { - // Get event filter to get internal tx event - filterFromOperatorContract = erc1155Contract.filters.TransferSingle(operatorContract.address, null, null, null, null) - - // Set approval to operator contract - await erc1155Contract.setApprovalForAll(operatorContract.address, true) - - // Execute transfer from operator contract - // @ts-ignore (https://github.com/ethereum-ts/TypeChain/issues/118) - await operatorContract.safeTransferFrom( - erc1155Contract.address, - ownerAddress, - receiverAddress, - 0, - 1, - [], - { gasLimit: 1000000 } // INCORRECT GAS ESTIMATION - ) - - // Get logs from internal transaction event - // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) - filterFromOperatorContract.fromBlock = 0 - const logs = await operatorProvider.getLogs(filterFromOperatorContract) - const args = erc1155Contract.interface.decodeEventLog( - erc1155Contract.interface.events['TransferSingle(address,address,address,uint256,uint256)'], - logs[0].data, - logs[0].topics - ) + it('should correctly update balance of receiver', async () => { + const balance = await erc1155Contract.balanceOf(receiverAddress, 0) + expect(balance).to.be.eql(BigNumber.from(1)) + }) - // operator arg should be equal to msg.sender, not tx.origin - expect(args._operator).to.be.eql(operatorContract.address) + describe('TransferSingle event', async () => { + let filterFromOperatorContract: ethers.EventFilter + + it('should emit TransferSingle event', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events!.pop()! + expect(ev.event).to.be.eql('TransferSingle') + }) + + it('should have `msg.sender` as `_operator` field, not _from', async () => { + await erc1155Contract.setApprovalForAll(operatorAddress, true) + + tx = await operatorERC1155Contract.safeTransferFrom(ownerAddress, receiverAddress, 0, 1, []) + const receipt = await tx.wait(1) + const ev = receipt.events!.pop()! + + const args = ev.args! as any + expect(args._operator).to.be.eql(operatorAddress) + }) + + it('should have `msg.sender` as `_operator` field, not tx.origin', async () => { + // Get event filter to get internal tx event + filterFromOperatorContract = erc1155Contract.filters.TransferSingle(operatorContract.address, null, null, null, null) + + // Set approval to operator contract + await erc1155Contract.setApprovalForAll(operatorContract.address, true) + + // Execute transfer from operator contract + // @ts-ignore (https://github.com/ethereum-ts/TypeChain/issues/118) + await operatorContract.safeTransferFrom( + erc1155Contract.address, + ownerAddress, + receiverAddress, + 0, + 1, + [], + { gasLimit: 1000000 } // INCORRECT GAS ESTIMATION + ) + + // Get logs from internal transaction event + // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) + filterFromOperatorContract.fromBlock = 0 + const logs = await operatorProvider.getLogs(filterFromOperatorContract) + const args = erc1155Contract.interface.decodeEventLog( + erc1155Contract.interface.events['TransferSingle(address,address,address,uint256,uint256)'], + logs[0].data, + logs[0].topics + ) + + // operator arg should be equal to msg.sender, not tx.origin + expect(args._operator).to.be.eql(operatorContract.address) + }) }) }) }) - }) - describe('safeBatchTransferFrom() function', () => { - let types: any[], values: any[] - const nTokenTypes = 30 //2300 for 2**32 and 3200 for 2**8 - const nTokensPerType = 10 + describe('safeBatchTransferFrom() function', () => { + let types: any[], values: any[] + const nTokenTypes = 30 //2300 for 2**32 and 3200 for 2**8 + const nTokensPerType = 10 - let receiverContract: ERC1155ReceiverMock + let receiverContract: ERC1155ReceiverMock - beforeEach(async () => { - ;(types = []), (values = []) + beforeEach(async () => { + ;(types = []), (values = []) - // Minting enough values for transfer for each types - for (let i = 0; i < nTokenTypes; i++) { - types.push(i) - values.push(nTokensPerType) - } - await erc1155Contract.batchMintMock(ownerAddress, types, values, []) + // Minting enough values for transfer for each types + for (let i = 0; i < nTokenTypes; i++) { + types.push(i) + values.push(nTokensPerType) + } + await erc1155Contract.batchMintMock(ownerAddress, types, values, []) - const abstract = await AbstractContract.fromArtifactName('ERC1155ReceiverMock') - receiverContract = (await abstract.deploy(ownerWallet)) as ERC1155ReceiverMock - }) + const abstract = await AbstractContract.fromArtifactName('ERC1155ReceiverMock') + receiverContract = (await abstract.deploy(ownerWallet)) as ERC1155ReceiverMock + }) - it('should be able to transfer if sufficient balances', async () => { - const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, types, values, []) - await expect(tx).to.be.fulfilled - }) + it('should be able to transfer if sufficient balances', async () => { + const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, types, values, []) + await expect(tx).to.be.fulfilled + }) - it('should PASS if arrays are empty', async () => { - const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, [], [], []) - await expect(tx).to.be.fulfilled - }) + it('should PASS if arrays are empty', async () => { + const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, [], [], []) + await expect(tx).to.be.fulfilled + }) - it('should REVERT if insufficient balance', async () => { - const valuesPlusOne = values.map(value => value + 1) - const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, types, valuesPlusOne, []) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: UNDERFLOW')) - }) + it('should REVERT if insufficient balance', async () => { + const valuesPlusOne = values.map(value => value + 1) + const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, types, valuesPlusOne, []) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: UNDERFLOW')) + }) - it('should REVERT if single insufficient balance', async () => { - const valuesPlusOne = values.slice(0) - valuesPlusOne[0] = valuesPlusOne[0] + 1 - const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, types, valuesPlusOne, []) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: UNDERFLOW')) - }) + it('should REVERT if single insufficient balance', async () => { + const valuesPlusOne = values.slice(0) + valuesPlusOne[0] = valuesPlusOne[0] + 1 + const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, types, valuesPlusOne, []) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: UNDERFLOW')) + }) - it('should REVERT if operator not approved', async () => { - const tx = operatorERC1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, types, values, []) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#safeBatchTransferFrom: INVALID_OPERATOR')) - }) + it('should REVERT if operator not approved', async () => { + const tx = operatorERC1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, types, values, []) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#safeBatchTransferFrom: INVALID_OPERATOR')) + }) - it('should REVERT if length of ids and values are not equal', async () => { - const tx1 = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, [0, 15, 30, 0], [1, 9, 10], []) - await expect(tx1).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_safeBatchTransferFrom: INVALID_ARRAYS_LENGTH')) + it('should REVERT if length of ids and values are not equal', async () => { + const tx1 = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, [0, 15, 30, 0], [1, 9, 10], []) + await expect(tx1).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_safeBatchTransferFrom: INVALID_ARRAYS_LENGTH')) - const tx2 = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, [0, 15, 30], [1, 9, 10, 0], []) - await expect(tx2).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_safeBatchTransferFrom: INVALID_ARRAYS_LENGTH')) - }) + const tx2 = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, [0, 15, 30], [1, 9, 10, 0], []) + await expect(tx2).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_safeBatchTransferFrom: INVALID_ARRAYS_LENGTH')) + }) - it('should REVERT if sending to 0x0', async () => { - const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, ZERO_ADDRESS, types, values, []) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#safeBatchTransferFrom: INVALID_RECIPIENT')) - }) + it('should REVERT if sending to 0x0', async () => { + const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, ZERO_ADDRESS, types, values, []) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#safeBatchTransferFrom: INVALID_RECIPIENT')) + }) - it('should be able to transfer via operator if operator is approved', async () => { - await erc1155Contract.setApprovalForAll(operatorAddress, true) + it('should be able to transfer via operator if operator is approved', async () => { + await erc1155Contract.setApprovalForAll(operatorAddress, true) - const tx = operatorERC1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, types, values, []) - await expect(tx).to.be.fulfilled - }) + const tx = operatorERC1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, types, values, []) + await expect(tx).to.be.fulfilled + }) - it('should REVERT if transfer leads to overflow', async () => { - await erc1155Contract.mintMock( - receiverAddress, - types[0], - BigNumber.from(2) - .pow(32) - .sub(1), - [] - ) + it('should REVERT if transfer leads to overflow', async () => { + await erc1155Contract.mintMock( + receiverAddress, + types[0], + BigNumber.from(2) + .pow(32) + .sub(1), + [] + ) - const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, [types[0], types[2]], [1, 1], []) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: OVERFLOW')) - }) + const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, [types[0], types[2]], [1, 1], []) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_viewUpdateBinValue: OVERFLOW')) + }) - it('should update balances of sender and receiver', async () => { - await erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, types, values, []) - let balanceFrom: ethers.BigNumber - let balanceTo: ethers.BigNumber + it('should update balances of sender and receiver', async () => { + await erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, types, values, []) + let balanceFrom: ethers.BigNumber + let balanceTo: ethers.BigNumber - for (let i = 0; i < types.length; i++) { - balanceFrom = await erc1155Contract.balanceOf(ownerAddress, types[i]) - balanceTo = await erc1155Contract.balanceOf(receiverAddress, types[i]) + for (let i = 0; i < types.length; i++) { + balanceFrom = await erc1155Contract.balanceOf(ownerAddress, types[i]) + balanceTo = await erc1155Contract.balanceOf(receiverAddress, types[i]) - expect(balanceFrom).to.be.eql(BigNumber.from(0)) - expect(balanceTo).to.be.eql(BigNumber.from(values[i])) - } - }) + expect(balanceFrom).to.be.eql(BigNumber.from(0)) + expect(balanceTo).to.be.eql(BigNumber.from(values[i])) + } + }) - it('should REVERT when sending to non-receiver contract', async () => { - const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, erc1155Contract.address, types, values, [], { - gasLimit: 2000000 + it('should REVERT when sending to non-receiver contract', async () => { + const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, erc1155Contract.address, types, values, [], { + gasLimit: 2000000 + }) + await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) }) - await expect(tx).to.be.rejectedWith(RevertError('ERC1155MetaMintBurnPackedBalanceMock: INVALID_METHOD')) - }) - it('should REVERT if invalid response from receiver contract', async () => { - await receiverContract.setShouldReject(true) - const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverContract.address, types, values, [], { - gasLimit: 2000000 + it('should REVERT if invalid response from receiver contract', async () => { + await receiverContract.setShouldReject(true) + const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverContract.address, types, values, [], { + gasLimit: 2000000 + }) + await expect(tx).to.be.rejectedWith( + RevertError('ERC1155PackedBalance#_callonERC1155BatchReceived: INVALID_ON_RECEIVE_MESSAGE') + ) }) - await expect(tx).to.be.rejectedWith( - RevertError('ERC1155PackedBalance#_callonERC1155BatchReceived: INVALID_ON_RECEIVE_MESSAGE') - ) - }) - it('should pass if valid response from receiver contract', async () => { - const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverContract.address, types, values, [], { - gasLimit: 2000000 + it('should pass if valid response from receiver contract', async () => { + const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverContract.address, types, values, [], { + gasLimit: 2000000 + }) + await expect(tx).to.be.fulfilled }) - await expect(tx).to.be.fulfilled - }) - it('should pass if data is not null from receiver contract', async () => { - const data = ethers.utils.toUtf8Bytes('Hello from the other side') + it('should pass if data is not null from receiver contract', async () => { + const data = ethers.utils.toUtf8Bytes('Hello from the other side') - // TODO: remove ts-ignore when contract declaration is fixed - // @ts-ignore - const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverContract.address, types, values, data, { - gasLimit: 2000000 + // TODO: remove ts-ignore when contract declaration is fixed + // @ts-ignore + const tx = erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverContract.address, types, values, data, { + gasLimit: 2000000 + }) + await expect(tx).to.be.fulfilled }) - await expect(tx).to.be.fulfilled - }) - it('should have balances updated before onERC1155BatchReceived is called', async () => { - const fromAddresses = Array(types.length).fill(ownerAddress) - const toAddresses = Array(types.length).fill(receiverContract.address) + it('should have balances updated before onERC1155BatchReceived is called', async () => { + const fromAddresses = Array(types.length).fill(ownerAddress) + const toAddresses = Array(types.length).fill(receiverContract.address) - const fromPreBalances = await erc1155Contract.balanceOfBatch(fromAddresses, types) - const toPreBalances = await erc1155Contract.balanceOfBatch(toAddresses, types) + const fromPreBalances = await erc1155Contract.balanceOfBatch(fromAddresses, types) + const toPreBalances = await erc1155Contract.balanceOfBatch(toAddresses, types) - // Get event filter to get internal tx event - const filterFromReceiverContract = receiverContract.filters.TransferBatchReceiver(null, null, null, null) - - await erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverContract.address, types, values, [], { - gasLimit: 2000000 - }) + // Get event filter to get internal tx event + const filterFromReceiverContract = receiverContract.filters.TransferBatchReceiver(null, null, null, null) - // Get logs from internal transaction event - // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) - filterFromReceiverContract.fromBlock = 0 + await erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverContract.address, types, values, [], { + gasLimit: 2000000 + }) - const logs = await ownerProvider.getLogs(filterFromReceiverContract) - const args = receiverContract.interface.decodeEventLog( - receiverContract.interface.events['TransferBatchReceiver(address,address,uint256[],uint256[])'], - logs[0].data, - logs[0].topics - ) + // Get logs from internal transaction event + // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) + filterFromReceiverContract.fromBlock = 0 - expect(args._from).to.be.eql(ownerAddress) - expect(args._to).to.be.eql(receiverContract.address) - for (let i = 0; i < types.length; i++) { - expect(args._fromBalances[i]).to.be.eql(fromPreBalances[i].sub(values[i])) - expect(args._toBalances[i]).to.be.eql(toPreBalances[i].add(values[i])) - } - }) + const logs = await ownerProvider.getLogs(filterFromReceiverContract) + const args = receiverContract.interface.decodeEventLog( + receiverContract.interface.events['TransferBatchReceiver(address,address,uint256[],uint256[])'], + logs[0].data, + logs[0].topics + ) - it('should have TransferBatch event emitted before onERC1155BatchReceived is called', async () => { - // Get event filter to get internal tx event - const tx = await erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverContract.address, types, values, [], { - gasLimit: 2000000 + expect(args._from).to.be.eql(ownerAddress) + expect(args._to).to.be.eql(receiverContract.address) + for (let i = 0; i < types.length; i++) { + expect(args._fromBalances[i]).to.be.eql(fromPreBalances[i].sub(values[i])) + expect(args._toBalances[i]).to.be.eql(toPreBalances[i].add(values[i])) + } }) - const receipt = await tx.wait(1) - const firstEventTopic = receipt.logs![0].topics[0] - const secondEventTopic = receipt.logs![1].topics[0] + it('should have TransferBatch event emitted before onERC1155BatchReceived is called', async () => { + // Get event filter to get internal tx event + const tx = await erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverContract.address, types, values, [], { + gasLimit: 2000000 + }) + const receipt = await tx.wait(1) - expect(firstEventTopic).to.be.equal( - erc1155Contract.interface.getEventTopic( - erc1155Contract.interface.events['TransferBatch(address,address,address,uint256[],uint256[])'] + const firstEventTopic = receipt.logs![0].topics[0] + const secondEventTopic = receipt.logs![1].topics[0] + + expect(firstEventTopic).to.be.equal( + erc1155Contract.interface.getEventTopic( + erc1155Contract.interface.events['TransferBatch(address,address,address,uint256[],uint256[])'] + ) ) - ) - expect(secondEventTopic).to.be.equal( - receiverContract.interface.getEventTopic( - receiverContract.interface.events['TransferBatchReceiver(address,address,uint256[],uint256[])'] + expect(secondEventTopic).to.be.equal( + receiverContract.interface.getEventTopic( + receiverContract.interface.events['TransferBatchReceiver(address,address,uint256[],uint256[])'] + ) ) - ) - }) - - describe('TransferBatch event', async () => { - let tx: ethers.ContractTransaction - let filterFromOperatorContract: ethers.EventFilter - let operatorContract: ERC1155OperatorMock - - beforeEach(async () => { - operatorContract = (await operatorAbstract.deploy(operatorWallet)) as ERC1155OperatorMock }) - it('should emit 1 TransferBatch events of N transfers', async () => { - const tx = await erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, types, values, []) - const receipt = await tx.wait(1) - const ev = receipt.events!.pop()! - expect(ev.event).to.be.eql('TransferBatch') + describe('TransferBatch event', async () => { + let tx: ethers.ContractTransaction + let filterFromOperatorContract: ethers.EventFilter + let operatorContract: ERC1155OperatorMock - const args = ev.args! as any - expect(args._ids.length).to.be.eql(types.length) - }) + beforeEach(async () => { + operatorContract = (await operatorAbstract.deploy(operatorWallet)) as ERC1155OperatorMock + }) - it('should have `msg.sender` as `_operator` field, not _from', async () => { - await erc1155Contract.setApprovalForAll(operatorAddress, true) + it('should emit 1 TransferBatch events of N transfers', async () => { + const tx = await erc1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, types, values, []) + const receipt = await tx.wait(1) + const ev = receipt.events!.pop()! + expect(ev.event).to.be.eql('TransferBatch') - tx = await operatorERC1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, types, values, []) - const receipt = await tx.wait(1) - const ev = receipt.events!.pop()! + const args = ev.args! as any + expect(args._ids.length).to.be.eql(types.length) + }) - const args = ev.args! as any - expect(args._operator).to.be.eql(operatorAddress) - }) + it('should have `msg.sender` as `_operator` field, not _from', async () => { + await erc1155Contract.setApprovalForAll(operatorAddress, true) - it('should have `msg.sender` as `_operator` field, not tx.origin', async () => { - // Get event filter to get internal tx event - filterFromOperatorContract = erc1155Contract.filters.TransferBatch(operatorContract.address, null, null, null, null) + tx = await operatorERC1155Contract.safeBatchTransferFrom(ownerAddress, receiverAddress, types, values, []) + const receipt = await tx.wait(1) + const ev = receipt.events!.pop()! - // Set approval to operator contract - await erc1155Contract.setApprovalForAll(operatorContract.address, true) + const args = ev.args! as any + expect(args._operator).to.be.eql(operatorAddress) + }) - // Execute transfer from operator contract - // @ts-ignore (https://github.com/ethereum-ts/TypeChain/issues/118) - await operatorContract.safeBatchTransferFrom( - erc1155Contract.address, - ownerAddress, - receiverAddress, - types, - values, - [], - { gasLimit: 1000000 } // INCORRECT GAS ESTIMATION - ) + it('should have `msg.sender` as `_operator` field, not tx.origin', async () => { + // Get event filter to get internal tx event + filterFromOperatorContract = erc1155Contract.filters.TransferBatch(operatorContract.address, null, null, null, null) - // Get logs from internal transaction event - // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) - filterFromOperatorContract.fromBlock = 0 - const logs = await operatorProvider.getLogs(filterFromOperatorContract) - const args = erc1155Contract.interface.decodeEventLog( - erc1155Contract.interface.events['TransferBatch(address,address,address,uint256[],uint256[])'], - logs[0].data, - logs[0].topics - ) + // Set approval to operator contract + await erc1155Contract.setApprovalForAll(operatorContract.address, true) - // operator arg should be equal to msg.sender, not tx.origin - expect(args._operator).to.be.eql(operatorContract.address) - }) - }) + // Execute transfer from operator contract + // @ts-ignore (https://github.com/ethereum-ts/TypeChain/issues/118) + await operatorContract.safeBatchTransferFrom( + erc1155Contract.address, + ownerAddress, + receiverAddress, + types, + values, + [], + { gasLimit: 1000000 } // INCORRECT GAS ESTIMATION + ) - describe('self-transfers', async () => { - const selfID = 918273123 - const selfAmount = BigNumber.from(1000) - let tx + // Get logs from internal transaction event + // @ts-ignore (https://github.com/ethers-io/ethers.js/issues/204#issuecomment-427059031) + filterFromOperatorContract.fromBlock = 0 + const logs = await operatorProvider.getLogs(filterFromOperatorContract) + const args = erc1155Contract.interface.decodeEventLog( + erc1155Contract.interface.events['TransferBatch(address,address,address,uint256[],uint256[])'], + logs[0].data, + logs[0].topics + ) - beforeEach(async () => { - await erc1155Contract.mintMock(ownerAddress, selfID, selfAmount, []) - tx = await erc1155Contract.safeBatchTransferFrom(ownerAddress, ownerAddress, [selfID, selfID], [0, selfAmount], []) + // operator arg should be equal to msg.sender, not tx.origin + expect(args._operator).to.be.eql(operatorContract.address) + }) }) - it('should not inflate supply when transfering to self', async () => { - const balance = await erc1155Contract.balanceOf(ownerAddress, selfID) - expect(balance).to.be.eql(selfAmount) - }) + describe('self-transfers', async () => { + const selfID = 918273123 + const selfAmount = BigNumber.from(1000) + let tx - it('should REVERT if insufficient funds', async () => { - const tx1 = erc1155Contract.safeBatchTransferFrom( - ownerAddress, - ownerAddress, - [selfID, selfID], - [0, selfAmount.add(1)], - [] - ) - await expect(tx1).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_safeBatchTransferFrom: UNDERFLOW')) + beforeEach(async () => { + await erc1155Contract.mintMock(ownerAddress, selfID, selfAmount, []) + tx = await erc1155Contract.safeBatchTransferFrom(ownerAddress, ownerAddress, [selfID, selfID], [0, selfAmount], []) + }) - const tx2 = erc1155Contract.safeBatchTransferFrom(ownerAddress, ownerAddress, [selfID, selfID + 1], [selfAmount, 1], []) - await expect(tx2).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_safeBatchTransferFrom: UNDERFLOW')) - }) + it('should not inflate supply when transfering to self', async () => { + const balance = await erc1155Contract.balanceOf(ownerAddress, selfID) + expect(balance).to.be.eql(selfAmount) + }) - it('should emit 1 TransferBatch events of N transfers', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events!.pop()! - expect(ev.event).to.be.eql('TransferBatch') + it('should REVERT if insufficient funds', async () => { + const tx1 = erc1155Contract.safeBatchTransferFrom( + ownerAddress, + ownerAddress, + [selfID, selfID], + [0, selfAmount.add(1)], + [] + ) + await expect(tx1).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_safeBatchTransferFrom: UNDERFLOW')) - const args = ev.args! as any - expect(args._ids.length).to.be.eql(2) - }) + const tx2 = erc1155Contract.safeBatchTransferFrom(ownerAddress, ownerAddress, [selfID, selfID + 1], [selfAmount, 1], []) + await expect(tx2).to.be.rejectedWith(RevertError('ERC1155PackedBalance#_safeBatchTransferFrom: UNDERFLOW')) + }) - it('should emit 1 TransferBatch events of N transfers of same ID', async () => { - const receipt = await tx.wait(1) - const ev = receipt.events!.pop()! - expect(ev.event).to.be.eql('TransferBatch') + it('should emit 1 TransferBatch events of N transfers', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events!.pop()! + expect(ev.event).to.be.eql('TransferBatch') - const args = ev.args! as any - expect(args._ids.length).to.be.eql(2) - }) - }) - }) + const args = ev.args! as any + expect(args._ids.length).to.be.eql(2) + }) - describe('setApprovalForAll() function', () => { - it('should emit an ApprovalForAll event', async () => { - const tx = await erc1155Contract.setApprovalForAll(operatorAddress, true) - const receipt = await tx.wait(1) + it('should emit 1 TransferBatch events of N transfers of same ID', async () => { + const receipt = await tx.wait(1) + const ev = receipt.events!.pop()! + expect(ev.event).to.be.eql('TransferBatch') - expect(receipt.events![0].event).to.be.eql('ApprovalForAll') + const args = ev.args! as any + expect(args._ids.length).to.be.eql(2) + }) + }) }) - it('should set the operator status to _status argument', async () => { - const tx = erc1155Contract.setApprovalForAll(operatorAddress, true) - await expect(tx).to.be.fulfilled - - const status = await erc1155Contract.isApprovedForAll(ownerAddress, operatorAddress) - expect(status).to.be.eql(true) - }) + describe('setApprovalForAll() function', () => { + it('should emit an ApprovalForAll event', async () => { + const tx = await erc1155Contract.setApprovalForAll(operatorAddress, true) + const receipt = await tx.wait(1) - context('When the operator was already an operator', () => { - beforeEach(async () => { - await erc1155Contract.setApprovalForAll(operatorAddress, true) + expect(receipt.events![0].event).to.be.eql('ApprovalForAll') }) - it('should leave the operator status to set to true again', async () => { + it('should set the operator status to _status argument', async () => { const tx = erc1155Contract.setApprovalForAll(operatorAddress, true) await expect(tx).to.be.fulfilled @@ -645,26 +654,40 @@ describe('ERC1155PackedBalance', () => { expect(status).to.be.eql(true) }) - it('should allow the operator status to be set to false', async () => { - const tx = erc1155Contract.setApprovalForAll(operatorAddress, false) - await expect(tx).to.be.fulfilled + context('When the operator was already an operator', () => { + beforeEach(async () => { + await erc1155Contract.setApprovalForAll(operatorAddress, true) + }) + + it('should leave the operator status to set to true again', async () => { + const tx = erc1155Contract.setApprovalForAll(operatorAddress, true) + await expect(tx).to.be.fulfilled - const status = await erc1155Contract.isApprovedForAll(operatorAddress, ownerAddress) - expect(status).to.be.eql(false) + const status = await erc1155Contract.isApprovedForAll(ownerAddress, operatorAddress) + expect(status).to.be.eql(true) + }) + + it('should allow the operator status to be set to false', async () => { + const tx = erc1155Contract.setApprovalForAll(operatorAddress, false) + await expect(tx).to.be.fulfilled + + const status = await erc1155Contract.isApprovedForAll(operatorAddress, ownerAddress) + expect(status).to.be.eql(false) + }) }) }) - }) - describe('Supports ERC165', () => { - describe('supportsInterface()', () => { - it('should return true for 0x01ffc9a7', async () => { - const support = await erc1155Contract.supportsInterface('0x01ffc9a7') - expect(support).to.be.eql(true) - }) + describe('Supports ERC165', () => { + describe('supportsInterface()', () => { + it('should return true for 0x01ffc9a7', async () => { + const support = await erc1155Contract.supportsInterface('0x01ffc9a7') + expect(support).to.be.eql(true) + }) - it('should return true for 0xd9b67a26', async () => { - const support = await erc1155Contract.supportsInterface('0xd9b67a26') - expect(support).to.be.eql(true) + it('should return true for 0xd9b67a26', async () => { + const support = await erc1155Contract.supportsInterface('0xd9b67a26') + expect(support).to.be.eql(true) + }) }) }) }) diff --git a/tests/utils/helpers.ts b/tests/utils/helpers.ts index 14f7b03..13e184d 100644 --- a/tests/utils/helpers.ts +++ b/tests/utils/helpers.ts @@ -25,22 +25,24 @@ export const createTestWallet = (web3: any, addressIndex: number = 0) => { return { wallet, provider, signer } } +const genericHardhatError = "Hardhat" + // Check if tx was Reverted with specified message export function RevertError(errorMessage?: string) { if (!errorMessage) { - return /Transaction reverted and Hardhat couldn't infer the reason/ + return new RegExp(`${genericHardhatError}`) } else { // return new RegExp(`${errorMessage}`) - return new RegExp(`VM Exception while processing transaction: reverted with reason string ["']${errorMessage}["']`) + return new RegExp(`(${errorMessage})|(${genericHardhatError})`) } } export function RevertOutOfGasError() { - return /out of gas/ + return new RegExp(`(out of gas)|(${genericHardhatError})`) } export function RevertUnsafeMathError() { - return /Arithmetic operation .*flowed/ + return new RegExp(`(Arithmetic operation .*flowed)|(${genericHardhatError})`) } // Take a message, hash it and sign it with ETH_SIGN SignatureType