diff --git a/contracts/erc20/CHANGELOG.json b/contracts/erc20/CHANGELOG.json index d33d7ae852..f6d2ec04ca 100644 --- a/contracts/erc20/CHANGELOG.json +++ b/contracts/erc20/CHANGELOG.json @@ -5,6 +5,10 @@ { "note": "Add `LibERC20Token.approveIfBelow()`", "pr": 2512 + }, + { + "note": "Add solidity 0.6 contracts", + "pr": 2545 } ] }, diff --git a/contracts/erc20/contracts/src/v06/IERC20TokenV06.sol b/contracts/erc20/contracts/src/v06/IERC20TokenV06.sol new file mode 100644 index 0000000000..c7d347bf7f --- /dev/null +++ b/contracts/erc20/contracts/src/v06/IERC20TokenV06.sol @@ -0,0 +1,95 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; + + +interface IERC20TokenV06 { + + // solhint-disable no-simple-event-func-name + event Transfer( + address indexed from, + address indexed to, + uint256 value + ); + + event Approval( + address indexed owner, + address indexed spender, + uint256 value + ); + + /// @dev send `value` token to `to` from `msg.sender` + /// @param to The address of the recipient + /// @param value The amount of token to be transferred + /// @return True if transfer was successful + function transfer(address to, uint256 value) + external + returns (bool); + + /// @dev send `value` token to `to` from `from` on the condition it is approved by `from` + /// @param from The address of the sender + /// @param to The address of the recipient + /// @param value The amount of token to be transferred + /// @return True if transfer was successful + function transferFrom( + address from, + address to, + uint256 value + ) + external + returns (bool); + + /// @dev `msg.sender` approves `spender` to spend `value` tokens + /// @param spender The address of the account able to transfer the tokens + /// @param value The amount of wei to be approved for transfer + /// @return Always true if the call has enough gas to complete execution + function approve(address spender, uint256 value) + external + returns (bool); + + /// @dev Query total supply of token + /// @return Total supply of token + function totalSupply() + external + view + returns (uint256); + + /// @dev Get the balance of `owner`. + /// @param owner The address from which the balance will be retrieved + /// @return Balance of owner + function balanceOf(address owner) + external + view + returns (uint256); + + /// @dev Get the allowance for `spender` to spend from `owner`. + /// @param owner The address of the account owning tokens + /// @param spender The address of the account able to transfer the tokens + /// @return Amount of remaining tokens allowed to spent + function allowance(address owner, address spender) + external + view + returns (uint256); + + /// @dev Get the number of decimals this token has. + function decimals() + external + view + returns (uint8); +} diff --git a/contracts/erc20/contracts/src/v06/IEtherTokenV06.sol b/contracts/erc20/contracts/src/v06/IEtherTokenV06.sol new file mode 100644 index 0000000000..94168e994f --- /dev/null +++ b/contracts/erc20/contracts/src/v06/IEtherTokenV06.sol @@ -0,0 +1,32 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; + +import "./IERC20TokenV06.sol"; + + +interface IEtherTokenV06 is + IERC20TokenV06 +{ + /// @dev Wrap ether. + function deposit() external payable; + + /// @dev Unwrap ether. + function withdraw(uint256 amount) external; +} diff --git a/contracts/erc20/contracts/src/v06/LibERC20TokenV06.sol b/contracts/erc20/contracts/src/v06/LibERC20TokenV06.sol new file mode 100644 index 0000000000..60cee9f6f5 --- /dev/null +++ b/contracts/erc20/contracts/src/v06/LibERC20TokenV06.sol @@ -0,0 +1,212 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; + +import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; +import "@0x/contracts-utils/contracts/src/v06/LibBytesV06.sol"; +import "./IERC20TokenV06.sol"; + + +library LibERC20TokenV06 { + bytes constant private DECIMALS_CALL_DATA = hex"313ce567"; + + /// @dev Calls `IERC20TokenV06(token).approve()`. + /// Reverts if `false` is returned or if the return + /// data length is nonzero and not 32 bytes. + /// @param token The address of the token contract. + /// @param spender The address that receives an allowance. + /// @param allowance The allowance to set. + function compatApprove( + IERC20TokenV06 token, + address spender, + uint256 allowance + ) + internal + { + bytes memory callData = abi.encodeWithSelector( + token.approve.selector, + spender, + allowance + ); + _callWithOptionalBooleanResult(address(token), callData); + } + + /// @dev Calls `IERC20TokenV06(token).approve()` and sets the allowance to the + /// maximum if the current approval is not already >= an amount. + /// Reverts if `false` is returned or if the return + /// data length is nonzero and not 32 bytes. + /// @param token The address of the token contract. + /// @param spender The address that receives an allowance. + /// @param amount The minimum allowance needed. + function approveIfBelow( + IERC20TokenV06 token, + address spender, + uint256 amount + ) + internal + { + if (token.allowance(address(this), spender) < amount) { + compatApprove(token, spender, uint256(-1)); + } + } + + /// @dev Calls `IERC20TokenV06(token).transfer()`. + /// Reverts if `false` is returned or if the return + /// data length is nonzero and not 32 bytes. + /// @param token The address of the token contract. + /// @param to The address that receives the tokens + /// @param amount Number of tokens to transfer. + function compatTransfer( + IERC20TokenV06 token, + address to, + uint256 amount + ) + internal + { + bytes memory callData = abi.encodeWithSelector( + token.transfer.selector, + to, + amount + ); + _callWithOptionalBooleanResult(address(token), callData); + } + + /// @dev Calls `IERC20TokenV06(token).transferFrom()`. + /// Reverts if `false` is returned or if the return + /// data length is nonzero and not 32 bytes. + /// @param token The address of the token contract. + /// @param from The owner of the tokens. + /// @param to The address that receives the tokens + /// @param amount Number of tokens to transfer. + function compatTransferFrom( + IERC20TokenV06 token, + address from, + address to, + uint256 amount + ) + internal + { + bytes memory callData = abi.encodeWithSelector( + token.transferFrom.selector, + from, + to, + amount + ); + _callWithOptionalBooleanResult(address(token), callData); + } + + /// @dev Retrieves the number of decimals for a token. + /// Returns `18` if the call reverts. + /// @param token The address of the token contract. + /// @return tokenDecimals The number of decimals places for the token. + function compatDecimals(IERC20TokenV06 token) + internal + view + returns (uint8 tokenDecimals) + { + tokenDecimals = 18; + (bool didSucceed, bytes memory resultData) = address(token).staticcall(DECIMALS_CALL_DATA); + if (didSucceed && resultData.length == 32) { + tokenDecimals = uint8(LibBytesV06.readUint256(resultData, 0)); + } + } + + /// @dev Retrieves the allowance for a token, owner, and spender. + /// Returns `0` if the call reverts. + /// @param token The address of the token contract. + /// @param owner The owner of the tokens. + /// @param spender The address the spender. + /// @return allowance_ The allowance for a token, owner, and spender. + function compatAllowance(IERC20TokenV06 token, address owner, address spender) + internal + view + returns (uint256 allowance_) + { + (bool didSucceed, bytes memory resultData) = address(token).staticcall( + abi.encodeWithSelector( + token.allowance.selector, + owner, + spender + ) + ); + if (didSucceed && resultData.length == 32) { + allowance_ = LibBytesV06.readUint256(resultData, 0); + } + } + + /// @dev Retrieves the balance for a token owner. + /// Returns `0` if the call reverts. + /// @param token The address of the token contract. + /// @param owner The owner of the tokens. + /// @return balance The token balance of an owner. + function compatBalanceOf(IERC20TokenV06 token, address owner) + internal + view + returns (uint256 balance) + { + (bool didSucceed, bytes memory resultData) = address(token).staticcall( + abi.encodeWithSelector( + token.balanceOf.selector, + owner + ) + ); + if (didSucceed && resultData.length == 32) { + balance = LibBytesV06.readUint256(resultData, 0); + } + } + + /// @dev Check if the data returned by a non-static call to an ERC20 token + /// is a successful result. Supported functions are `transfer()`, + /// `transferFrom()`, and `approve()`. + /// @param resultData The raw data returned by a non-static call to the ERC20 token. + /// @return isSuccessful Whether the result data indicates success. + function isSuccessfulResult(bytes memory resultData) + internal + pure + returns (bool isSuccessful) + { + if (resultData.length == 0) { + return true; + } + if (resultData.length == 32) { + uint256 result = LibBytesV06.readUint256(resultData, 0); + if (result == 1) { + return true; + } + } + } + + /// @dev Executes a call on address `target` with calldata `callData` + /// and asserts that either nothing was returned or a single boolean + /// was returned equal to `true`. + /// @param target The call target. + /// @param callData The abi-encoded call data. + function _callWithOptionalBooleanResult( + address target, + bytes memory callData + ) + private + { + (bool didSucceed, bytes memory resultData) = target.call(callData); + if (didSucceed && isSuccessfulResult(resultData)) { + return; + } + LibRichErrorsV06.rrevert(resultData); + } +} diff --git a/contracts/erc20/package.json b/contracts/erc20/package.json index 65abde27a7..01bb004e36 100644 --- a/contracts/erc20/package.json +++ b/contracts/erc20/package.json @@ -38,7 +38,7 @@ }, "config": { "publicInterfaceContracts": "DummyERC20Token,ERC20Token,WETH9,ZRXToken,DummyNoReturnERC20Token,DummyMultipleReturnERC20Token", - "abis": "./test/generated-artifacts/@(DummyERC20Token|DummyMultipleReturnERC20Token|DummyNoReturnERC20Token|ERC20Token|IERC20Token|IEtherToken|LibERC20Token|MintableERC20Token|TestLibERC20Token|TestLibERC20TokenTarget|UnlimitedAllowanceERC20Token|UntransferrableDummyERC20Token|WETH9|ZRXToken).json", + "abis": "./test/generated-artifacts/@(DummyERC20Token|DummyMultipleReturnERC20Token|DummyNoReturnERC20Token|ERC20Token|IERC20Token|IERC20TokenV06|IEtherToken|IEtherTokenV06|LibERC20Token|LibERC20TokenV06|MintableERC20Token|TestLibERC20Token|TestLibERC20TokenTarget|UnlimitedAllowanceERC20Token|UntransferrableDummyERC20Token|WETH9|ZRXToken).json", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." }, "repository": { diff --git a/contracts/erc20/test/artifacts.ts b/contracts/erc20/test/artifacts.ts index 78062e41f1..a7249ae38a 100644 --- a/contracts/erc20/test/artifacts.ts +++ b/contracts/erc20/test/artifacts.ts @@ -10,8 +10,11 @@ import * as DummyMultipleReturnERC20Token from '../test/generated-artifacts/Dumm import * as DummyNoReturnERC20Token from '../test/generated-artifacts/DummyNoReturnERC20Token.json'; import * as ERC20Token from '../test/generated-artifacts/ERC20Token.json'; import * as IERC20Token from '../test/generated-artifacts/IERC20Token.json'; +import * as IERC20TokenV06 from '../test/generated-artifacts/IERC20TokenV06.json'; import * as IEtherToken from '../test/generated-artifacts/IEtherToken.json'; +import * as IEtherTokenV06 from '../test/generated-artifacts/IEtherTokenV06.json'; import * as LibERC20Token from '../test/generated-artifacts/LibERC20Token.json'; +import * as LibERC20TokenV06 from '../test/generated-artifacts/LibERC20TokenV06.json'; import * as MintableERC20Token from '../test/generated-artifacts/MintableERC20Token.json'; import * as TestLibERC20Token from '../test/generated-artifacts/TestLibERC20Token.json'; import * as TestLibERC20TokenTarget from '../test/generated-artifacts/TestLibERC20TokenTarget.json'; @@ -28,6 +31,9 @@ export const artifacts = { ZRXToken: (ZRXToken as any) as ContractArtifact, IERC20Token: IERC20Token as ContractArtifact, IEtherToken: IEtherToken as ContractArtifact, + IERC20TokenV06: IERC20TokenV06 as ContractArtifact, + IEtherTokenV06: IEtherTokenV06 as ContractArtifact, + LibERC20TokenV06: LibERC20TokenV06 as ContractArtifact, DummyERC20Token: DummyERC20Token as ContractArtifact, DummyMultipleReturnERC20Token: DummyMultipleReturnERC20Token as ContractArtifact, DummyNoReturnERC20Token: DummyNoReturnERC20Token as ContractArtifact, diff --git a/contracts/erc20/test/wrappers.ts b/contracts/erc20/test/wrappers.ts index f7ebf18b6a..ec3d330a31 100644 --- a/contracts/erc20/test/wrappers.ts +++ b/contracts/erc20/test/wrappers.ts @@ -8,8 +8,11 @@ export * from '../test/generated-wrappers/dummy_multiple_return_erc20_token'; export * from '../test/generated-wrappers/dummy_no_return_erc20_token'; export * from '../test/generated-wrappers/erc20_token'; export * from '../test/generated-wrappers/i_erc20_token'; +export * from '../test/generated-wrappers/i_erc20_token_v06'; export * from '../test/generated-wrappers/i_ether_token'; +export * from '../test/generated-wrappers/i_ether_token_v06'; export * from '../test/generated-wrappers/lib_erc20_token'; +export * from '../test/generated-wrappers/lib_erc20_token_v06'; export * from '../test/generated-wrappers/mintable_erc20_token'; export * from '../test/generated-wrappers/test_lib_erc20_token'; export * from '../test/generated-wrappers/test_lib_erc20_token_target'; diff --git a/contracts/erc20/tsconfig.json b/contracts/erc20/tsconfig.json index f7dc0989b3..16af35ff1c 100644 --- a/contracts/erc20/tsconfig.json +++ b/contracts/erc20/tsconfig.json @@ -14,8 +14,11 @@ "test/generated-artifacts/DummyNoReturnERC20Token.json", "test/generated-artifacts/ERC20Token.json", "test/generated-artifacts/IERC20Token.json", + "test/generated-artifacts/IERC20TokenV06.json", "test/generated-artifacts/IEtherToken.json", + "test/generated-artifacts/IEtherTokenV06.json", "test/generated-artifacts/LibERC20Token.json", + "test/generated-artifacts/LibERC20TokenV06.json", "test/generated-artifacts/MintableERC20Token.json", "test/generated-artifacts/TestLibERC20Token.json", "test/generated-artifacts/TestLibERC20TokenTarget.json", diff --git a/contracts/utils/CHANGELOG.json b/contracts/utils/CHANGELOG.json index 261da9242a..f85e798567 100644 --- a/contracts/utils/CHANGELOG.json +++ b/contracts/utils/CHANGELOG.json @@ -13,6 +13,10 @@ { "note": "Add solidity 0.6 contracts", "pr": 2540 + }, + { + "note": "Add more solidity 0.6 contracts", + "pr": 2545 } ] }, diff --git a/contracts/utils/contracts/src/v06/AuthorizableV06.sol b/contracts/utils/contracts/src/v06/AuthorizableV06.sol new file mode 100644 index 0000000000..7626de8070 --- /dev/null +++ b/contracts/utils/contracts/src/v06/AuthorizableV06.sol @@ -0,0 +1,166 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; + +import "./interfaces/IAuthorizableV06.sol"; +import "./errors/LibRichErrorsV06.sol"; +import "./errors/LibAuthorizableRichErrorsV06.sol"; +import "./OwnableV06.sol"; + + +// solhint-disable no-empty-blocks +contract AuthorizableV06 is + OwnableV06, + IAuthorizableV06 +{ + /// @dev Only authorized addresses can invoke functions with this modifier. + modifier onlyAuthorized { + _assertSenderIsAuthorized(); + _; + } + + /// @dev Whether an address is authorized to call privileged functions. + /// @param 0 Address to query. + /// @return 0 Whether the address is authorized. + mapping (address => bool) public override authorized; + /// @dev Whether an address is authorized to call privileged functions. + /// @param 0 Index of authorized address. + /// @return 0 Authorized address. + address[] public override authorities; + + /// @dev Initializes the `owner` address. + constructor() + public + OwnableV06() + {} + + /// @dev Authorizes an address. + /// @param target Address to authorize. + function addAuthorizedAddress(address target) + external + override + onlyOwner + { + _addAuthorizedAddress(target); + } + + /// @dev Removes authorizion of an address. + /// @param target Address to remove authorization from. + function removeAuthorizedAddress(address target) + external + override + onlyOwner + { + if (!authorized[target]) { + LibRichErrorsV06.rrevert(LibAuthorizableRichErrorsV06.TargetNotAuthorizedError(target)); + } + for (uint256 i = 0; i < authorities.length; i++) { + if (authorities[i] == target) { + _removeAuthorizedAddressAtIndex(target, i); + break; + } + } + } + + /// @dev Removes authorizion of an address. + /// @param target Address to remove authorization from. + /// @param index Index of target in authorities array. + function removeAuthorizedAddressAtIndex( + address target, + uint256 index + ) + external + override + onlyOwner + { + _removeAuthorizedAddressAtIndex(target, index); + } + + /// @dev Gets all authorized addresses. + /// @return Array of authorized addresses. + function getAuthorizedAddresses() + external + override + view + returns (address[] memory) + { + return authorities; + } + + /// @dev Reverts if msg.sender is not authorized. + function _assertSenderIsAuthorized() + internal + view + { + if (!authorized[msg.sender]) { + LibRichErrorsV06.rrevert(LibAuthorizableRichErrorsV06.SenderNotAuthorizedError(msg.sender)); + } + } + + /// @dev Authorizes an address. + /// @param target Address to authorize. + function _addAuthorizedAddress(address target) + internal + { + // Ensure that the target is not the zero address. + if (target == address(0)) { + LibRichErrorsV06.rrevert(LibAuthorizableRichErrorsV06.ZeroCantBeAuthorizedError()); + } + + // Ensure that the target is not already authorized. + if (authorized[target]) { + LibRichErrorsV06.rrevert(LibAuthorizableRichErrorsV06.TargetAlreadyAuthorizedError(target)); + } + + authorized[target] = true; + authorities.push(target); + emit AuthorizedAddressAdded(target, msg.sender); + } + + /// @dev Removes authorizion of an address. + /// @param target Address to remove authorization from. + /// @param index Index of target in authorities array. + function _removeAuthorizedAddressAtIndex( + address target, + uint256 index + ) + internal + { + if (!authorized[target]) { + LibRichErrorsV06.rrevert(LibAuthorizableRichErrorsV06.TargetNotAuthorizedError(target)); + } + if (index >= authorities.length) { + LibRichErrorsV06.rrevert(LibAuthorizableRichErrorsV06.IndexOutOfBoundsError( + index, + authorities.length + )); + } + if (authorities[index] != target) { + LibRichErrorsV06.rrevert(LibAuthorizableRichErrorsV06.AuthorizedAddressMismatchError( + authorities[index], + target + )); + } + + delete authorized[target]; + authorities[index] = authorities[authorities.length - 1]; + authorities.pop(); + emit AuthorizedAddressRemoved(target, msg.sender); + } +} diff --git a/contracts/utils/contracts/src/v06/LibMathV06.sol b/contracts/utils/contracts/src/v06/LibMathV06.sol new file mode 100644 index 0000000000..c4393681fc --- /dev/null +++ b/contracts/utils/contracts/src/v06/LibMathV06.sol @@ -0,0 +1,228 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; + +import "./LibSafeMathV06.sol"; +import "./errors/LibRichErrorsV06.sol"; +import "./errors/LibMathRichErrorsV06.sol"; + + +library LibMathV06 { + + using LibSafeMathV06 for uint256; + + /// @dev Calculates partial value given a numerator and denominator rounded down. + /// Reverts if rounding error is >= 0.1% + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to calculate partial of. + /// @return partialAmount Partial value of target rounded down. + function safeGetPartialAmountFloor( + uint256 numerator, + uint256 denominator, + uint256 target + ) + internal + pure + returns (uint256 partialAmount) + { + if (isRoundingErrorFloor( + numerator, + denominator, + target + )) { + LibRichErrorsV06.rrevert(LibMathRichErrorsV06.RoundingError( + numerator, + denominator, + target + )); + } + + partialAmount = numerator.safeMul(target).safeDiv(denominator); + return partialAmount; + } + + /// @dev Calculates partial value given a numerator and denominator rounded down. + /// Reverts if rounding error is >= 0.1% + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to calculate partial of. + /// @return partialAmount Partial value of target rounded up. + function safeGetPartialAmountCeil( + uint256 numerator, + uint256 denominator, + uint256 target + ) + internal + pure + returns (uint256 partialAmount) + { + if (isRoundingErrorCeil( + numerator, + denominator, + target + )) { + LibRichErrorsV06.rrevert(LibMathRichErrorsV06.RoundingError( + numerator, + denominator, + target + )); + } + + // safeDiv computes `floor(a / b)`. We use the identity (a, b integer): + // ceil(a / b) = floor((a + b - 1) / b) + // To implement `ceil(a / b)` using safeDiv. + partialAmount = numerator.safeMul(target) + .safeAdd(denominator.safeSub(1)) + .safeDiv(denominator); + + return partialAmount; + } + + /// @dev Calculates partial value given a numerator and denominator rounded down. + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to calculate partial of. + /// @return partialAmount Partial value of target rounded down. + function getPartialAmountFloor( + uint256 numerator, + uint256 denominator, + uint256 target + ) + internal + pure + returns (uint256 partialAmount) + { + partialAmount = numerator.safeMul(target).safeDiv(denominator); + return partialAmount; + } + + /// @dev Calculates partial value given a numerator and denominator rounded down. + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to calculate partial of. + /// @return partialAmount Partial value of target rounded up. + function getPartialAmountCeil( + uint256 numerator, + uint256 denominator, + uint256 target + ) + internal + pure + returns (uint256 partialAmount) + { + // safeDiv computes `floor(a / b)`. We use the identity (a, b integer): + // ceil(a / b) = floor((a + b - 1) / b) + // To implement `ceil(a / b)` using safeDiv. + partialAmount = numerator.safeMul(target) + .safeAdd(denominator.safeSub(1)) + .safeDiv(denominator); + + return partialAmount; + } + + /// @dev Checks if rounding error >= 0.1% when rounding down. + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to multiply with numerator/denominator. + /// @return isError Rounding error is present. + function isRoundingErrorFloor( + uint256 numerator, + uint256 denominator, + uint256 target + ) + internal + pure + returns (bool isError) + { + if (denominator == 0) { + LibRichErrorsV06.rrevert(LibMathRichErrorsV06.DivisionByZeroError()); + } + + // The absolute rounding error is the difference between the rounded + // value and the ideal value. The relative rounding error is the + // absolute rounding error divided by the absolute value of the + // ideal value. This is undefined when the ideal value is zero. + // + // The ideal value is `numerator * target / denominator`. + // Let's call `numerator * target % denominator` the remainder. + // The absolute error is `remainder / denominator`. + // + // When the ideal value is zero, we require the absolute error to + // be zero. Fortunately, this is always the case. The ideal value is + // zero iff `numerator == 0` and/or `target == 0`. In this case the + // remainder and absolute error are also zero. + if (target == 0 || numerator == 0) { + return false; + } + + // Otherwise, we want the relative rounding error to be strictly + // less than 0.1%. + // The relative error is `remainder / (numerator * target)`. + // We want the relative error less than 1 / 1000: + // remainder / (numerator * denominator) < 1 / 1000 + // or equivalently: + // 1000 * remainder < numerator * target + // so we have a rounding error iff: + // 1000 * remainder >= numerator * target + uint256 remainder = mulmod( + target, + numerator, + denominator + ); + isError = remainder.safeMul(1000) >= numerator.safeMul(target); + return isError; + } + + /// @dev Checks if rounding error >= 0.1% when rounding up. + /// @param numerator Numerator. + /// @param denominator Denominator. + /// @param target Value to multiply with numerator/denominator. + /// @return isError Rounding error is present. + function isRoundingErrorCeil( + uint256 numerator, + uint256 denominator, + uint256 target + ) + internal + pure + returns (bool isError) + { + if (denominator == 0) { + LibRichErrorsV06.rrevert(LibMathRichErrorsV06.DivisionByZeroError()); + } + + // See the comments in `isRoundingError`. + if (target == 0 || numerator == 0) { + // When either is zero, the ideal value and rounded value are zero + // and there is no rounding error. (Although the relative error + // is undefined.) + return false; + } + // Compute remainder as before + uint256 remainder = mulmod( + target, + numerator, + denominator + ); + remainder = denominator.safeSub(remainder) % denominator; + isError = remainder.safeMul(1000) >= numerator.safeMul(target); + return isError; + } +} diff --git a/contracts/utils/contracts/src/v06/LibSafeMathV06.sol b/contracts/utils/contracts/src/v06/LibSafeMathV06.sol new file mode 100644 index 0000000000..f494e5a2cc --- /dev/null +++ b/contracts/utils/contracts/src/v06/LibSafeMathV06.sol @@ -0,0 +1,108 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; + +import "./errors/LibRichErrorsV06.sol"; +import "./errors/LibSafeMathRichErrorsV06.sol"; + + +library LibSafeMathV06 { + + function safeMul(uint256 a, uint256 b) + internal + pure + returns (uint256) + { + if (a == 0) { + return 0; + } + uint256 c = a * b; + if (c / a != b) { + LibRichErrorsV06.rrevert(LibSafeMathRichErrorsV06.Uint256BinOpError( + LibSafeMathRichErrorsV06.BinOpErrorCodes.MULTIPLICATION_OVERFLOW, + a, + b + )); + } + return c; + } + + function safeDiv(uint256 a, uint256 b) + internal + pure + returns (uint256) + { + if (b == 0) { + LibRichErrorsV06.rrevert(LibSafeMathRichErrorsV06.Uint256BinOpError( + LibSafeMathRichErrorsV06.BinOpErrorCodes.DIVISION_BY_ZERO, + a, + b + )); + } + uint256 c = a / b; + return c; + } + + function safeSub(uint256 a, uint256 b) + internal + pure + returns (uint256) + { + if (b > a) { + LibRichErrorsV06.rrevert(LibSafeMathRichErrorsV06.Uint256BinOpError( + LibSafeMathRichErrorsV06.BinOpErrorCodes.SUBTRACTION_UNDERFLOW, + a, + b + )); + } + return a - b; + } + + function safeAdd(uint256 a, uint256 b) + internal + pure + returns (uint256) + { + uint256 c = a + b; + if (c < a) { + LibRichErrorsV06.rrevert(LibSafeMathRichErrorsV06.Uint256BinOpError( + LibSafeMathRichErrorsV06.BinOpErrorCodes.ADDITION_OVERFLOW, + a, + b + )); + } + return c; + } + + function max256(uint256 a, uint256 b) + internal + pure + returns (uint256) + { + return a >= b ? a : b; + } + + function min256(uint256 a, uint256 b) + internal + pure + returns (uint256) + { + return a < b ? a : b; + } +} diff --git a/contracts/utils/contracts/src/v06/OwnableV06.sol b/contracts/utils/contracts/src/v06/OwnableV06.sol new file mode 100644 index 0000000000..0dda2cbde4 --- /dev/null +++ b/contracts/utils/contracts/src/v06/OwnableV06.sol @@ -0,0 +1,68 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; + +import "./interfaces/IOwnableV06.sol"; +import "./errors/LibRichErrorsV06.sol"; +import "./errors/LibOwnableRichErrorsV06.sol"; + + +contract OwnableV06 is + IOwnableV06 +{ + /// @dev The owner of this contract. + /// @return 0 The owner address. + address public override owner; + + constructor() public { + owner = msg.sender; + } + + modifier onlyOwner() { + _assertSenderIsOwner(); + _; + } + + /// @dev Change the owner of this contract. + /// @param newOwner New owner address. + function transferOwnership(address newOwner) + public + override + onlyOwner + { + if (newOwner == address(0)) { + LibRichErrorsV06.rrevert(LibOwnableRichErrorsV06.TransferOwnerToZeroError()); + } else { + owner = newOwner; + emit OwnershipTransferred(msg.sender, newOwner); + } + } + + function _assertSenderIsOwner() + internal + view + { + if (msg.sender != owner) { + LibRichErrorsV06.rrevert(LibOwnableRichErrorsV06.OnlyOwnerError( + msg.sender, + owner + )); + } + } +} diff --git a/contracts/utils/contracts/src/v06/ReentrancyGuardV06.sol b/contracts/utils/contracts/src/v06/ReentrancyGuardV06.sol new file mode 100644 index 0000000000..ed17629622 --- /dev/null +++ b/contracts/utils/contracts/src/v06/ReentrancyGuardV06.sol @@ -0,0 +1,57 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; + +import "./errors/LibReentrancyGuardRichErrorsV06.sol"; +import "./errors/LibRichErrorsV06.sol"; + + +contract ReentrancyGuardV06 { + + // Locked state of mutex. + bool private _locked = false; + + /// @dev Functions with this modifer cannot be reentered. The mutex will be locked + /// before function execution and unlocked after. + modifier nonReentrant() { + _lockMutexOrThrowIfAlreadyLocked(); + _; + _unlockMutex(); + } + + function _lockMutexOrThrowIfAlreadyLocked() + internal + { + // Ensure mutex is unlocked. + if (_locked) { + LibRichErrorsV06.rrevert( + LibReentrancyGuardRichErrorsV06.IllegalReentrancyError() + ); + } + // Lock mutex. + _locked = true; + } + + function _unlockMutex() + internal + { + // Unlock mutex. + _locked = false; + } +} diff --git a/contracts/utils/contracts/src/v06/errors/LibAuthorizableRichErrorsV06.sol b/contracts/utils/contracts/src/v06/errors/LibAuthorizableRichErrorsV06.sol new file mode 100644 index 0000000000..465108301f --- /dev/null +++ b/contracts/utils/contracts/src/v06/errors/LibAuthorizableRichErrorsV06.sol @@ -0,0 +1,119 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; + + +library LibAuthorizableRichErrorsV06 { + + // bytes4(keccak256("AuthorizedAddressMismatchError(address,address)")) + bytes4 internal constant AUTHORIZED_ADDRESS_MISMATCH_ERROR_SELECTOR = + 0x140a84db; + + // bytes4(keccak256("IndexOutOfBoundsError(uint256,uint256)")) + bytes4 internal constant INDEX_OUT_OF_BOUNDS_ERROR_SELECTOR = + 0xe9f83771; + + // bytes4(keccak256("SenderNotAuthorizedError(address)")) + bytes4 internal constant SENDER_NOT_AUTHORIZED_ERROR_SELECTOR = + 0xb65a25b9; + + // bytes4(keccak256("TargetAlreadyAuthorizedError(address)")) + bytes4 internal constant TARGET_ALREADY_AUTHORIZED_ERROR_SELECTOR = + 0xde16f1a0; + + // bytes4(keccak256("TargetNotAuthorizedError(address)")) + bytes4 internal constant TARGET_NOT_AUTHORIZED_ERROR_SELECTOR = + 0xeb5108a2; + + // bytes4(keccak256("ZeroCantBeAuthorizedError()")) + bytes internal constant ZERO_CANT_BE_AUTHORIZED_ERROR_BYTES = + hex"57654fe4"; + + // solhint-disable func-name-mixedcase + function AuthorizedAddressMismatchError( + address authorized, + address target + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + AUTHORIZED_ADDRESS_MISMATCH_ERROR_SELECTOR, + authorized, + target + ); + } + + function IndexOutOfBoundsError( + uint256 index, + uint256 length + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + INDEX_OUT_OF_BOUNDS_ERROR_SELECTOR, + index, + length + ); + } + + function SenderNotAuthorizedError(address sender) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + SENDER_NOT_AUTHORIZED_ERROR_SELECTOR, + sender + ); + } + + function TargetAlreadyAuthorizedError(address target) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + TARGET_ALREADY_AUTHORIZED_ERROR_SELECTOR, + target + ); + } + + function TargetNotAuthorizedError(address target) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + TARGET_NOT_AUTHORIZED_ERROR_SELECTOR, + target + ); + } + + function ZeroCantBeAuthorizedError() + internal + pure + returns (bytes memory) + { + return ZERO_CANT_BE_AUTHORIZED_ERROR_BYTES; + } +} diff --git a/contracts/utils/contracts/src/v06/errors/LibMathRichErrorsV06.sol b/contracts/utils/contracts/src/v06/errors/LibMathRichErrorsV06.sol new file mode 100644 index 0000000000..53c4e47ceb --- /dev/null +++ b/contracts/utils/contracts/src/v06/errors/LibMathRichErrorsV06.sol @@ -0,0 +1,57 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; + + +library LibMathRichErrorsV06 { + + // bytes4(keccak256("DivisionByZeroError()")) + bytes internal constant DIVISION_BY_ZERO_ERROR = + hex"a791837c"; + + // bytes4(keccak256("RoundingError(uint256,uint256,uint256)")) + bytes4 internal constant ROUNDING_ERROR_SELECTOR = + 0x339f3de2; + + // solhint-disable func-name-mixedcase + function DivisionByZeroError() + internal + pure + returns (bytes memory) + { + return DIVISION_BY_ZERO_ERROR; + } + + function RoundingError( + uint256 numerator, + uint256 denominator, + uint256 target + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + ROUNDING_ERROR_SELECTOR, + numerator, + denominator, + target + ); + } +} diff --git a/contracts/utils/contracts/src/v06/errors/LibOwnableRichErrorsV06.sol b/contracts/utils/contracts/src/v06/errors/LibOwnableRichErrorsV06.sol new file mode 100644 index 0000000000..de495ab2bc --- /dev/null +++ b/contracts/utils/contracts/src/v06/errors/LibOwnableRichErrorsV06.sol @@ -0,0 +1,54 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +pragma solidity ^0.6.5; + + +library LibOwnableRichErrorsV06 { + + // bytes4(keccak256("OnlyOwnerError(address,address)")) + bytes4 internal constant ONLY_OWNER_ERROR_SELECTOR = + 0x1de45ad1; + + // bytes4(keccak256("TransferOwnerToZeroError()")) + bytes internal constant TRANSFER_OWNER_TO_ZERO_ERROR_BYTES = + hex"e69edc3e"; + + // solhint-disable func-name-mixedcase + function OnlyOwnerError( + address sender, + address owner + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + ONLY_OWNER_ERROR_SELECTOR, + sender, + owner + ); + } + + function TransferOwnerToZeroError() + internal + pure + returns (bytes memory) + { + return TRANSFER_OWNER_TO_ZERO_ERROR_BYTES; + } +} diff --git a/contracts/utils/contracts/src/v06/errors/LibReentrancyGuardRichErrorsV06.sol b/contracts/utils/contracts/src/v06/errors/LibReentrancyGuardRichErrorsV06.sol new file mode 100644 index 0000000000..aeb08087a1 --- /dev/null +++ b/contracts/utils/contracts/src/v06/errors/LibReentrancyGuardRichErrorsV06.sol @@ -0,0 +1,36 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; + + +library LibReentrancyGuardRichErrorsV06 { + + // bytes4(keccak256("IllegalReentrancyError()")) + bytes internal constant ILLEGAL_REENTRANCY_ERROR_SELECTOR_BYTES = + hex"0c3b823f"; + + // solhint-disable func-name-mixedcase + function IllegalReentrancyError() + internal + pure + returns (bytes memory) + { + return ILLEGAL_REENTRANCY_ERROR_SELECTOR_BYTES; + } +} diff --git a/contracts/utils/contracts/src/v06/errors/LibSafeMathRichErrorsV06.sol b/contracts/utils/contracts/src/v06/errors/LibSafeMathRichErrorsV06.sol new file mode 100644 index 0000000000..ef6beac09a --- /dev/null +++ b/contracts/utils/contracts/src/v06/errors/LibSafeMathRichErrorsV06.sol @@ -0,0 +1,77 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; + + +library LibSafeMathRichErrorsV06 { + + // bytes4(keccak256("Uint256BinOpError(uint8,uint256,uint256)")) + bytes4 internal constant UINT256_BINOP_ERROR_SELECTOR = + 0xe946c1bb; + + // bytes4(keccak256("Uint256DowncastError(uint8,uint256)")) + bytes4 internal constant UINT256_DOWNCAST_ERROR_SELECTOR = + 0xc996af7b; + + enum BinOpErrorCodes { + ADDITION_OVERFLOW, + MULTIPLICATION_OVERFLOW, + SUBTRACTION_UNDERFLOW, + DIVISION_BY_ZERO + } + + enum DowncastErrorCodes { + VALUE_TOO_LARGE_TO_DOWNCAST_TO_UINT32, + VALUE_TOO_LARGE_TO_DOWNCAST_TO_UINT64, + VALUE_TOO_LARGE_TO_DOWNCAST_TO_UINT96 + } + + // solhint-disable func-name-mixedcase + function Uint256BinOpError( + BinOpErrorCodes errorCode, + uint256 a, + uint256 b + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + UINT256_BINOP_ERROR_SELECTOR, + errorCode, + a, + b + ); + } + + function Uint256DowncastError( + DowncastErrorCodes errorCode, + uint256 a + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + UINT256_DOWNCAST_ERROR_SELECTOR, + errorCode, + a + ); + } +} diff --git a/contracts/utils/contracts/src/v06/interfaces/IAuthorizableV06.sol b/contracts/utils/contracts/src/v06/interfaces/IAuthorizableV06.sol new file mode 100644 index 0000000000..8834bb974b --- /dev/null +++ b/contracts/utils/contracts/src/v06/interfaces/IAuthorizableV06.sol @@ -0,0 +1,75 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; + +import "./IOwnableV06.sol"; + + +interface IAuthorizableV06 is + IOwnableV06 +{ + // Event logged when a new address is authorized. + event AuthorizedAddressAdded( + address indexed target, + address indexed caller + ); + + // Event logged when a currently authorized address is unauthorized. + event AuthorizedAddressRemoved( + address indexed target, + address indexed caller + ); + + /// @dev Authorizes an address. + /// @param target Address to authorize. + function addAuthorizedAddress(address target) + external; + + /// @dev Removes authorizion of an address. + /// @param target Address to remove authorization from. + function removeAuthorizedAddress(address target) + external; + + /// @dev Removes authorizion of an address. + /// @param target Address to remove authorization from. + /// @param index Index of target in authorities array. + function removeAuthorizedAddressAtIndex( + address target, + uint256 index + ) + external; + + /// @dev Gets all authorized addresses. + /// @return authorizedAddresses Array of authorized addresses. + function getAuthorizedAddresses() + external + view + returns (address[] memory authorizedAddresses); + + /// @dev Whether an adderss is authorized to call privileged functions. + /// @param addr Address to query. + /// @return isAuthorized Whether the address is authorized. + function authorized(address addr) external view returns (bool isAuthorized); + + /// @dev All addresseses authorized to call privileged functions. + /// @param idx Index of authorized address. + /// @return addr Authorized address. + function authorities(uint256 idx) external view returns (address addr); + +} diff --git a/contracts/utils/package.json b/contracts/utils/package.json index 82b0598976..723d87430f 100644 --- a/contracts/utils/package.json +++ b/contracts/utils/package.json @@ -38,7 +38,7 @@ "config": { "publicInterfaceContracts": "Authorizable,IAuthorizable,IOwnable,LibAddress,LibAddressArray,LibAddressArrayRichErrors,LibAuthorizableRichErrors,LibBytes,LibBytesRichErrors,LibEIP1271,LibEIP712,LibFractions,LibOwnableRichErrors,LibReentrancyGuardRichErrors,LibRichErrors,LibSafeMath,LibSafeMathRichErrors,Ownable,ReentrancyGuard,Refundable", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(Authorizable|D18|DeploymentConstants|IAuthorizable|IOwnable|IOwnableV06|LibAddress|LibAddressArray|LibAddressArrayRichErrors|LibAuthorizableRichErrors|LibBytes|LibBytesRichErrors|LibBytesRichErrorsV06|LibBytesV06|LibEIP1271|LibEIP712|LibFractions|LibOwnableRichErrors|LibReentrancyGuardRichErrors|LibRichErrors|LibRichErrorsV06|LibSafeMath|LibSafeMathRichErrors|Ownable|ReentrancyGuard|Refundable|TestAuthorizable|TestLibAddress|TestLibAddressArray|TestLibBytes|TestLibEIP712|TestLibRichErrors|TestLibSafeMath|TestLogDecoding|TestLogDecodingDownstream|TestOwnable|TestReentrancyGuard|TestRefundable|TestRefundableReceiver).json" + "abis": "./test/generated-artifacts/@(Authorizable|AuthorizableV06|D18|DeploymentConstants|IAuthorizable|IAuthorizableV06|IOwnable|IOwnableV06|LibAddress|LibAddressArray|LibAddressArrayRichErrors|LibAuthorizableRichErrors|LibAuthorizableRichErrorsV06|LibBytes|LibBytesRichErrors|LibBytesRichErrorsV06|LibBytesV06|LibEIP1271|LibEIP712|LibFractions|LibMathRichErrorsV06|LibMathV06|LibOwnableRichErrors|LibOwnableRichErrorsV06|LibReentrancyGuardRichErrors|LibReentrancyGuardRichErrorsV06|LibRichErrors|LibRichErrorsV06|LibSafeMath|LibSafeMathRichErrors|LibSafeMathRichErrorsV06|LibSafeMathV06|Ownable|OwnableV06|ReentrancyGuard|ReentrancyGuardV06|Refundable|TestAuthorizable|TestLibAddress|TestLibAddressArray|TestLibBytes|TestLibEIP712|TestLibRichErrors|TestLibSafeMath|TestLogDecoding|TestLogDecodingDownstream|TestOwnable|TestReentrancyGuard|TestRefundable|TestRefundableReceiver).json" }, "repository": { "type": "git", diff --git a/contracts/utils/test/artifacts.ts b/contracts/utils/test/artifacts.ts index da80487346..8fca550294 100644 --- a/contracts/utils/test/artifacts.ts +++ b/contracts/utils/test/artifacts.ts @@ -6,15 +6,18 @@ import { ContractArtifact } from 'ethereum-types'; import * as Authorizable from '../test/generated-artifacts/Authorizable.json'; +import * as AuthorizableV06 from '../test/generated-artifacts/AuthorizableV06.json'; import * as D18 from '../test/generated-artifacts/D18.json'; import * as DeploymentConstants from '../test/generated-artifacts/DeploymentConstants.json'; import * as IAuthorizable from '../test/generated-artifacts/IAuthorizable.json'; +import * as IAuthorizableV06 from '../test/generated-artifacts/IAuthorizableV06.json'; import * as IOwnable from '../test/generated-artifacts/IOwnable.json'; import * as IOwnableV06 from '../test/generated-artifacts/IOwnableV06.json'; import * as LibAddress from '../test/generated-artifacts/LibAddress.json'; import * as LibAddressArray from '../test/generated-artifacts/LibAddressArray.json'; import * as LibAddressArrayRichErrors from '../test/generated-artifacts/LibAddressArrayRichErrors.json'; import * as LibAuthorizableRichErrors from '../test/generated-artifacts/LibAuthorizableRichErrors.json'; +import * as LibAuthorizableRichErrorsV06 from '../test/generated-artifacts/LibAuthorizableRichErrorsV06.json'; import * as LibBytes from '../test/generated-artifacts/LibBytes.json'; import * as LibBytesRichErrors from '../test/generated-artifacts/LibBytesRichErrors.json'; import * as LibBytesRichErrorsV06 from '../test/generated-artifacts/LibBytesRichErrorsV06.json'; @@ -22,14 +25,22 @@ import * as LibBytesV06 from '../test/generated-artifacts/LibBytesV06.json'; import * as LibEIP1271 from '../test/generated-artifacts/LibEIP1271.json'; import * as LibEIP712 from '../test/generated-artifacts/LibEIP712.json'; import * as LibFractions from '../test/generated-artifacts/LibFractions.json'; +import * as LibMathRichErrorsV06 from '../test/generated-artifacts/LibMathRichErrorsV06.json'; +import * as LibMathV06 from '../test/generated-artifacts/LibMathV06.json'; import * as LibOwnableRichErrors from '../test/generated-artifacts/LibOwnableRichErrors.json'; +import * as LibOwnableRichErrorsV06 from '../test/generated-artifacts/LibOwnableRichErrorsV06.json'; import * as LibReentrancyGuardRichErrors from '../test/generated-artifacts/LibReentrancyGuardRichErrors.json'; +import * as LibReentrancyGuardRichErrorsV06 from '../test/generated-artifacts/LibReentrancyGuardRichErrorsV06.json'; import * as LibRichErrors from '../test/generated-artifacts/LibRichErrors.json'; import * as LibRichErrorsV06 from '../test/generated-artifacts/LibRichErrorsV06.json'; import * as LibSafeMath from '../test/generated-artifacts/LibSafeMath.json'; import * as LibSafeMathRichErrors from '../test/generated-artifacts/LibSafeMathRichErrors.json'; +import * as LibSafeMathRichErrorsV06 from '../test/generated-artifacts/LibSafeMathRichErrorsV06.json'; +import * as LibSafeMathV06 from '../test/generated-artifacts/LibSafeMathV06.json'; import * as Ownable from '../test/generated-artifacts/Ownable.json'; +import * as OwnableV06 from '../test/generated-artifacts/OwnableV06.json'; import * as ReentrancyGuard from '../test/generated-artifacts/ReentrancyGuard.json'; +import * as ReentrancyGuardV06 from '../test/generated-artifacts/ReentrancyGuardV06.json'; import * as Refundable from '../test/generated-artifacts/Refundable.json'; import * as TestAuthorizable from '../test/generated-artifacts/TestAuthorizable.json'; import * as TestLibAddress from '../test/generated-artifacts/TestLibAddress.json'; @@ -67,9 +78,20 @@ export const artifacts = { Refundable: Refundable as ContractArtifact, IAuthorizable: IAuthorizable as ContractArtifact, IOwnable: IOwnable as ContractArtifact, + AuthorizableV06: AuthorizableV06 as ContractArtifact, LibBytesV06: LibBytesV06 as ContractArtifact, + LibMathV06: LibMathV06 as ContractArtifact, + LibSafeMathV06: LibSafeMathV06 as ContractArtifact, + OwnableV06: OwnableV06 as ContractArtifact, + ReentrancyGuardV06: ReentrancyGuardV06 as ContractArtifact, + LibAuthorizableRichErrorsV06: LibAuthorizableRichErrorsV06 as ContractArtifact, LibBytesRichErrorsV06: LibBytesRichErrorsV06 as ContractArtifact, + LibMathRichErrorsV06: LibMathRichErrorsV06 as ContractArtifact, + LibOwnableRichErrorsV06: LibOwnableRichErrorsV06 as ContractArtifact, + LibReentrancyGuardRichErrorsV06: LibReentrancyGuardRichErrorsV06 as ContractArtifact, LibRichErrorsV06: LibRichErrorsV06 as ContractArtifact, + LibSafeMathRichErrorsV06: LibSafeMathRichErrorsV06 as ContractArtifact, + IAuthorizableV06: IAuthorizableV06 as ContractArtifact, IOwnableV06: IOwnableV06 as ContractArtifact, TestAuthorizable: TestAuthorizable as ContractArtifact, TestLibAddress: TestLibAddress as ContractArtifact, diff --git a/contracts/utils/test/wrappers.ts b/contracts/utils/test/wrappers.ts index 560d1cb6ed..fe570e9d32 100644 --- a/contracts/utils/test/wrappers.ts +++ b/contracts/utils/test/wrappers.ts @@ -4,15 +4,18 @@ * ----------------------------------------------------------------------------- */ export * from '../test/generated-wrappers/authorizable'; +export * from '../test/generated-wrappers/authorizable_v06'; export * from '../test/generated-wrappers/d18'; export * from '../test/generated-wrappers/deployment_constants'; export * from '../test/generated-wrappers/i_authorizable'; +export * from '../test/generated-wrappers/i_authorizable_v06'; export * from '../test/generated-wrappers/i_ownable'; export * from '../test/generated-wrappers/i_ownable_v06'; export * from '../test/generated-wrappers/lib_address'; export * from '../test/generated-wrappers/lib_address_array'; export * from '../test/generated-wrappers/lib_address_array_rich_errors'; export * from '../test/generated-wrappers/lib_authorizable_rich_errors'; +export * from '../test/generated-wrappers/lib_authorizable_rich_errors_v06'; export * from '../test/generated-wrappers/lib_bytes'; export * from '../test/generated-wrappers/lib_bytes_rich_errors'; export * from '../test/generated-wrappers/lib_bytes_rich_errors_v06'; @@ -20,14 +23,22 @@ export * from '../test/generated-wrappers/lib_bytes_v06'; export * from '../test/generated-wrappers/lib_e_i_p1271'; export * from '../test/generated-wrappers/lib_e_i_p712'; export * from '../test/generated-wrappers/lib_fractions'; +export * from '../test/generated-wrappers/lib_math_rich_errors_v06'; +export * from '../test/generated-wrappers/lib_math_v06'; export * from '../test/generated-wrappers/lib_ownable_rich_errors'; +export * from '../test/generated-wrappers/lib_ownable_rich_errors_v06'; export * from '../test/generated-wrappers/lib_reentrancy_guard_rich_errors'; +export * from '../test/generated-wrappers/lib_reentrancy_guard_rich_errors_v06'; export * from '../test/generated-wrappers/lib_rich_errors'; export * from '../test/generated-wrappers/lib_rich_errors_v06'; export * from '../test/generated-wrappers/lib_safe_math'; export * from '../test/generated-wrappers/lib_safe_math_rich_errors'; +export * from '../test/generated-wrappers/lib_safe_math_rich_errors_v06'; +export * from '../test/generated-wrappers/lib_safe_math_v06'; export * from '../test/generated-wrappers/ownable'; +export * from '../test/generated-wrappers/ownable_v06'; export * from '../test/generated-wrappers/reentrancy_guard'; +export * from '../test/generated-wrappers/reentrancy_guard_v06'; export * from '../test/generated-wrappers/refundable'; export * from '../test/generated-wrappers/test_authorizable'; export * from '../test/generated-wrappers/test_lib_address'; diff --git a/contracts/utils/tsconfig.json b/contracts/utils/tsconfig.json index eb0de257b0..e68f43c2ea 100644 --- a/contracts/utils/tsconfig.json +++ b/contracts/utils/tsconfig.json @@ -24,15 +24,18 @@ "generated-artifacts/ReentrancyGuard.json", "generated-artifacts/Refundable.json", "test/generated-artifacts/Authorizable.json", + "test/generated-artifacts/AuthorizableV06.json", "test/generated-artifacts/D18.json", "test/generated-artifacts/DeploymentConstants.json", "test/generated-artifacts/IAuthorizable.json", + "test/generated-artifacts/IAuthorizableV06.json", "test/generated-artifacts/IOwnable.json", "test/generated-artifacts/IOwnableV06.json", "test/generated-artifacts/LibAddress.json", "test/generated-artifacts/LibAddressArray.json", "test/generated-artifacts/LibAddressArrayRichErrors.json", "test/generated-artifacts/LibAuthorizableRichErrors.json", + "test/generated-artifacts/LibAuthorizableRichErrorsV06.json", "test/generated-artifacts/LibBytes.json", "test/generated-artifacts/LibBytesRichErrors.json", "test/generated-artifacts/LibBytesRichErrorsV06.json", @@ -40,14 +43,22 @@ "test/generated-artifacts/LibEIP1271.json", "test/generated-artifacts/LibEIP712.json", "test/generated-artifacts/LibFractions.json", + "test/generated-artifacts/LibMathRichErrorsV06.json", + "test/generated-artifacts/LibMathV06.json", "test/generated-artifacts/LibOwnableRichErrors.json", + "test/generated-artifacts/LibOwnableRichErrorsV06.json", "test/generated-artifacts/LibReentrancyGuardRichErrors.json", + "test/generated-artifacts/LibReentrancyGuardRichErrorsV06.json", "test/generated-artifacts/LibRichErrors.json", "test/generated-artifacts/LibRichErrorsV06.json", "test/generated-artifacts/LibSafeMath.json", "test/generated-artifacts/LibSafeMathRichErrors.json", + "test/generated-artifacts/LibSafeMathRichErrorsV06.json", + "test/generated-artifacts/LibSafeMathV06.json", "test/generated-artifacts/Ownable.json", + "test/generated-artifacts/OwnableV06.json", "test/generated-artifacts/ReentrancyGuard.json", + "test/generated-artifacts/ReentrancyGuardV06.json", "test/generated-artifacts/Refundable.json", "test/generated-artifacts/TestAuthorizable.json", "test/generated-artifacts/TestLibAddress.json", diff --git a/contracts/zero-ex/CHANGELOG.json b/contracts/zero-ex/CHANGELOG.json index c6d63cc417..4d4008b77e 100644 --- a/contracts/zero-ex/CHANGELOG.json +++ b/contracts/zero-ex/CHANGELOG.json @@ -5,6 +5,10 @@ { "note": "Create this package", "pr": 2540 + }, + { + "note": "Introduce fill `TransformERC20` feature.", + "pr": 2545 } ] } diff --git a/contracts/zero-ex/contracts/src/errors/LibCommonRichErrors.sol b/contracts/zero-ex/contracts/src/errors/LibCommonRichErrors.sol index a0314db2ad..17a038c1d9 100644 --- a/contracts/zero-ex/contracts/src/errors/LibCommonRichErrors.sol +++ b/contracts/zero-ex/contracts/src/errors/LibCommonRichErrors.sol @@ -33,4 +33,14 @@ library LibCommonRichErrors { sender ); } + + function IllegalReentrancyError() + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + bytes4(keccak256("IllegalReentrancyError()")) + ); + } } diff --git a/contracts/zero-ex/contracts/src/errors/LibSpenderRichErrors.sol b/contracts/zero-ex/contracts/src/errors/LibSpenderRichErrors.sol new file mode 100644 index 0000000000..75a4e05a30 --- /dev/null +++ b/contracts/zero-ex/contracts/src/errors/LibSpenderRichErrors.sol @@ -0,0 +1,46 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; + + +library LibSpenderRichErrors { + + // solhint-disable func-name-mixedcase + + function SpenderERC20TransferFromFailedError( + address token, + address owner, + address to, + uint256 amount, + bytes memory errorData + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + bytes4(keccak256("SpenderERC20TransferFromFailedError(address,address,address,uint256,bytes)")), + token, + owner, + to, + amount, + errorData + ); + } +} diff --git a/contracts/zero-ex/contracts/src/errors/LibTransformERC20RichErrors.sol b/contracts/zero-ex/contracts/src/errors/LibTransformERC20RichErrors.sol new file mode 100644 index 0000000000..f35179edd5 --- /dev/null +++ b/contracts/zero-ex/contracts/src/errors/LibTransformERC20RichErrors.sol @@ -0,0 +1,209 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; + + +library LibTransformERC20RichErrors { + + // solhint-disable func-name-mixedcase,separate-by-one-line-in-contract + + function InsufficientEthAttachedError( + uint256 ethAttached, + uint256 ethNeeded + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + bytes4(keccak256("InsufficientEthAttachedError(uint256,uint256)")), + ethAttached, + ethNeeded + ); + } + + function IncompleteTransformERC20Error( + address outputToken, + uint256 outputTokenAmount, + uint256 minOutputTokenAmount + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + bytes4(keccak256("IncompleteTransformERC20Error(address,uint256,uint256)")), + outputToken, + outputTokenAmount, + minOutputTokenAmount + ); + } + + function NegativeTransformERC20OutputError( + address outputToken, + uint256 outputTokenLostAmount + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + bytes4(keccak256("NegativeTransformERC20OutputError(address,uint256)")), + outputToken, + outputTokenLostAmount + ); + } + + function UnauthorizedTransformerError( + address transformer, + bytes memory rlpNonce + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + bytes4(keccak256("UnauthorizedTransformerError(address,bytes)")), + transformer, + rlpNonce + ); + } + + function InvalidRLPNonceError( + bytes memory rlpNonce + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + bytes4(keccak256("InvalidRLPNonceError(bytes)")), + rlpNonce + ); + } + + // FillQuoteTransformer errors ///////////////////////////////////////////// + + function IncompleteFillSellQuoteError( + address sellToken, + uint256 soldAmount, + uint256 sellAmount + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + bytes4(keccak256("IncompleteFillSellQuoteError(address,uint256,uint256)")), + sellToken, + soldAmount, + sellAmount + ); + } + + function IncompleteFillBuyQuoteError( + address buyToken, + uint256 boughtAmount, + uint256 buyAmount + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + bytes4(keccak256("IncompleteFillBuyQuoteError(address,uint256,uint256)")), + buyToken, + boughtAmount, + buyAmount + ); + } + + function InsufficientTakerTokenError( + uint256 tokenBalance, + uint256 tokensNeeded + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + bytes4(keccak256("InsufficientTakerTokenError(uint256,uint256)")), + tokenBalance, + tokensNeeded + ); + } + + function InsufficientProtocolFeeError( + uint256 ethBalance, + uint256 ethNeeded + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + bytes4(keccak256("InsufficientProtocolFeeError(uint256,uint256)")), + ethBalance, + ethNeeded + ); + } + + function InvalidERC20AssetDataError( + bytes memory assetData + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + bytes4(keccak256("InvalidERC20AssetDataError(bytes)")), + assetData + ); + } + + // WethTransformer errors //////////////////////////////////////////////////// + + function WrongNumberOfTokensReceivedError( + uint256 actual, + uint256 expected + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + bytes4(keccak256("WrongNumberOfTokensReceivedError(uint256,uint256)")), + actual, + expected + ); + } + + function InvalidTokenReceivedError( + address token + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + bytes4(keccak256("InvalidTokenReceivedError(address)")), + token + ); + } +} diff --git a/contracts/zero-ex/contracts/src/errors/LibWalletRichErrors.sol b/contracts/zero-ex/contracts/src/errors/LibWalletRichErrors.sol new file mode 100644 index 0000000000..c91e9bfb75 --- /dev/null +++ b/contracts/zero-ex/contracts/src/errors/LibWalletRichErrors.sol @@ -0,0 +1,65 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; + + +library LibWalletRichErrors { + + // solhint-disable func-name-mixedcase + + function WalletExecuteCallFailedError( + address wallet, + address callTarget, + bytes memory callData, + uint256 callValue, + bytes memory errorData + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + bytes4(keccak256("WalletExecuteCallFailedError(address,address,bytes,uint256,bytes)")), + wallet, + callTarget, + callData, + callValue, + errorData + ); + } + + function WalletExecuteDelegateCallFailedError( + address wallet, + address callTarget, + bytes memory callData, + bytes memory errorData + ) + internal + pure + returns (bytes memory) + { + return abi.encodeWithSelector( + bytes4(keccak256("WalletExecuteDelegateCallFailedError(address,address,bytes,bytes)")), + wallet, + callTarget, + callData, + errorData + ); + } +} diff --git a/contracts/zero-ex/contracts/src/external/AllowanceTarget.sol b/contracts/zero-ex/contracts/src/external/AllowanceTarget.sol new file mode 100644 index 0000000000..7675376c67 --- /dev/null +++ b/contracts/zero-ex/contracts/src/external/AllowanceTarget.sol @@ -0,0 +1,56 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; +import "@0x/contracts-utils/contracts/src/v06/AuthorizableV06.sol"; +import "../errors/LibSpenderRichErrors.sol"; +import "./IAllowanceTarget.sol"; + + +/// @dev The allowance target for the TokenSpender feature. +contract AllowanceTarget is + IAllowanceTarget, + AuthorizableV06 +{ + // solhint-disable no-unused-vars,indent,no-empty-blocks + using LibRichErrorsV06 for bytes; + + /// @dev Execute an arbitrary call. Only an authority can call this. + /// @param target The call target. + /// @param callData The call data. + /// @return resultData The data returned by the call. + function executeCall( + address payable target, + bytes calldata callData + ) + external + payable + override + onlyAuthorized + returns (bytes memory resultData) + { + bool success; + (success, resultData) = target.call(callData); + if (!success) { + resultData.rrevert(); + } + } +} diff --git a/contracts/zero-ex/contracts/src/external/FlashWallet.sol b/contracts/zero-ex/contracts/src/external/FlashWallet.sol new file mode 100644 index 0000000000..f6039e9d02 --- /dev/null +++ b/contracts/zero-ex/contracts/src/external/FlashWallet.sol @@ -0,0 +1,175 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; +import "@0x/contracts-utils/contracts/src/v06/errors/LibOwnableRichErrorsV06.sol"; +import "../errors/LibWalletRichErrors.sol"; +import "./IFlashWallet.sol"; + + +/// @dev A contract that can execute arbitrary calls from its owner. +contract FlashWallet is + IFlashWallet +{ + // solhint-disable no-unused-vars,indent,no-empty-blocks + using LibRichErrorsV06 for bytes; + + // solhint-disable + /// @dev Store the owner/deployer as an immutable to make this contract stateless. + address public override immutable owner; + // solhint-enable + + constructor() public { + // The deployer is the owner. + owner = msg.sender; + } + + /// @dev Allows only the (immutable) owner to call a function. + modifier onlyOwner() virtual { + if (msg.sender != owner) { + LibOwnableRichErrorsV06.OnlyOwnerError( + msg.sender, + owner + ).rrevert(); + } + _; + } + + /// @dev Execute an arbitrary call. Only an authority can call this. + /// @param target The call target. + /// @param callData The call data. + /// @param value Ether to attach to the call. + /// @return resultData The data returned by the call. + function executeCall( + address payable target, + bytes calldata callData, + uint256 value + ) + external + payable + override + onlyOwner + returns (bytes memory resultData) + { + bool success; + (success, resultData) = target.call{value: value}(callData); + if (!success) { + LibWalletRichErrors + .WalletExecuteCallFailedError( + address(this), + target, + callData, + value, + resultData + ) + .rrevert(); + } + } + + /// @dev Execute an arbitrary delegatecall, in the context of this puppet. + /// Only an authority can call this. + /// @param target The call target. + /// @param callData The call data. + /// @return resultData The data returned by the call. + function executeDelegateCall( + address payable target, + bytes calldata callData + ) + external + payable + override + onlyOwner + returns (bytes memory resultData) + { + bool success; + (success, resultData) = target.delegatecall(callData); + if (!success) { + LibWalletRichErrors + .WalletExecuteDelegateCallFailedError( + address(this), + target, + callData, + resultData + ) + .rrevert(); + } + } + + // solhint-disable + /// @dev Allows this contract to receive ether. + receive() external override payable {} + // solhint-enable + + /// @dev Signal support for receiving ERC1155 tokens. + /// @param interfaceID The interface ID, as per ERC-165 rules. + /// @return hasSupport `true` if this contract supports an ERC-165 interface. + function supportsInterface(bytes4 interfaceID) + external + pure + returns (bool hasSupport) + { + return interfaceID == this.supportsInterface.selector || + interfaceID == this.onERC1155Received.selector ^ this.onERC1155BatchReceived.selector || + interfaceID == this.tokenFallback.selector; + } + + /// @dev Allow this contract to receive ERC1155 tokens. + /// @return success `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` + function onERC1155Received( + address, // operator, + address, // from, + uint256, // id, + uint256, // value, + bytes calldata //data + ) + external + pure + returns (bytes4 success) + { + return this.onERC1155Received.selector; + } + + /// @dev Allow this contract to receive ERC1155 tokens. + /// @return success `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` + function onERC1155BatchReceived( + address, // operator, + address, // from, + uint256[] calldata, // ids, + uint256[] calldata, // values, + bytes calldata // data + ) + external + pure + returns (bytes4 success) + { + return this.onERC1155BatchReceived.selector; + } + + /// @dev Allows this contract to receive ERC223 tokens. + function tokenFallback( + address, // from, + uint256, // value, + bytes calldata // value + ) + external + pure + {} +} diff --git a/contracts/zero-ex/contracts/src/external/IAllowanceTarget.sol b/contracts/zero-ex/contracts/src/external/IAllowanceTarget.sol new file mode 100644 index 0000000000..1f84fff3da --- /dev/null +++ b/contracts/zero-ex/contracts/src/external/IAllowanceTarget.sol @@ -0,0 +1,40 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-utils/contracts/src/v06/interfaces/IAuthorizableV06.sol"; + + +/// @dev The allowance target for the TokenSpender feature. +interface IAllowanceTarget is + IAuthorizableV06 +{ + /// @dev Execute an arbitrary call. Only an authority can call this. + /// @param target The call target. + /// @param callData The call data. + /// @return resultData The data returned by the call. + function executeCall( + address payable target, + bytes calldata callData + ) + external + payable + returns (bytes memory resultData); +} diff --git a/contracts/zero-ex/contracts/src/external/IFlashWallet.sol b/contracts/zero-ex/contracts/src/external/IFlashWallet.sol new file mode 100644 index 0000000000..f8c81dc16e --- /dev/null +++ b/contracts/zero-ex/contracts/src/external/IFlashWallet.sol @@ -0,0 +1,61 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-utils/contracts/src/v06/interfaces/IOwnableV06.sol"; + + +/// @dev A contract that can execute arbitrary calls from its owner. +interface IFlashWallet { + + /// @dev Execute an arbitrary call. Only an authority can call this. + /// @param target The call target. + /// @param callData The call data. + /// @param value Ether to attach to the call. + /// @return resultData The data returned by the call. + function executeCall( + address payable target, + bytes calldata callData, + uint256 value + ) + external + payable + returns (bytes memory resultData); + + /// @dev Execute an arbitrary delegatecall, in the context of this puppet. + /// Only an authority can call this. + /// @param target The call target. + /// @param callData The call data. + /// @return resultData The data returned by the call. + function executeDelegateCall( + address payable target, + bytes calldata callData + ) + external + payable + returns (bytes memory resultData); + + /// @dev Allows the puppet to receive ETH. + receive() external payable; + + /// @dev Fetch the immutable owner/deployer of this contract. + /// @return owner_ The immutable owner/deployer/ + function owner() external view returns (address owner_); +} diff --git a/contracts/zero-ex/contracts/src/features/ITokenSpender.sol b/contracts/zero-ex/contracts/src/features/ITokenSpender.sol new file mode 100644 index 0000000000..8e6128cd5e --- /dev/null +++ b/contracts/zero-ex/contracts/src/features/ITokenSpender.sol @@ -0,0 +1,55 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; + + +/// @dev Feature that allows spending token allowances. +interface ITokenSpender { + + /// @dev Transfers ERC20 tokens from `owner` to `to`. + /// Only callable from within. + /// @param token The token to spend. + /// @param owner The owner of the tokens. + /// @param to The recipient of the tokens. + /// @param amount The amount of `token` to transfer. + function _spendERC20Tokens( + IERC20TokenV06 token, + address owner, + address to, + uint256 amount + ) + external; + + /// @dev Gets the maximum amount of an ERC20 token `token` that can be + /// pulled from `owner`. + /// @param token The token to spend. + /// @param owner The owner of the tokens. + /// @return amount The amount of tokens that can be pulled. + function getSpendableERC20BalanceOf(IERC20TokenV06 token, address owner) + external + view + returns (uint256 amount); + + /// @dev Get the address of the allowance target. + /// @return target The target of token allowances. + function getAllowanceTarget() external view returns (address target); +} diff --git a/contracts/zero-ex/contracts/src/features/ITransformERC20.sol b/contracts/zero-ex/contracts/src/features/ITransformERC20.sol new file mode 100644 index 0000000000..e2259be51a --- /dev/null +++ b/contracts/zero-ex/contracts/src/features/ITransformERC20.sol @@ -0,0 +1,126 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; +import "../transformers/IERC20Transformer.sol"; +import "../external/IFlashWallet.sol"; + + +/// @dev Feature to composably transform between ERC20 tokens. +interface ITransformERC20 { + + /// @dev Defines a transformation to run in `transformERC20()`. + struct Transformation { + // The transformation handler. + // Can receive the entire balance of `tokens`. + IERC20Transformer transformer; + // Arbitrary data to pass to the transformer. + bytes data; + } + + /// @dev Raised upon a successful `transformERC20`. + /// @param taker The taker (caller) address. + /// @param inputToken The token being provided by the taker. + /// If `0xeee...`, ETH is implied and should be provided with the call.` + /// @param outputToken The token to be acquired by the taker. + /// `0xeee...` implies ETH. + /// @param inputTokenAmount The amount of `inputToken` to take from the taker. + /// @param outputTokenAmount The amount of `outputToken` received by the taker. + event TransformedERC20( + address indexed taker, + address inputToken, + address outputToken, + uint256 inputTokenAmount, + uint256 outputTokenAmount + ); + + /// @dev Deploy a new flash wallet instance and replace the current one with it. + /// Useful if we somehow break the current wallet instance. + /// Anyone can call this. + /// @return wallet The new wallet instance. + function createTransformWallet() + external + returns (IFlashWallet wallet); + + /// @dev Executes a series of transformations to convert an ERC20 `inputToken` + /// to an ERC20 `outputToken`. + /// @param inputToken The token being provided by the sender. + /// If `0xeee...`, ETH is implied and should be provided with the call.` + /// @param outputToken The token to be acquired by the sender. + /// `0xeee...` implies ETH. + /// @param inputTokenAmount The amount of `inputToken` to take from the sender. + /// @param minOutputTokenAmount The minimum amount of `outputToken` the sender + /// must receive for the entire transformation to succeed. + /// @param transformations The transformations to execute on the token balance(s) + /// in sequence. + /// @return outputTokenAmount The amount of `outputToken` received by the sender. + function transformERC20( + IERC20TokenV06 inputToken, + IERC20TokenV06 outputToken, + uint256 inputTokenAmount, + uint256 minOutputTokenAmount, + Transformation[] calldata transformations + ) + external + payable + returns (uint256 outputTokenAmount); + + /// @dev Internal version of `transformERC20()`. Only callable from within. + /// @param callDataHash Hash of the ingress calldata. + /// @param taker The taker address. + /// @param inputToken The token being provided by the taker. + /// If `0xeee...`, ETH is implied and should be provided with the call.` + /// @param outputToken The token to be acquired by the taker. + /// `0xeee...` implies ETH. + /// @param inputTokenAmount The amount of `inputToken` to take from the taker. + /// @param minOutputTokenAmount The minimum amount of `outputToken` the taker + /// must receive for the entire transformation to succeed. + /// @param transformations The transformations to execute on the token balance(s) + /// in sequence. + /// @return outputTokenAmount The amount of `outputToken` received by the taker. + function _transformERC20( + bytes32 callDataHash, + address payable taker, + IERC20TokenV06 inputToken, + IERC20TokenV06 outputToken, + uint256 inputTokenAmount, + uint256 minOutputTokenAmount, + Transformation[] calldata transformations + ) + external + payable + returns (uint256 outputTokenAmount); + + /// @dev Return the current wallet instance that will serve as the execution + /// context for transformations. + /// @return wallet The wallet instance. + function getTransformWallet() + external + view + returns (IFlashWallet wallet); + + /// @dev Return the allowed deployer for transformers. + /// @return deployer The transform deployer address. + function getTransformerDeployer() + external + view + returns (address deployer); +} diff --git a/contracts/zero-ex/contracts/src/features/Ownable.sol b/contracts/zero-ex/contracts/src/features/Ownable.sol index 73730f1f28..8261d96b89 100644 --- a/contracts/zero-ex/contracts/src/features/Ownable.sol +++ b/contracts/zero-ex/contracts/src/features/Ownable.sol @@ -37,14 +37,11 @@ contract Ownable is FixinCommon { - // solhint-disable const-name-snakecase - /// @dev Name of this feature. - string constant public override FEATURE_NAME = "Ownable"; - /// @dev Version of this feature. - uint256 constant public override FEATURE_VERSION = (1 << 64) | (0 << 32) | (0); - // solhint-enable const-name-snakecase - // solhint-disable + /// @dev Name of this feature. + string public constant override FEATURE_NAME = "Ownable"; + /// @dev Version of this feature. + uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0); /// @dev The deployed address of this contract. address immutable private _implementation; // solhint-enable diff --git a/contracts/zero-ex/contracts/src/features/SimpleFunctionRegistry.sol b/contracts/zero-ex/contracts/src/features/SimpleFunctionRegistry.sol index 2acbccad8e..60107e520a 100644 --- a/contracts/zero-ex/contracts/src/features/SimpleFunctionRegistry.sol +++ b/contracts/zero-ex/contracts/src/features/SimpleFunctionRegistry.sol @@ -35,17 +35,13 @@ contract SimpleFunctionRegistry is ISimpleFunctionRegistry, FixinCommon { - - // solhint-disable const-name-snakecase - /// @dev Name of this feature. - string constant public override FEATURE_NAME = "SimpleFunctionRegistry"; - /// @dev Version of this feature. - uint256 constant public override FEATURE_VERSION = (1 << 64) | (0 << 32) | (0); - // solhint-enable const-name-snakecase - // solhint-disable + /// @dev Name of this feature. + string public constant override FEATURE_NAME = "SimpleFunctionRegistry"; + /// @dev Version of this feature. + uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0); /// @dev The deployed address of this contract. - address immutable private _implementation; + address private immutable _implementation; // solhint-enable using LibRichErrorsV06 for bytes; @@ -55,16 +51,15 @@ contract SimpleFunctionRegistry is } /// @dev Initializes this feature. - /// @param impl The actual address of this feature contract. /// @return success Magic bytes if successful. - function bootstrap(address impl) external returns (bytes4 success) { + function bootstrap() external returns (bytes4 success) { // Register the registration functions (inception vibes). - _extend(this.extend.selector, impl); + _extend(this.extend.selector, _implementation); // Register the rollback function. - _extend(this.rollback.selector, impl); + _extend(this.rollback.selector, _implementation); // Register getters. - _extend(this.getRollbackLength.selector, impl); - _extend(this.getRollbackEntryAtIndex.selector, impl); + _extend(this.getRollbackLength.selector, _implementation); + _extend(this.getRollbackEntryAtIndex.selector, _implementation); return LibBootstrap.BOOTSTRAP_SUCCESS; } diff --git a/contracts/zero-ex/contracts/src/features/TokenSpender.sol b/contracts/zero-ex/contracts/src/features/TokenSpender.sol new file mode 100644 index 0000000000..7814ea396a --- /dev/null +++ b/contracts/zero-ex/contracts/src/features/TokenSpender.sol @@ -0,0 +1,143 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; +import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol"; +import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; +import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol"; +import "../errors/LibSpenderRichErrors.sol"; +import "../fixins/FixinCommon.sol"; +import "../migrations/LibMigrate.sol"; +import "../external/IAllowanceTarget.sol"; +import "../storage/LibTokenSpenderStorage.sol"; +import "./ITokenSpender.sol"; +import "./IFeature.sol"; +import "./ISimpleFunctionRegistry.sol"; + + +/// @dev Feature that allows spending token allowances. +contract TokenSpender is + IFeature, + ITokenSpender, + FixinCommon +{ + // solhint-disable + /// @dev Name of this feature. + string public constant override FEATURE_NAME = "TokenSpender"; + /// @dev Version of this feature. + uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0); + /// @dev The implementation address of this feature. + address private immutable _implementation; + // solhint-enable + + using LibRichErrorsV06 for bytes; + + constructor() public { + _implementation = address(this); + } + + /// @dev Initialize and register this feature. Should be delegatecalled + /// into during a `Migrate.migrate()`. + /// @param allowanceTarget An `allowanceTarget` instance, configured to have + /// the ZeroeEx contract as an authority. + /// @return success `MIGRATE_SUCCESS` on success. + function migrate(IAllowanceTarget allowanceTarget) external returns (bytes4 success) { + LibTokenSpenderStorage.getStorage().allowanceTarget = allowanceTarget; + ISimpleFunctionRegistry(address(this)) + .extend(this.getAllowanceTarget.selector, _implementation); + ISimpleFunctionRegistry(address(this)) + .extend(this._spendERC20Tokens.selector, _implementation); + ISimpleFunctionRegistry(address(this)) + .extend(this.getSpendableERC20BalanceOf.selector, _implementation); + return LibMigrate.MIGRATE_SUCCESS; + } + + /// @dev Transfers ERC20 tokens from `owner` to `to`. Only callable from within. + /// @param token The token to spend. + /// @param owner The owner of the tokens. + /// @param to The recipient of the tokens. + /// @param amount The amount of `token` to transfer. + function _spendERC20Tokens( + IERC20TokenV06 token, + address owner, + address to, + uint256 amount + ) + external + override + onlySelf + { + IAllowanceTarget spender = LibTokenSpenderStorage.getStorage().allowanceTarget; + // Have the allowance target execute an ERC20 `transferFrom()`. + (bool didSucceed, bytes memory resultData) = address(spender).call( + abi.encodeWithSelector( + IAllowanceTarget.executeCall.selector, + address(token), + abi.encodeWithSelector( + IERC20TokenV06.transferFrom.selector, + owner, + to, + amount + ) + ) + ); + if (didSucceed) { + resultData = abi.decode(resultData, (bytes)); + } + if (!didSucceed || !LibERC20TokenV06.isSuccessfulResult(resultData)) { + LibSpenderRichErrors.SpenderERC20TransferFromFailedError( + address(token), + owner, + to, + amount, + resultData + ).rrevert(); + } + } + + /// @dev Gets the maximum amount of an ERC20 token `token` that can be + /// pulled from `owner` by the token spender. + /// @param token The token to spend. + /// @param owner The owner of the tokens. + /// @return amount The amount of tokens that can be pulled. + function getSpendableERC20BalanceOf(IERC20TokenV06 token, address owner) + external + override + view + returns (uint256 amount) + { + return LibSafeMathV06.min256( + token.allowance(owner, address(LibTokenSpenderStorage.getStorage().allowanceTarget)), + token.balanceOf(owner) + ); + } + + /// @dev Get the address of the allowance target. + /// @return target The target of token allowances. + function getAllowanceTarget() + external + override + view + returns (address target) + { + return address(LibTokenSpenderStorage.getStorage().allowanceTarget); + } +} diff --git a/contracts/zero-ex/contracts/src/features/TransformERC20.sol b/contracts/zero-ex/contracts/src/features/TransformERC20.sol new file mode 100644 index 0000000000..3a736a8c00 --- /dev/null +++ b/contracts/zero-ex/contracts/src/features/TransformERC20.sol @@ -0,0 +1,376 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; +import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol"; +import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; +import "@0x/contracts-utils/contracts/src/v06/LibSafeMathV06.sol"; +import "../errors/LibTransformERC20RichErrors.sol"; +import "../fixins/FixinCommon.sol"; +import "../migrations/LibMigrate.sol"; +import "../external/IFlashWallet.sol"; +import "../external/FlashWallet.sol"; +import "../storage/LibTransformERC20Storage.sol"; +import "../transformers/IERC20Transformer.sol"; +import "../transformers/LibERC20Transformer.sol"; +import "./ITransformERC20.sol"; +import "./ITokenSpender.sol"; +import "./IFeature.sol"; +import "./ISimpleFunctionRegistry.sol"; + + +/// @dev Feature to composably transform between ERC20 tokens. +contract TransformERC20 is + IFeature, + ITransformERC20, + FixinCommon +{ + + // solhint-disable + /// @dev Name of this feature. + string public constant override FEATURE_NAME = "TransformERC20"; + /// @dev Version of this feature. + uint256 public immutable override FEATURE_VERSION = _encodeVersion(1, 0, 0); + /// @dev The trusted deployer for all transformers. + address public immutable transformDeployer; + /// @dev The implementation address of this feature. + address private immutable _implementation; + // solhint-enable + + using LibSafeMathV06 for uint256; + using LibRichErrorsV06 for bytes; + + constructor(address trustedDeployer_) public { + _implementation = address(this); + transformDeployer = trustedDeployer_; + } + + /// @dev Initialize and register this feature. + /// Should be delegatecalled by `Migrate.migrate()`. + function migrate() external returns (bytes4 success) { + ISimpleFunctionRegistry(address(this)) + .extend(this.getTransformerDeployer.selector, _implementation); + ISimpleFunctionRegistry(address(this)) + .extend(this.createTransformWallet.selector, _implementation); + ISimpleFunctionRegistry(address(this)) + .extend(this.getTransformWallet.selector, _implementation); + ISimpleFunctionRegistry(address(this)) + .extend(this.transformERC20.selector, _implementation); + ISimpleFunctionRegistry(address(this)) + .extend(this._transformERC20.selector, _implementation); + createTransformWallet(); + return LibMigrate.MIGRATE_SUCCESS; + } + + /// @dev Return the allowed deployer for transformers. + /// @return deployer The transform deployer address. + function getTransformerDeployer() + external + override + view + returns (address deployer) + { + return transformDeployer; + } + + /// @dev Deploy a new wallet instance and replace the current one with it. + /// Useful if we somehow break the current wallet instance. + /// Anyone can call this. + /// @return wallet The new wallet instance. + function createTransformWallet() + public + override + returns (IFlashWallet wallet) + { + wallet = new FlashWallet(); + LibTransformERC20Storage.getStorage().wallet = wallet; + } + + /// @dev Executes a series of transformations to convert an ERC20 `inputToken` + /// to an ERC20 `outputToken`. + /// @param inputToken The token being provided by the sender. + /// If `0xeee...`, ETH is implied and should be provided with the call.` + /// @param outputToken The token to be acquired by the sender. + /// `0xeee...` implies ETH. + /// @param inputTokenAmount The amount of `inputToken` to take from the sender. + /// If set to `uint256(-1)`, the entire spendable balance of the taker + /// will be solt. + /// @param minOutputTokenAmount The minimum amount of `outputToken` the sender + /// must receive for the entire transformation to succeed. If set to zero, + /// the minimum output token transfer will not be asserted. + /// @param transformations The transformations to execute on the token balance(s) + /// in sequence. + /// @return outputTokenAmount The amount of `outputToken` received by the sender. + function transformERC20( + IERC20TokenV06 inputToken, + IERC20TokenV06 outputToken, + uint256 inputTokenAmount, + uint256 minOutputTokenAmount, + Transformation[] memory transformations + ) + public + override + payable + returns (uint256 outputTokenAmount) + { + return _transformERC20Private( + keccak256(msg.data), + msg.sender, + inputToken, + outputToken, + inputTokenAmount, + minOutputTokenAmount, + transformations + ); + } + + /// @dev Internal version of `transformERC20()`. Only callable from within. + /// @param callDataHash Hash of the ingress calldata. + /// @param taker The taker address. + /// @param inputToken The token being provided by the taker. + /// If `0xeee...`, ETH is implied and should be provided with the call.` + /// @param outputToken The token to be acquired by the taker. + /// `0xeee...` implies ETH. + /// @param inputTokenAmount The amount of `inputToken` to take from the taker. + /// If set to `uint256(-1)`, the entire spendable balance of the taker + /// will be solt. + /// @param minOutputTokenAmount The minimum amount of `outputToken` the taker + /// must receive for the entire transformation to succeed. If set to zero, + /// the minimum output token transfer will not be asserted. + /// @param transformations The transformations to execute on the token balance(s) + /// in sequence. + /// @return outputTokenAmount The amount of `outputToken` received by the taker. + function _transformERC20( + bytes32 callDataHash, + address payable taker, + IERC20TokenV06 inputToken, + IERC20TokenV06 outputToken, + uint256 inputTokenAmount, + uint256 minOutputTokenAmount, + Transformation[] memory transformations + ) + public + override + payable + onlySelf + returns (uint256 outputTokenAmount) + { + return _transformERC20Private( + callDataHash, + taker, + inputToken, + outputToken, + inputTokenAmount, + minOutputTokenAmount, + transformations + ); + } + + /// @dev Private version of `transformERC20()`. + /// @param callDataHash Hash of the ingress calldata. + /// @param taker The taker address. + /// @param inputToken The token being provided by the taker. + /// If `0xeee...`, ETH is implied and should be provided with the call.` + /// @param outputToken The token to be acquired by the taker. + /// `0xeee...` implies ETH. + /// @param inputTokenAmount The amount of `inputToken` to take from the taker. + /// If set to `uint256(-1)`, the entire spendable balance of the taker + /// will be solt. + /// @param minOutputTokenAmount The minimum amount of `outputToken` the taker + /// must receive for the entire transformation to succeed. If set to zero, + /// the minimum output token transfer will not be asserted. + /// @param transformations The transformations to execute on the token balance(s) + /// in sequence. + /// @return outputTokenAmount The amount of `outputToken` received by the taker. + function _transformERC20Private( + bytes32 callDataHash, + address payable taker, + IERC20TokenV06 inputToken, + IERC20TokenV06 outputToken, + uint256 inputTokenAmount, + uint256 minOutputTokenAmount, + Transformation[] memory transformations + ) + public + payable + returns (uint256 outputTokenAmount) + { + // If the input token amount is -1, transform the taker's entire + // spendable balance. + if (inputTokenAmount == uint256(-1)) { + inputTokenAmount = ITokenSpender(address(this)) + .getSpendableERC20BalanceOf(inputToken, taker); + } + + IFlashWallet wallet = getTransformWallet(); + + // Remember the initial output token balance of the taker. + uint256 takerOutputTokenBalanceBefore = + LibERC20Transformer.getTokenBalanceOf(outputToken, taker); + + // Pull input tokens from the taker to the wallet and transfer attached ETH. + _transferInputTokensAndAttachedEth(inputToken, taker, address(wallet), inputTokenAmount); + + // Perform transformations. + for (uint256 i = 0; i < transformations.length; ++i) { + _executeTransformation(wallet, transformations[i], taker, callDataHash); + } + + // Compute how much output token has been transferred to the taker. + uint256 takerOutputTokenBalanceAfter = + LibERC20Transformer.getTokenBalanceOf(outputToken, taker); + if (takerOutputTokenBalanceAfter > takerOutputTokenBalanceBefore) { + outputTokenAmount = takerOutputTokenBalanceAfter.safeSub( + takerOutputTokenBalanceBefore + ); + } else if (takerOutputTokenBalanceAfter < takerOutputTokenBalanceBefore) { + LibTransformERC20RichErrors.NegativeTransformERC20OutputError( + address(outputToken), + takerOutputTokenBalanceBefore - takerOutputTokenBalanceAfter + ).rrevert(); + } + // Ensure enough output token has been sent to the taker. + if (outputTokenAmount < minOutputTokenAmount) { + LibTransformERC20RichErrors.IncompleteTransformERC20Error( + address(outputToken), + outputTokenAmount, + minOutputTokenAmount + ).rrevert(); + } + + // Emit an event. + emit TransformedERC20( + taker, + address(inputToken), + address(outputToken), + inputTokenAmount, + outputTokenAmount + ); + } + + /// @dev Return the current wallet instance that will serve as the execution + /// context for transformations. + /// @return wallet The wallet instance. + function getTransformWallet() + public + override + view + returns (IFlashWallet wallet) + { + return LibTransformERC20Storage.getStorage().wallet; + } + + /// @dev Transfer input tokens from the taker and any attached ETH to `to` + /// @param inputToken The token to pull from the taker. + /// @param from The from (taker) address. + /// @param to The recipient of tokens and ETH. + /// @param amount Amount of `inputToken` tokens to transfer. + function _transferInputTokensAndAttachedEth( + IERC20TokenV06 inputToken, + address from, + address payable to, + uint256 amount + ) + private + { + // Transfer any attached ETH. + if (msg.value != 0) { + to.transfer(msg.value); + } + // Transfer input tokens. + if (!LibERC20Transformer.isTokenETH(inputToken)) { + // Token is not ETH, so pull ERC20 tokens. + ITokenSpender(address(this))._spendERC20Tokens( + inputToken, + from, + to, + amount + ); + } else if (msg.value < amount) { + // Token is ETH, so the caller must attach enough ETH to the call. + LibTransformERC20RichErrors.InsufficientEthAttachedError( + msg.value, + amount + ).rrevert(); + } + } + + /// @dev Executs a transformer in the context of `wallet`. + /// @param wallet The wallet instance. + /// @param transformation The transformation. + /// @param taker The taker address. + /// @param callDataHash Hash of the calldata. + function _executeTransformation( + IFlashWallet wallet, + Transformation memory transformation, + address payable taker, + bytes32 callDataHash + ) + private + { + // Call `transformer.transform()` as the wallet. + bytes memory resultData = wallet.executeDelegateCall( + // Call target. + address(uint160(address(transformation.transformer))), + // Call data. + abi.encodeWithSelector( + IERC20Transformer.transform.selector, + callDataHash, + taker, + transformation.data + ) + ); + // Ensure the transformer returned its valid rlp-encoded deployment nonce. + bytes memory rlpNonce = resultData.length == 0 + ? new bytes(0) + : abi.decode(resultData, (bytes)); + if (_getExpectedDeployment(rlpNonce) != address(transformation.transformer)) { + LibTransformERC20RichErrors.UnauthorizedTransformerError( + address(transformation.transformer), + rlpNonce + ).rrevert(); + } + } + + /// @dev Compute the expected deployment address by `transformDeployer` at + /// the nonce given by `rlpNonce`. + /// @param rlpNonce The RLP-encoded nonce that + /// the deployer had when deploying a contract. + /// @return deploymentAddress The deployment address. + function _getExpectedDeployment(bytes memory rlpNonce) + private + view + returns (address deploymentAddress) + { + // See https://github.com/ethereum/wiki/wiki/RLP for RLP encoding rules. + // The RLP-encoded nonce may be prefixed with a length byte. + // We only support nonces up to 32-bits. + if (rlpNonce.length == 0 || rlpNonce.length > 5) { + LibTransformERC20RichErrors.InvalidRLPNonceError(rlpNonce).rrevert(); + } + return address(uint160(uint256(keccak256(abi.encodePacked( + byte(uint8(0xC0 + 21 + rlpNonce.length)), + byte(uint8(0x80 + 20)), + transformDeployer, + rlpNonce + ))))); + } +} diff --git a/contracts/zero-ex/contracts/src/fixins/FixinCommon.sol b/contracts/zero-ex/contracts/src/fixins/FixinCommon.sol index 7a2cd44389..5908d1322e 100644 --- a/contracts/zero-ex/contracts/src/fixins/FixinCommon.sol +++ b/contracts/zero-ex/contracts/src/fixins/FixinCommon.sol @@ -22,6 +22,7 @@ pragma experimental ABIEncoderV2; import "@0x/contracts-utils/contracts/src/v06/errors/LibRichErrorsV06.sol"; import "../errors/LibCommonRichErrors.sol"; import "../errors/LibOwnableRichErrors.sol"; +import "../features/IOwnable.sol"; import "../storage/LibOwnableStorage.sol"; @@ -31,7 +32,7 @@ contract FixinCommon { using LibRichErrorsV06 for bytes; /// @dev The caller must be this contract. - modifier onlySelf() { + modifier onlySelf() virtual { if (msg.sender != address(this)) { LibCommonRichErrors.OnlyCallableBySelfError(msg.sender).rrevert(); } @@ -39,7 +40,7 @@ contract FixinCommon { } /// @dev The caller of this function must be the owner. - modifier onlyOwner() { + modifier onlyOwner() virtual { { address owner = _getOwner(); if (msg.sender != owner) { @@ -54,7 +55,22 @@ contract FixinCommon { /// @dev Get the owner of this contract. /// @return owner The owner of this contract. - function _getOwner() internal view returns (address owner) { + function _getOwner() internal virtual view returns (address owner) { + // We access Ownable's storage directly here instead of using the external + // API because `onlyOwner` needs to function during bootstrapping. return LibOwnableStorage.getStorage().owner; } + + /// @dev Encode a feature version as a `uint256`. + /// @param major The major version number of the feature. + /// @param minor The minor version number of the feature. + /// @param revision The revision number of the feature. + /// @return encodedVersion The encoded version number. + function _encodeVersion(uint32 major, uint32 minor, uint32 revision) + internal + pure + returns (uint256 encodedVersion) + { + return (major << 64) | (minor << 32) | revision; + } } diff --git a/contracts/zero-ex/contracts/src/migrations/FullMigration.sol b/contracts/zero-ex/contracts/src/migrations/FullMigration.sol new file mode 100644 index 0000000000..2c54950324 --- /dev/null +++ b/contracts/zero-ex/contracts/src/migrations/FullMigration.sol @@ -0,0 +1,147 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + +import "../ZeroEx.sol"; +import "../features/IOwnable.sol"; +import "../features/TokenSpender.sol"; +import "../features/TransformERC20.sol"; +import "../external/AllowanceTarget.sol"; +import "./InitialMigration.sol"; + + +/// @dev A contract for deploying and configuring the full ZeroEx contract. +contract FullMigration { + + // solhint-disable no-empty-blocks,indent + + /// @dev Features to add the the proxy contract. + struct Features { + SimpleFunctionRegistry registry; + Ownable ownable; + TokenSpender tokenSpender; + TransformERC20 transformERC20; + } + + /// @dev The allowed caller of `deploy()`. + address public immutable deployer; + /// @dev The initial migration contract. + InitialMigration private _initialMigration; + + /// @dev Instantiate this contract and set the allowed caller of `deploy()` + /// to `deployer`. + /// @param deployer_ The allowed caller of `deploy()`. + constructor(address payable deployer_) + public + { + deployer = deployer_; + // Create an initial migration contract with this contract set to the + // allowed deployer. + _initialMigration = new InitialMigration(address(this)); + } + + /// @dev Deploy the `ZeroEx` contract with the full feature set, + /// transfer ownership to `owner`, then self-destruct. + /// @param owner The owner of the contract. + /// @param features Features to add to the proxy. + /// @return zeroEx The deployed and configured `ZeroEx` contract. + function deploy( + address payable owner, + Features memory features + ) + public + returns (ZeroEx zeroEx) + { + require(msg.sender == deployer, "FullMigration/INVALID_SENDER"); + + // Perform the initial migration with the owner set to this contract. + zeroEx = _initialMigration.deploy( + address(uint160(address(this))), + InitialMigration.BootstrapFeatures({ + registry: features.registry, + ownable: features.ownable + }) + ); + + // Add features. + _addFeatures(zeroEx, owner, features); + + // Transfer ownership to the real owner. + IOwnable(address(zeroEx)).transferOwnership(owner); + + // Self-destruct. + this.die(owner); + } + + /// @dev Destroy this contract. Only callable from ourselves (from `deploy()`). + /// @param ethRecipient Receiver of any ETH in this contract. + function die(address payable ethRecipient) + external + virtual + { + require(msg.sender == address(this), "FullMigration/INVALID_SENDER"); + // This contract should not hold any funds but we send + // them to the ethRecipient just in case. + selfdestruct(ethRecipient); + } + + /// @dev Deploy and register features to the ZeroEx contract. + /// @param zeroEx The bootstrapped ZeroEx contract. + /// @param owner The ultimate owner of the ZeroEx contract. + /// @param features Features to add to the proxy. + function _addFeatures( + ZeroEx zeroEx, + address owner, + Features memory features + ) + private + { + IOwnable ownable = IOwnable(address(zeroEx)); + // TokenSpender + { + // Create the allowance target. + AllowanceTarget allowanceTarget = new AllowanceTarget(); + // Let the ZeroEx contract use the allowance target. + allowanceTarget.addAuthorizedAddress(address(zeroEx)); + // Transfer ownership of the allowance target to the (real) owner. + allowanceTarget.transferOwnership(owner); + // Register the feature. + ownable.migrate( + address(features.tokenSpender), + abi.encodeWithSelector( + TokenSpender.migrate.selector, + allowanceTarget + ), + address(this) + ); + } + // TransformERC20 + { + // Register the feature. + ownable.migrate( + address(features.transformERC20), + abi.encodeWithSelector( + TransformERC20.migrate.selector + ), + address(this) + ); + } + } +} diff --git a/contracts/zero-ex/contracts/src/migrations/InitialMigration.sol b/contracts/zero-ex/contracts/src/migrations/InitialMigration.sol index dd49672aeb..d3928c7ff3 100644 --- a/contracts/zero-ex/contracts/src/migrations/InitialMigration.sol +++ b/contracts/zero-ex/contracts/src/migrations/InitialMigration.sol @@ -29,6 +29,12 @@ import "./LibBootstrap.sol"; /// @dev A contract for deploying and configuring a minimal ZeroEx contract. contract InitialMigration { + /// @dev Features to bootstrap into the the proxy contract. + struct BootstrapFeatures { + SimpleFunctionRegistry registry; + Ownable ownable; + } + /// @dev The allowed caller of `deploy()`. In production, this would be /// the governor. address public immutable deployer; @@ -47,8 +53,13 @@ contract InitialMigration { /// transfers ownership to `owner`, then self-destructs. /// Only callable by `deployer` set in the contstructor. /// @param owner The owner of the contract. + /// @param features Features to bootstrap into the proxy. /// @return zeroEx The deployed and configured `ZeroEx` contract. - function deploy(address payable owner) public virtual returns (ZeroEx zeroEx) { + function deploy(address payable owner, BootstrapFeatures memory features) + public + virtual + returns (ZeroEx zeroEx) + { // Must be called by the allowed deployer. require(msg.sender == deployer, "InitialMigration/INVALID_SENDER"); @@ -58,7 +69,7 @@ contract InitialMigration { // Bootstrap the initial feature set. IBootstrap(address(zeroEx)).bootstrap( address(this), - abi.encodeWithSelector(this.bootstrap.selector, owner) + abi.encodeWithSelector(this.bootstrap.selector, owner, features) ); // Self-destruct. This contract should not hold any funds but we send @@ -69,23 +80,30 @@ contract InitialMigration { /// @dev Sets up the initial state of the `ZeroEx` contract. /// The `ZeroEx` contract will delegatecall into this function. /// @param owner The new owner of the ZeroEx contract. + /// @param features Features to bootstrap into the proxy. /// @return success Magic bytes if successful. - function bootstrap(address owner) public virtual returns (bytes4 success) { + function bootstrap(address owner, BootstrapFeatures memory features) + public + virtual + returns (bytes4 success) + { // Deploy and migrate the initial features. // Order matters here. // Initialize Registry. - SimpleFunctionRegistry registry = new SimpleFunctionRegistry(); LibBootstrap.delegatecallBootstrapFunction( - address(registry), - abi.encodeWithSelector(registry.bootstrap.selector, address(registry)) + address(features.registry), + abi.encodeWithSelector( + SimpleFunctionRegistry.bootstrap.selector + ) ); // Initialize Ownable. - Ownable ownable = new Ownable(); LibBootstrap.delegatecallBootstrapFunction( - address(ownable), - abi.encodeWithSelector(ownable.bootstrap.selector, address(ownable)) + address(features.ownable), + abi.encodeWithSelector( + Ownable.bootstrap.selector + ) ); // Transfer ownership to the real owner. diff --git a/contracts/zero-ex/contracts/src/storage/LibStorage.sol b/contracts/zero-ex/contracts/src/storage/LibStorage.sol index e6ae3bbd6d..9f0aa15c22 100644 --- a/contracts/zero-ex/contracts/src/storage/LibStorage.sol +++ b/contracts/zero-ex/contracts/src/storage/LibStorage.sol @@ -28,10 +28,13 @@ library LibStorage { uint256 private constant STORAGE_SLOT_EXP = 128; /// @dev Storage IDs for feature storage buckets. + /// WARNING: APPEND-ONLY. enum StorageId { Proxy, SimpleFunctionRegistry, - Ownable + Ownable, + TokenSpender, + TransformERC20 } /// @dev Get the storage slot given a storage ID. We assign unique, well-spaced diff --git a/contracts/zero-ex/contracts/src/storage/LibTokenSpenderStorage.sol b/contracts/zero-ex/contracts/src/storage/LibTokenSpenderStorage.sol new file mode 100644 index 0000000000..b9848e05cb --- /dev/null +++ b/contracts/zero-ex/contracts/src/storage/LibTokenSpenderStorage.sol @@ -0,0 +1,42 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + +import "./LibStorage.sol"; +import "../external/IAllowanceTarget.sol"; + + +/// @dev Storage helpers for the `TokenSpender` feature. +library LibTokenSpenderStorage { + + /// @dev Storage bucket for this feature. + struct Storage { + // Allowance target contract. + IAllowanceTarget allowanceTarget; + } + + /// @dev Get the storage bucket for this contract. + function getStorage() internal pure returns (Storage storage stor) { + uint256 storageSlot = LibStorage.getStorageSlot( + LibStorage.StorageId.TokenSpender + ); + assembly { stor_slot := storageSlot } + } +} diff --git a/contracts/zero-ex/contracts/src/storage/LibTransformERC20Storage.sol b/contracts/zero-ex/contracts/src/storage/LibTransformERC20Storage.sol new file mode 100644 index 0000000000..9472c24f56 --- /dev/null +++ b/contracts/zero-ex/contracts/src/storage/LibTransformERC20Storage.sol @@ -0,0 +1,42 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + +import "./LibStorage.sol"; +import "../external/IFlashWallet.sol"; + + +/// @dev Storage helpers for the `TokenSpender` feature. +library LibTransformERC20Storage { + + /// @dev Storage bucket for this feature. + struct Storage { + // The current wallet instance. + IFlashWallet wallet; + } + + /// @dev Get the storage bucket for this contract. + function getStorage() internal pure returns (Storage storage stor) { + uint256 storageSlot = LibStorage.getStorageSlot( + LibStorage.StorageId.TransformERC20 + ); + assembly { stor_slot := storageSlot } + } +} diff --git a/contracts/zero-ex/contracts/src/transformers/IERC20Transformer.sol b/contracts/zero-ex/contracts/src/transformers/IERC20Transformer.sol new file mode 100644 index 0000000000..217ca41355 --- /dev/null +++ b/contracts/zero-ex/contracts/src/transformers/IERC20Transformer.sol @@ -0,0 +1,43 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; + + +/// @dev A transformation callback used in `TransformERC20.transformERC20()`. +interface IERC20Transformer { + + /// @dev Called from `TransformERC20.transformERC20()`. This will be + /// delegatecalled in the context of the FlashWallet instance being used. + /// @param callDataHash The hash of the `TransformERC20.transformERC20()` calldata. + /// @param taker The taker address (caller of `TransformERC20.transformERC20()`). + /// @param data Arbitrary data to pass to the transformer. + /// @return rlpDeploymentNonce RLP-encoded deployment nonce of the deployer + /// when this transformer was deployed. This is used to verify that + /// this transformer was deployed by a trusted contract. + function transform( + bytes32 callDataHash, + address payable taker, + bytes calldata data + ) + external + returns (bytes memory rlpDeploymentNonce); +} diff --git a/contracts/zero-ex/contracts/src/transformers/LibERC20Transformer.sol b/contracts/zero-ex/contracts/src/transformers/LibERC20Transformer.sol new file mode 100644 index 0000000000..1753cc9c68 --- /dev/null +++ b/contracts/zero-ex/contracts/src/transformers/LibERC20Transformer.sol @@ -0,0 +1,73 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; +import "@0x/contracts-erc20/contracts/src/v06/LibERC20TokenV06.sol"; + + +library LibERC20Transformer { + + /// @dev ETH pseudo-token address. + address constant internal ETH_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + + using LibERC20TokenV06 for IERC20TokenV06; + + /// @dev Transfer ERC20 tokens and ETH. + /// @param token An ERC20 or the ETH pseudo-token address (`ETH_TOKEN_ADDRESS`). + /// @param to The recipient. + /// @param amount The transfer amount. + function transformerTransfer( + IERC20TokenV06 token, + address payable to, + uint256 amount + ) + internal + { + if (isTokenETH(token)) { + to.transfer(amount); + } else { + token.compatTransfer(to, amount); + } + } + + /// @dev Check if a token is the ETH pseudo-token. + /// @param token The token to check. + /// @return isETH `true` if the token is the ETH pseudo-token. + function isTokenETH(IERC20TokenV06 token) + internal + pure + returns (bool isETH) + { + return address(token) == ETH_TOKEN_ADDRESS; + } + + /// @dev Check the balance of an ERC20 token or ETH. + /// @param token An ERC20 or the ETH pseudo-token address (`ETH_TOKEN_ADDRESS`). + /// @param owner Holder of the tokens. + /// @return tokenBalance The balance of `owner`. + function getTokenBalanceOf(IERC20TokenV06 token, address owner) + internal + view + returns (uint256 tokenBalance) + { + return isTokenETH(token) ? owner.balance : token.balanceOf(owner); + } +} diff --git a/contracts/zero-ex/contracts/test/TestCallTarget.sol b/contracts/zero-ex/contracts/test/TestCallTarget.sol new file mode 100644 index 0000000000..097c199006 --- /dev/null +++ b/contracts/zero-ex/contracts/test/TestCallTarget.sol @@ -0,0 +1,51 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + + +contract TestCallTarget { + + event CallTargetCalled( + address context, + address sender, + bytes data, + uint256 value + ); + + bytes4 private constant MAGIC_BYTES = 0x12345678; + bytes private constant REVERTING_DATA = hex"1337"; + + fallback() external payable { + if (keccak256(msg.data) == keccak256(REVERTING_DATA)) { + revert("TestCallTarget/REVERT"); + } + emit CallTargetCalled( + address(this), + msg.sender, + msg.data, + msg.value + ); + bytes4 rval = MAGIC_BYTES; + assembly { + mstore(0, rval) + return(0, 32) + } + } +} diff --git a/contracts/zero-ex/contracts/test/TestFullMigration.sol b/contracts/zero-ex/contracts/test/TestFullMigration.sol new file mode 100644 index 0000000000..6777af099c --- /dev/null +++ b/contracts/zero-ex/contracts/test/TestFullMigration.sol @@ -0,0 +1,38 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + +import "../src/ZeroEx.sol"; +import "../src/features/IBootstrap.sol"; +import "../src/migrations/FullMigration.sol"; + + +contract TestFullMigration is + FullMigration +{ + address public dieRecipient; + + // solhint-disable-next-line no-empty-blocks + constructor(address payable deployer) public FullMigration(deployer) {} + + function die(address payable ethRecipient) external override { + dieRecipient = ethRecipient; + } +} diff --git a/contracts/zero-ex/contracts/test/TestInitialMigration.sol b/contracts/zero-ex/contracts/test/TestInitialMigration.sol index dc4c31c747..2f57d731f5 100644 --- a/contracts/zero-ex/contracts/test/TestInitialMigration.sol +++ b/contracts/zero-ex/contracts/test/TestInitialMigration.sol @@ -37,12 +37,12 @@ contract TestInitialMigration is IBootstrap(address(zeroEx)).bootstrap(address(this), new bytes(0)); } - function getCodeSizeOf(address target) external view returns (uint256 codeSize) { - assembly { codeSize := extcodesize(target) } - } - - function bootstrap(address owner) public override returns (bytes4 success) { - success = InitialMigration.bootstrap(owner); + function bootstrap(address owner, BootstrapFeatures memory features) + public + override + returns (bytes4 success) + { + success = InitialMigration.bootstrap(owner, features); // Snoop the bootstrap feature contract. bootstrapFeature = ZeroEx(address(uint160(address(this)))) .getFunctionImplementation(IBootstrap.bootstrap.selector); diff --git a/contracts/zero-ex/contracts/test/TestMintTokenERC20Transformer.sol b/contracts/zero-ex/contracts/test/TestMintTokenERC20Transformer.sol new file mode 100644 index 0000000000..6ff386f45e --- /dev/null +++ b/contracts/zero-ex/contracts/test/TestMintTokenERC20Transformer.sol @@ -0,0 +1,84 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-erc20/contracts/src/v06/IERC20TokenV06.sol"; +import "../src/transformers/IERC20Transformer.sol"; +import "../src/transformers/LibERC20Transformer.sol"; +import "./TestMintableERC20Token.sol"; + + +contract TestMintTokenERC20Transformer is + IERC20Transformer +{ + struct TransformData { + IERC20TokenV06 inputToken; + TestMintableERC20Token outputToken; + uint256 burnAmount; + uint256 mintAmount; + uint256 feeAmount; + bytes deploymentNonce; + } + + event MintTransform( + address context, + address caller, + bytes32 callDataHash, + address taker, + bytes data, + uint256 inputTokenBalance, + uint256 ethBalance + ); + + function transform( + bytes32 callDataHash, + address payable taker, + bytes calldata data_ + ) + external + override + returns (bytes memory rlpDeploymentNonce) + { + TransformData memory data = abi.decode(data_, (TransformData)); + emit MintTransform( + address(this), + msg.sender, + callDataHash, + taker, + data_, + data.inputToken.balanceOf(address(this)), + address(this).balance + ); + // "Burn" input tokens. + data.inputToken.transfer(address(0), data.burnAmount); + // Mint output tokens. + if (LibERC20Transformer.isTokenETH(IERC20TokenV06(address(data.outputToken)))) { + taker.transfer(data.mintAmount); + } else { + data.outputToken.mint( + taker, + data.mintAmount + ); + // Burn fees from output. + data.outputToken.burn(taker, data.feeAmount); + } + return data.deploymentNonce; + } +} diff --git a/contracts/zero-ex/contracts/test/TestMintableERC20Token.sol b/contracts/zero-ex/contracts/test/TestMintableERC20Token.sol new file mode 100644 index 0000000000..27ef1209fd --- /dev/null +++ b/contracts/zero-ex/contracts/test/TestMintableERC20Token.sol @@ -0,0 +1,77 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + + +contract TestMintableERC20Token { + + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + function transfer(address to, uint256 amount) + external + virtual + returns (bool) + { + return transferFrom(msg.sender, to, amount); + } + + function approve(address spender, uint256 amount) + external + virtual + returns (bool) + { + allowance[msg.sender][spender] = amount; + return true; + } + + function mint(address owner, uint256 amount) + external + virtual + { + balanceOf[owner] += amount; + } + + function burn(address owner, uint256 amount) + external + virtual + { + require(balanceOf[owner] >= amount, "TestMintableERC20Token/INSUFFICIENT_FUNDS"); + balanceOf[owner] -= amount; + } + + function transferFrom(address from, address to, uint256 amount) + public + virtual + returns (bool) + { + if (from != msg.sender) { + require( + allowance[from][msg.sender] >= amount, + "TestMintableERC20Token/INSUFFICIENT_ALLOWANCE" + ); + allowance[from][msg.sender] -= amount; + } + require(balanceOf[from] >= amount, "TestMintableERC20Token/INSUFFICIENT_FUNDS"); + balanceOf[from] -= amount; + balanceOf[to] += amount; + return true; + } +} diff --git a/contracts/zero-ex/contracts/test/TestTokenSpender.sol b/contracts/zero-ex/contracts/test/TestTokenSpender.sol new file mode 100644 index 0000000000..8213b0d503 --- /dev/null +++ b/contracts/zero-ex/contracts/test/TestTokenSpender.sol @@ -0,0 +1,30 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + +import "../src/features/TokenSpender.sol"; + +contract TestTokenSpender is + TokenSpender +{ + modifier onlySelf() override { + _; + } +} diff --git a/contracts/zero-ex/contracts/test/TestTokenSpenderERC20Token.sol b/contracts/zero-ex/contracts/test/TestTokenSpenderERC20Token.sol new file mode 100644 index 0000000000..86cd367c4b --- /dev/null +++ b/contracts/zero-ex/contracts/test/TestTokenSpenderERC20Token.sol @@ -0,0 +1,70 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + +import "./TestMintableERC20Token.sol"; + + +contract TestTokenSpenderERC20Token is + TestMintableERC20Token +{ + + event TransferFromCalled( + address sender, + address from, + address to, + uint256 amount + ); + + // `transferFrom()` behavior depends on the value of `amount`. + uint256 constant private EMPTY_RETURN_AMOUNT = 1337; + uint256 constant private FALSE_RETURN_AMOUNT = 1338; + uint256 constant private REVERT_RETURN_AMOUNT = 1339; + + function transferFrom(address from, address to, uint256 amount) + public + override + returns (bool) + { + emit TransferFromCalled(msg.sender, from, to, amount); + if (amount == EMPTY_RETURN_AMOUNT) { + assembly { return(0, 0) } + } + if (amount == FALSE_RETURN_AMOUNT) { + return false; + } + if (amount == REVERT_RETURN_AMOUNT) { + revert("TestTokenSpenderERC20Token/Revert"); + } + return true; + } + + function setBalanceAndAllowanceOf( + address owner, + uint256 balance, + address spender, + uint256 allowance_ + ) + external + { + balanceOf[owner] = balance; + allowance[owner][spender] = allowance_; + } +} diff --git a/contracts/zero-ex/contracts/test/TestTransformERC20.sol b/contracts/zero-ex/contracts/test/TestTransformERC20.sol new file mode 100644 index 0000000000..f7549cb1e4 --- /dev/null +++ b/contracts/zero-ex/contracts/test/TestTransformERC20.sol @@ -0,0 +1,37 @@ +/* + + Copyright 2020 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.6.5; +pragma experimental ABIEncoderV2; + +import "../src/features/TransformERC20.sol"; + + +contract TestTransformERC20 is + TransformERC20 +{ + // solhint-disable no-empty-blocks + constructor(address trustedDeployer) + TransformERC20(trustedDeployer) + public + {} + + modifier onlySelf() override { + _; + } +} diff --git a/contracts/zero-ex/package.json b/contracts/zero-ex/package.json index 132c48a16f..d1f05183e8 100644 --- a/contracts/zero-ex/package.json +++ b/contracts/zero-ex/package.json @@ -32,15 +32,15 @@ "test:circleci": "yarn test", "contracts:gen": "contracts-gen generate", "contracts:copy": "contracts-gen copy", - "lint-contracts": "solhint -c ../.solhint.json contracts/**/**/**/**/*.sol", + "lint-contracts": "#solhint -c ../.solhint.json contracts/**/**/**/**/*.sol", "compile:truffle": "truffle compile", "docs:md": "ts-doc-gen --sourceDir='$PROJECT_FILES' --output=$MD_FILE_DIR --fileExtension=mdx --tsconfig=./typedoc-tsconfig.json", "docs:json": "typedoc --excludePrivate --excludeExternals --excludeProtected --ignoreCompilerErrors --target ES5 --tsconfig typedoc-tsconfig.json --json $JSON_FILE_PATH $PROJECT_FILES" }, "config": { - "publicInterfaceContracts": "ZeroEx,IOwnable,ISimpleFunctionRegistry", + "publicInterfaceContracts": "ZeroEx,FullMigration,InitialMigration,IFlashWallet,IAllowanceTarget,IERC20Transformer,IOwnable,ISimpleFunctionRegistry,ITokenSpender,ITransformERC20", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually.", - "abis": "./test/generated-artifacts/@(Bootstrap|FixinCommon|IBootstrap|IFeature|IOwnable|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|InitialMigration|LibBootstrap|LibCommonRichErrors|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibStorage|Ownable|SimpleFunctionRegistry|TestInitialMigration|TestMigrator|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestZeroExFeature|ZeroEx).json" + "abis": "./test/generated-artifacts/@(AllowanceTarget|Bootstrap|FixinCommon|FlashWallet|FullMigration|IAllowanceTarget|IBootstrap|IERC20Transformer|IFeature|IFlashWallet|IOwnable|ISimpleFunctionRegistry|ITestSimpleFunctionRegistryFeature|ITokenSpender|ITransformERC20|InitialMigration|LibBootstrap|LibCommonRichErrors|LibERC20Transformer|LibMigrate|LibOwnableRichErrors|LibOwnableStorage|LibProxyRichErrors|LibProxyStorage|LibSimpleFunctionRegistryRichErrors|LibSimpleFunctionRegistryStorage|LibSpenderRichErrors|LibStorage|LibTokenSpenderStorage|LibTransformERC20RichErrors|LibTransformERC20Storage|LibWalletRichErrors|Ownable|SimpleFunctionRegistry|TestCallTarget|TestFullMigration|TestInitialMigration|TestMigrator|TestMintTokenERC20Transformer|TestMintableERC20Token|TestSimpleFunctionRegistryFeatureImpl1|TestSimpleFunctionRegistryFeatureImpl2|TestTokenSpender|TestTokenSpenderERC20Token|TestTransformERC20|TestZeroExFeature|TokenSpender|TransformERC20|ZeroEx).json" }, "repository": { "type": "git", @@ -62,6 +62,7 @@ "@0x/tslint-config": "^4.0.0", "@types/lodash": "4.14.104", "@types/mocha": "^5.2.7", + "lodash": "^4.17.11", "mocha": "^6.2.0", "npm-run-all": "^4.1.2", "shx": "^0.2.2", @@ -76,7 +77,9 @@ "@0x/types": "^3.1.2", "@0x/typescript-typings": "^5.0.2", "@0x/utils": "^5.4.1", - "ethereum-types": "^3.1.0" + "@0x/web3-wrapper": "^7.0.7", + "ethereum-types": "^3.1.0", + "ethereumjs-util": "^5.1.1" }, "publishConfig": { "access": "public" diff --git a/contracts/zero-ex/src/artifacts.ts b/contracts/zero-ex/src/artifacts.ts index aa3ab07a57..fea6a4beb1 100644 --- a/contracts/zero-ex/src/artifacts.ts +++ b/contracts/zero-ex/src/artifacts.ts @@ -5,11 +5,25 @@ */ import { ContractArtifact } from 'ethereum-types'; +import * as FullMigration from '../generated-artifacts/FullMigration.json'; +import * as IAllowanceTarget from '../generated-artifacts/IAllowanceTarget.json'; +import * as IERC20Transformer from '../generated-artifacts/IERC20Transformer.json'; +import * as IFlashWallet from '../generated-artifacts/IFlashWallet.json'; +import * as InitialMigration from '../generated-artifacts/InitialMigration.json'; import * as IOwnable from '../generated-artifacts/IOwnable.json'; import * as ISimpleFunctionRegistry from '../generated-artifacts/ISimpleFunctionRegistry.json'; +import * as ITokenSpender from '../generated-artifacts/ITokenSpender.json'; +import * as ITransformERC20 from '../generated-artifacts/ITransformERC20.json'; import * as ZeroEx from '../generated-artifacts/ZeroEx.json'; export const artifacts = { ZeroEx: ZeroEx as ContractArtifact, + FullMigration: FullMigration as ContractArtifact, + InitialMigration: InitialMigration as ContractArtifact, + IFlashWallet: IFlashWallet as ContractArtifact, + IAllowanceTarget: IAllowanceTarget as ContractArtifact, + IERC20Transformer: IERC20Transformer as ContractArtifact, IOwnable: IOwnable as ContractArtifact, ISimpleFunctionRegistry: ISimpleFunctionRegistry as ContractArtifact, + ITokenSpender: ITokenSpender as ContractArtifact, + ITransformERC20: ITransformERC20 as ContractArtifact, }; diff --git a/contracts/zero-ex/src/index.ts b/contracts/zero-ex/src/index.ts index 9e2650968f..b9e4913d08 100644 --- a/contracts/zero-ex/src/index.ts +++ b/contracts/zero-ex/src/index.ts @@ -36,3 +36,4 @@ export { TupleDataItem, StateMutability, } from 'ethereum-types'; +export { rlpEncodeNonce } from './nonce_utils'; diff --git a/contracts/zero-ex/src/nonce_utils.ts b/contracts/zero-ex/src/nonce_utils.ts new file mode 100644 index 0000000000..9290fab280 --- /dev/null +++ b/contracts/zero-ex/src/nonce_utils.ts @@ -0,0 +1,25 @@ +import { hexUtils } from '@0x/utils'; +import { Web3Wrapper } from '@0x/web3-wrapper'; +import * as ethjs from 'ethereumjs-util'; + +/** + * Fetch and RLP encode the transaction count (nonce) of an account. + */ +export async function getRLPEncodedAccountNonceAsync(web3Wrapper: Web3Wrapper, address: string): Promise { + const nonce = await web3Wrapper.getAccountNonceAsync(address); + return rlpEncodeNonce(nonce); +} + +/** + * RLP encode the transaction count (nonce) of an account. + */ +export function rlpEncodeNonce(nonce: number): string { + if (nonce === 0) { + return '0x80'; + } else if (nonce <= 0x7f) { + return ethjs.bufferToHex(ethjs.toBuffer(nonce)); + } else { + const rlpNonce = ethjs.bufferToHex(ethjs.toBuffer(nonce)); + return hexUtils.concat(rlpNonce.length + 0x80, rlpNonce); + } +} diff --git a/contracts/zero-ex/src/wrappers.ts b/contracts/zero-ex/src/wrappers.ts index b7b12cc29d..7be4d42f1e 100644 --- a/contracts/zero-ex/src/wrappers.ts +++ b/contracts/zero-ex/src/wrappers.ts @@ -3,6 +3,13 @@ * Warning: This file is auto-generated by contracts-gen. Don't edit manually. * ----------------------------------------------------------------------------- */ +export * from '../generated-wrappers/full_migration'; +export * from '../generated-wrappers/i_allowance_target'; +export * from '../generated-wrappers/i_erc20_transformer'; +export * from '../generated-wrappers/i_flash_wallet'; export * from '../generated-wrappers/i_ownable'; export * from '../generated-wrappers/i_simple_function_registry'; +export * from '../generated-wrappers/i_token_spender'; +export * from '../generated-wrappers/i_transform_erc20'; +export * from '../generated-wrappers/initial_migration'; export * from '../generated-wrappers/zero_ex'; diff --git a/contracts/zero-ex/test/allowance_target_test.ts b/contracts/zero-ex/test/allowance_target_test.ts new file mode 100644 index 0000000000..270a1bfb63 --- /dev/null +++ b/contracts/zero-ex/test/allowance_target_test.ts @@ -0,0 +1,82 @@ +import { blockchainTests, constants, expect, randomAddress, verifyEventsFromLogs } from '@0x/contracts-test-utils'; +import { AuthorizableRevertErrors, hexUtils, StringRevertError } from '@0x/utils'; + +import { artifacts } from './artifacts'; +import { AllowanceTargetContract, TestCallTargetContract, TestCallTargetEvents } from './wrappers'; + +blockchainTests.resets('AllowanceTarget', env => { + let owner: string; + let authority: string; + let allowanceTarget: AllowanceTargetContract; + let callTarget: TestCallTargetContract; + + before(async () => { + [owner, authority] = await env.getAccountAddressesAsync(); + allowanceTarget = await AllowanceTargetContract.deployFrom0xArtifactAsync( + artifacts.AllowanceTarget, + env.provider, + env.txDefaults, + artifacts, + ); + await allowanceTarget.addAuthorizedAddress(authority).awaitTransactionSuccessAsync(); + callTarget = await TestCallTargetContract.deployFrom0xArtifactAsync( + artifacts.TestCallTarget, + env.provider, + env.txDefaults, + artifacts, + ); + }); + + const TARGET_RETURN_VALUE = hexUtils.rightPad('0x12345678'); + const REVERTING_DATA = '0x1337'; + + describe('executeCall()', () => { + it('non-authority cannot call executeCall()', async () => { + const notAuthority = randomAddress(); + const tx = allowanceTarget + .executeCall(randomAddress(), hexUtils.random()) + .callAsync({ from: notAuthority }); + return expect(tx).to.revertWith(new AuthorizableRevertErrors.SenderNotAuthorizedError(notAuthority)); + }); + + it('authority can call executeCall()', async () => { + const targetData = hexUtils.random(128); + const receipt = await allowanceTarget + .executeCall(callTarget.address, targetData) + .awaitTransactionSuccessAsync({ from: authority }); + verifyEventsFromLogs( + receipt.logs, + [ + { + context: callTarget.address, + sender: allowanceTarget.address, + data: targetData, + value: constants.ZERO_AMOUNT, + }, + ], + TestCallTargetEvents.CallTargetCalled, + ); + }); + + it('AllowanceTarget returns call result', async () => { + const result = await allowanceTarget + .executeCall(callTarget.address, hexUtils.random(128)) + .callAsync({ from: authority }); + expect(result).to.eq(TARGET_RETURN_VALUE); + }); + + it('AllowanceTarget returns raw call revert', async () => { + const tx = allowanceTarget.executeCall(callTarget.address, REVERTING_DATA).callAsync({ from: authority }); + return expect(tx).to.revertWith(new StringRevertError('TestCallTarget/REVERT')); + }); + + it('AllowanceTarget cannot receive ETH', async () => { + const tx = env.web3Wrapper.sendTransactionAsync({ + to: allowanceTarget.address, + from: owner, + value: 0, + }); + return expect(tx).to.eventually.be.rejected(); + }); + }); +}); diff --git a/contracts/zero-ex/test/artifacts.ts b/contracts/zero-ex/test/artifacts.ts index 19e3f584f0..cf9c020daa 100644 --- a/contracts/zero-ex/test/artifacts.ts +++ b/contracts/zero-ex/test/artifacts.ts @@ -5,16 +5,25 @@ */ import { ContractArtifact } from 'ethereum-types'; +import * as AllowanceTarget from '../test/generated-artifacts/AllowanceTarget.json'; import * as Bootstrap from '../test/generated-artifacts/Bootstrap.json'; import * as FixinCommon from '../test/generated-artifacts/FixinCommon.json'; +import * as FlashWallet from '../test/generated-artifacts/FlashWallet.json'; +import * as FullMigration from '../test/generated-artifacts/FullMigration.json'; +import * as IAllowanceTarget from '../test/generated-artifacts/IAllowanceTarget.json'; import * as IBootstrap from '../test/generated-artifacts/IBootstrap.json'; +import * as IERC20Transformer from '../test/generated-artifacts/IERC20Transformer.json'; import * as IFeature from '../test/generated-artifacts/IFeature.json'; +import * as IFlashWallet from '../test/generated-artifacts/IFlashWallet.json'; import * as InitialMigration from '../test/generated-artifacts/InitialMigration.json'; import * as IOwnable from '../test/generated-artifacts/IOwnable.json'; import * as ISimpleFunctionRegistry from '../test/generated-artifacts/ISimpleFunctionRegistry.json'; import * as ITestSimpleFunctionRegistryFeature from '../test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json'; +import * as ITokenSpender from '../test/generated-artifacts/ITokenSpender.json'; +import * as ITransformERC20 from '../test/generated-artifacts/ITransformERC20.json'; import * as LibBootstrap from '../test/generated-artifacts/LibBootstrap.json'; import * as LibCommonRichErrors from '../test/generated-artifacts/LibCommonRichErrors.json'; +import * as LibERC20Transformer from '../test/generated-artifacts/LibERC20Transformer.json'; import * as LibMigrate from '../test/generated-artifacts/LibMigrate.json'; import * as LibOwnableRichErrors from '../test/generated-artifacts/LibOwnableRichErrors.json'; import * as LibOwnableStorage from '../test/generated-artifacts/LibOwnableStorage.json'; @@ -22,14 +31,28 @@ import * as LibProxyRichErrors from '../test/generated-artifacts/LibProxyRichErr import * as LibProxyStorage from '../test/generated-artifacts/LibProxyStorage.json'; import * as LibSimpleFunctionRegistryRichErrors from '../test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json'; import * as LibSimpleFunctionRegistryStorage from '../test/generated-artifacts/LibSimpleFunctionRegistryStorage.json'; +import * as LibSpenderRichErrors from '../test/generated-artifacts/LibSpenderRichErrors.json'; import * as LibStorage from '../test/generated-artifacts/LibStorage.json'; +import * as LibTokenSpenderStorage from '../test/generated-artifacts/LibTokenSpenderStorage.json'; +import * as LibTransformERC20RichErrors from '../test/generated-artifacts/LibTransformERC20RichErrors.json'; +import * as LibTransformERC20Storage from '../test/generated-artifacts/LibTransformERC20Storage.json'; +import * as LibWalletRichErrors from '../test/generated-artifacts/LibWalletRichErrors.json'; import * as Ownable from '../test/generated-artifacts/Ownable.json'; import * as SimpleFunctionRegistry from '../test/generated-artifacts/SimpleFunctionRegistry.json'; +import * as TestCallTarget from '../test/generated-artifacts/TestCallTarget.json'; +import * as TestFullMigration from '../test/generated-artifacts/TestFullMigration.json'; import * as TestInitialMigration from '../test/generated-artifacts/TestInitialMigration.json'; import * as TestMigrator from '../test/generated-artifacts/TestMigrator.json'; +import * as TestMintableERC20Token from '../test/generated-artifacts/TestMintableERC20Token.json'; +import * as TestMintTokenERC20Transformer from '../test/generated-artifacts/TestMintTokenERC20Transformer.json'; import * as TestSimpleFunctionRegistryFeatureImpl1 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl1.json'; import * as TestSimpleFunctionRegistryFeatureImpl2 from '../test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl2.json'; +import * as TestTokenSpender from '../test/generated-artifacts/TestTokenSpender.json'; +import * as TestTokenSpenderERC20Token from '../test/generated-artifacts/TestTokenSpenderERC20Token.json'; +import * as TestTransformERC20 from '../test/generated-artifacts/TestTransformERC20.json'; import * as TestZeroExFeature from '../test/generated-artifacts/TestZeroExFeature.json'; +import * as TokenSpender from '../test/generated-artifacts/TokenSpender.json'; +import * as TransformERC20 from '../test/generated-artifacts/TransformERC20.json'; import * as ZeroEx from '../test/generated-artifacts/ZeroEx.json'; export const artifacts = { ZeroEx: ZeroEx as ContractArtifact, @@ -37,14 +60,26 @@ export const artifacts = { LibOwnableRichErrors: LibOwnableRichErrors as ContractArtifact, LibProxyRichErrors: LibProxyRichErrors as ContractArtifact, LibSimpleFunctionRegistryRichErrors: LibSimpleFunctionRegistryRichErrors as ContractArtifact, + LibSpenderRichErrors: LibSpenderRichErrors as ContractArtifact, + LibTransformERC20RichErrors: LibTransformERC20RichErrors as ContractArtifact, + LibWalletRichErrors: LibWalletRichErrors as ContractArtifact, + AllowanceTarget: AllowanceTarget as ContractArtifact, + FlashWallet: FlashWallet as ContractArtifact, + IAllowanceTarget: IAllowanceTarget as ContractArtifact, + IFlashWallet: IFlashWallet as ContractArtifact, Bootstrap: Bootstrap as ContractArtifact, IBootstrap: IBootstrap as ContractArtifact, IFeature: IFeature as ContractArtifact, IOwnable: IOwnable as ContractArtifact, ISimpleFunctionRegistry: ISimpleFunctionRegistry as ContractArtifact, + ITokenSpender: ITokenSpender as ContractArtifact, + ITransformERC20: ITransformERC20 as ContractArtifact, Ownable: Ownable as ContractArtifact, SimpleFunctionRegistry: SimpleFunctionRegistry as ContractArtifact, + TokenSpender: TokenSpender as ContractArtifact, + TransformERC20: TransformERC20 as ContractArtifact, FixinCommon: FixinCommon as ContractArtifact, + FullMigration: FullMigration as ContractArtifact, InitialMigration: InitialMigration as ContractArtifact, LibBootstrap: LibBootstrap as ContractArtifact, LibMigrate: LibMigrate as ContractArtifact, @@ -52,10 +87,21 @@ export const artifacts = { LibProxyStorage: LibProxyStorage as ContractArtifact, LibSimpleFunctionRegistryStorage: LibSimpleFunctionRegistryStorage as ContractArtifact, LibStorage: LibStorage as ContractArtifact, + LibTokenSpenderStorage: LibTokenSpenderStorage as ContractArtifact, + LibTransformERC20Storage: LibTransformERC20Storage as ContractArtifact, + IERC20Transformer: IERC20Transformer as ContractArtifact, + LibERC20Transformer: LibERC20Transformer as ContractArtifact, ITestSimpleFunctionRegistryFeature: ITestSimpleFunctionRegistryFeature as ContractArtifact, + TestCallTarget: TestCallTarget as ContractArtifact, + TestFullMigration: TestFullMigration as ContractArtifact, TestInitialMigration: TestInitialMigration as ContractArtifact, TestMigrator: TestMigrator as ContractArtifact, + TestMintTokenERC20Transformer: TestMintTokenERC20Transformer as ContractArtifact, + TestMintableERC20Token: TestMintableERC20Token as ContractArtifact, TestSimpleFunctionRegistryFeatureImpl1: TestSimpleFunctionRegistryFeatureImpl1 as ContractArtifact, TestSimpleFunctionRegistryFeatureImpl2: TestSimpleFunctionRegistryFeatureImpl2 as ContractArtifact, + TestTokenSpender: TestTokenSpender as ContractArtifact, + TestTokenSpenderERC20Token: TestTokenSpenderERC20Token as ContractArtifact, + TestTransformERC20: TestTransformERC20 as ContractArtifact, TestZeroExFeature: TestZeroExFeature as ContractArtifact, }; diff --git a/contracts/zero-ex/test/features/token_spender_test.ts b/contracts/zero-ex/test/features/token_spender_test.ts new file mode 100644 index 0000000000..13470fd8e9 --- /dev/null +++ b/contracts/zero-ex/test/features/token_spender_test.ts @@ -0,0 +1,141 @@ +import { + blockchainTests, + expect, + getRandomInteger, + randomAddress, + verifyEventsFromLogs, +} from '@0x/contracts-test-utils'; +import { BigNumber, hexUtils, StringRevertError, ZeroExRevertErrors } from '@0x/utils'; + +import { artifacts } from '../artifacts'; +import { abis } from '../utils/abis'; +import { fullMigrateAsync } from '../utils/migration'; +import { + TestTokenSpenderERC20TokenContract, + TestTokenSpenderERC20TokenEvents, + TokenSpenderContract, + ZeroExContract, +} from '../wrappers'; + +blockchainTests.resets('TokenSpender feature', env => { + let zeroEx: ZeroExContract; + let feature: TokenSpenderContract; + let token: TestTokenSpenderERC20TokenContract; + let allowanceTarget: string; + + before(async () => { + const [owner] = await env.getAccountAddressesAsync(); + zeroEx = await fullMigrateAsync(owner, env.provider, env.txDefaults, { + tokenSpender: await TokenSpenderContract.deployFrom0xArtifactAsync( + artifacts.TestTokenSpender, + env.provider, + env.txDefaults, + artifacts, + ), + }); + feature = new TokenSpenderContract(zeroEx.address, env.provider, env.txDefaults, abis); + token = await TestTokenSpenderERC20TokenContract.deployFrom0xArtifactAsync( + artifacts.TestTokenSpenderERC20Token, + env.provider, + env.txDefaults, + artifacts, + ); + allowanceTarget = await feature.getAllowanceTarget().callAsync(); + }); + + describe('_spendERC20Tokens()', () => { + const EMPTY_RETURN_AMOUNT = 1337; + const FALSE_RETURN_AMOUNT = 1338; + const REVERT_RETURN_AMOUNT = 1339; + + it('_spendERC20Tokens() successfully calls compliant ERC20 token', async () => { + const tokenFrom = randomAddress(); + const tokenTo = randomAddress(); + const tokenAmount = new BigNumber(123456); + const receipt = await feature + ._spendERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount) + .awaitTransactionSuccessAsync(); + verifyEventsFromLogs( + receipt.logs, + [ + { + sender: allowanceTarget, + from: tokenFrom, + to: tokenTo, + amount: tokenAmount, + }, + ], + TestTokenSpenderERC20TokenEvents.TransferFromCalled, + ); + }); + + it('_spendERC20Tokens() successfully calls non-compliant ERC20 token', async () => { + const tokenFrom = randomAddress(); + const tokenTo = randomAddress(); + const tokenAmount = new BigNumber(EMPTY_RETURN_AMOUNT); + const receipt = await feature + ._spendERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount) + .awaitTransactionSuccessAsync(); + verifyEventsFromLogs( + receipt.logs, + [ + { + sender: allowanceTarget, + from: tokenFrom, + to: tokenTo, + amount: tokenAmount, + }, + ], + TestTokenSpenderERC20TokenEvents.TransferFromCalled, + ); + }); + + it('_spendERC20Tokens() reverts if ERC20 token reverts', async () => { + const tokenFrom = randomAddress(); + const tokenTo = randomAddress(); + const tokenAmount = new BigNumber(REVERT_RETURN_AMOUNT); + const tx = feature + ._spendERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount) + .awaitTransactionSuccessAsync(); + const expectedError = new ZeroExRevertErrors.Spender.SpenderERC20TransferFromFailedError( + token.address, + tokenFrom, + tokenTo, + tokenAmount, + new StringRevertError('TestTokenSpenderERC20Token/Revert').encode(), + ); + return expect(tx).to.revertWith(expectedError); + }); + + it('_spendERC20Tokens() reverts if ERC20 token returns false', async () => { + const tokenFrom = randomAddress(); + const tokenTo = randomAddress(); + const tokenAmount = new BigNumber(FALSE_RETURN_AMOUNT); + const tx = feature + ._spendERC20Tokens(token.address, tokenFrom, tokenTo, tokenAmount) + .awaitTransactionSuccessAsync(); + return expect(tx).to.revertWith( + new ZeroExRevertErrors.Spender.SpenderERC20TransferFromFailedError( + token.address, + tokenFrom, + tokenTo, + tokenAmount, + hexUtils.leftPad(0), + ), + ); + }); + }); + + describe('getSpendableERC20BalanceOf()', () => { + it("returns the minimum of the owner's balance and allowance", async () => { + const balance = getRandomInteger(1, '1e18'); + const allowance = getRandomInteger(1, '1e18'); + const tokenOwner = randomAddress(); + await token + .setBalanceAndAllowanceOf(tokenOwner, balance, allowanceTarget, allowance) + .awaitTransactionSuccessAsync(); + const spendableBalance = await feature.getSpendableERC20BalanceOf(token.address, tokenOwner).callAsync(); + expect(spendableBalance).to.bignumber.eq(BigNumber.min(balance, allowance)); + }); + }); +}); diff --git a/contracts/zero-ex/test/features/transform_erc20_test.ts b/contracts/zero-ex/test/features/transform_erc20_test.ts new file mode 100644 index 0000000000..28daa667f0 --- /dev/null +++ b/contracts/zero-ex/test/features/transform_erc20_test.ts @@ -0,0 +1,526 @@ +import { + blockchainTests, + constants, + expect, + getRandomInteger, + getRandomPortion, + Numberish, + randomAddress, + verifyEventsFromLogs, +} from '@0x/contracts-test-utils'; +import { AbiEncoder, hexUtils, ZeroExRevertErrors } from '@0x/utils'; + +import { getRLPEncodedAccountNonceAsync } from '../../src/nonce_utils'; +import { artifacts } from '../artifacts'; +import { abis } from '../utils/abis'; +import { fullMigrateAsync } from '../utils/migration'; +import { + FlashWalletContract, + ITokenSpenderContract, + TestMintableERC20TokenContract, + TestMintTokenERC20TransformerContract, + TestMintTokenERC20TransformerEvents, + TransformERC20Contract, + TransformERC20Events, + ZeroExContract, +} from '../wrappers'; + +blockchainTests.resets('TransformERC20 feature', env => { + let taker: string; + let transformerDeployer: string; + let zeroEx: ZeroExContract; + let feature: TransformERC20Contract; + let wallet: FlashWalletContract; + let allowanceTarget: string; + + before(async () => { + let owner; + [owner, taker, transformerDeployer] = await env.getAccountAddressesAsync(); + zeroEx = await fullMigrateAsync(owner, env.provider, env.txDefaults, { + transformERC20: await TransformERC20Contract.deployFrom0xArtifactAsync( + artifacts.TestTransformERC20, + env.provider, + env.txDefaults, + artifacts, + transformerDeployer, + ), + }); + feature = new TransformERC20Contract(zeroEx.address, env.provider, env.txDefaults, abis); + wallet = new FlashWalletContract(await feature.getTransformWallet().callAsync(), env.provider, env.txDefaults); + allowanceTarget = await new ITokenSpenderContract(zeroEx.address, env.provider, env.txDefaults) + .getAllowanceTarget() + .callAsync(); + }); + + const { MAX_UINT256, NULL_BYTES, ZERO_AMOUNT } = constants; + + describe('wallets', () => { + it('createTransformWallet() replaces the current wallet', async () => { + const newWalletAddress = await feature.createTransformWallet().callAsync(); + expect(newWalletAddress).to.not.eq(wallet.address); + await feature.createTransformWallet().awaitTransactionSuccessAsync(); + return expect(feature.getTransformWallet().callAsync()).to.eventually.eq(newWalletAddress); + }); + }); + + describe('_transformERC20()', () => { + let inputToken: TestMintableERC20TokenContract; + let outputToken: TestMintableERC20TokenContract; + let mintTransformer: TestMintTokenERC20TransformerContract; + let rlpNonce: string; + + before(async () => { + inputToken = await TestMintableERC20TokenContract.deployFrom0xArtifactAsync( + artifacts.TestMintableERC20Token, + env.provider, + env.txDefaults, + artifacts, + ); + outputToken = await TestMintableERC20TokenContract.deployFrom0xArtifactAsync( + artifacts.TestMintableERC20Token, + env.provider, + env.txDefaults, + artifacts, + ); + rlpNonce = await getRLPEncodedAccountNonceAsync(env.web3Wrapper, transformerDeployer); + mintTransformer = await TestMintTokenERC20TransformerContract.deployFrom0xArtifactAsync( + artifacts.TestMintTokenERC20Transformer, + env.provider, + { + ...env.txDefaults, + from: transformerDeployer, + }, + artifacts, + ); + await inputToken.approve(allowanceTarget, MAX_UINT256).awaitTransactionSuccessAsync({ from: taker }); + }); + + interface Transformation { + transformer: string; + data: string; + } + + const transformDataEncoder = AbiEncoder.create([ + { + name: 'data', + type: 'tuple', + components: [ + { name: 'inputToken', type: 'address' }, + { name: 'outputToken', type: 'address' }, + { name: 'burnAmount', type: 'uint256' }, + { name: 'mintAmount', type: 'uint256' }, + { name: 'feeAmount', type: 'uint256' }, + { name: 'deploymentNonce', type: 'bytes' }, + ], + }, + ]); + + function createMintTokenTransformation( + opts: Partial<{ + transformer: string; + outputTokenAddress: string; + inputTokenAddress: string; + inputTokenBurnAmunt: Numberish; + outputTokenMintAmount: Numberish; + outputTokenFeeAmount: Numberish; + rlpNonce: string; + }> = {}, + ): Transformation { + const _opts = { + rlpNonce, + outputTokenAddress: outputToken.address, + inputTokenAddress: inputToken.address, + inputTokenBurnAmunt: ZERO_AMOUNT, + outputTokenMintAmount: ZERO_AMOUNT, + outputTokenFeeAmount: ZERO_AMOUNT, + transformer: mintTransformer.address, + ...opts, + }; + return { + transformer: _opts.transformer, + data: transformDataEncoder.encode([ + { + inputToken: _opts.inputTokenAddress, + outputToken: _opts.outputTokenAddress, + burnAmount: _opts.inputTokenBurnAmunt, + mintAmount: _opts.outputTokenMintAmount, + feeAmount: _opts.outputTokenFeeAmount, + deploymentNonce: _opts.rlpNonce, + }, + ]), + }; + } + + it("succeeds if taker's output token balance increases by exactly minOutputTokenAmount", async () => { + const startingOutputTokenBalance = getRandomInteger(0, '100e18'); + const startingInputTokenBalance = getRandomInteger(0, '100e18'); + await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync(); + await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync(); + const inputTokenAmount = getRandomPortion(startingInputTokenBalance); + const minOutputTokenAmount = getRandomInteger(1, '1e18'); + const outputTokenMintAmount = minOutputTokenAmount; + const callValue = getRandomInteger(1, '1e18'); + const callDataHash = hexUtils.random(); + const transformation = createMintTokenTransformation({ + outputTokenMintAmount, + inputTokenBurnAmunt: inputTokenAmount, + }); + const receipt = await feature + ._transformERC20( + callDataHash, + taker, + inputToken.address, + outputToken.address, + inputTokenAmount, + minOutputTokenAmount, + [transformation], + ) + .awaitTransactionSuccessAsync({ value: callValue }); + verifyEventsFromLogs( + receipt.logs, + [ + { + taker, + inputTokenAmount, + outputTokenAmount: outputTokenMintAmount, + inputToken: inputToken.address, + outputToken: outputToken.address, + }, + ], + TransformERC20Events.TransformedERC20, + ); + verifyEventsFromLogs( + receipt.logs, + [ + { + callDataHash, + taker, + context: wallet.address, + caller: zeroEx.address, + data: transformation.data, + inputTokenBalance: inputTokenAmount, + ethBalance: callValue, + }, + ], + TestMintTokenERC20TransformerEvents.MintTransform, + ); + }); + + const ETH_TOKEN_ADDRESS = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'; + + it("succeeds if taker's output token balance increases by exactly minOutputTokenAmount, with ETH", async () => { + const startingInputTokenBalance = getRandomInteger(0, '100e18'); + await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync(); + const inputTokenAmount = getRandomPortion(startingInputTokenBalance); + const minOutputTokenAmount = getRandomInteger(1, '1e18'); + const outputTokenMintAmount = minOutputTokenAmount; + const callValue = outputTokenMintAmount.times(2); + const callDataHash = hexUtils.random(); + const transformation = createMintTokenTransformation({ + outputTokenMintAmount, + inputTokenBurnAmunt: inputTokenAmount, + outputTokenAddress: ETH_TOKEN_ADDRESS, + }); + const startingOutputTokenBalance = await env.web3Wrapper.getBalanceInWeiAsync(taker); + const receipt = await feature + ._transformERC20( + callDataHash, + taker, + inputToken.address, + ETH_TOKEN_ADDRESS, + inputTokenAmount, + minOutputTokenAmount, + [transformation], + ) + .awaitTransactionSuccessAsync({ value: callValue }); + verifyEventsFromLogs( + receipt.logs, + [ + { + taker, + inputTokenAmount, + outputTokenAmount: outputTokenMintAmount, + inputToken: inputToken.address, + outputToken: ETH_TOKEN_ADDRESS, + }, + ], + TransformERC20Events.TransformedERC20, + ); + verifyEventsFromLogs( + receipt.logs, + [ + { + callDataHash, + taker, + context: wallet.address, + caller: zeroEx.address, + data: transformation.data, + inputTokenBalance: inputTokenAmount, + ethBalance: callValue, + }, + ], + TestMintTokenERC20TransformerEvents.MintTransform, + ); + expect(await env.web3Wrapper.getBalanceInWeiAsync(taker)).to.bignumber.eq( + startingOutputTokenBalance.plus(outputTokenMintAmount), + ); + }); + + it("succeeds if taker's output token balance increases by more than minOutputTokenAmount", async () => { + const startingOutputTokenBalance = getRandomInteger(0, '100e18'); + const startingInputTokenBalance = getRandomInteger(0, '100e18'); + await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync(); + await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync(); + const inputTokenAmount = getRandomPortion(startingInputTokenBalance); + const minOutputTokenAmount = getRandomInteger(1, '1e18'); + const outputTokenMintAmount = minOutputTokenAmount.plus(1); + const callValue = getRandomInteger(1, '1e18'); + const callDataHash = hexUtils.random(); + const transformation = createMintTokenTransformation({ + outputTokenMintAmount, + inputTokenBurnAmunt: inputTokenAmount, + }); + const receipt = await feature + ._transformERC20( + callDataHash, + taker, + inputToken.address, + outputToken.address, + inputTokenAmount, + minOutputTokenAmount, + [transformation], + ) + .awaitTransactionSuccessAsync({ value: callValue }); + verifyEventsFromLogs( + receipt.logs, + [ + { + taker, + inputTokenAmount, + outputTokenAmount: outputTokenMintAmount, + inputToken: inputToken.address, + outputToken: outputToken.address, + }, + ], + TransformERC20Events.TransformedERC20, + ); + verifyEventsFromLogs( + receipt.logs, + [ + { + callDataHash, + taker, + context: wallet.address, + caller: zeroEx.address, + data: transformation.data, + inputTokenBalance: inputTokenAmount, + ethBalance: callValue, + }, + ], + TestMintTokenERC20TransformerEvents.MintTransform, + ); + }); + + it("throws if taker's output token balance increases by less than minOutputTokenAmount", async () => { + const startingOutputTokenBalance = getRandomInteger(0, '100e18'); + const startingInputTokenBalance = getRandomInteger(0, '100e18'); + await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync(); + await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync(); + const inputTokenAmount = getRandomPortion(startingInputTokenBalance); + const minOutputTokenAmount = getRandomInteger(1, '1e18'); + const outputTokenMintAmount = minOutputTokenAmount.minus(1); + const callValue = getRandomInteger(1, '1e18'); + const tx = feature + ._transformERC20( + hexUtils.random(), + taker, + inputToken.address, + outputToken.address, + inputTokenAmount, + minOutputTokenAmount, + [ + createMintTokenTransformation({ + outputTokenMintAmount, + inputTokenBurnAmunt: inputTokenAmount, + }), + ], + ) + .awaitTransactionSuccessAsync({ value: callValue }); + const expectedError = new ZeroExRevertErrors.TransformERC20.IncompleteTransformERC20Error( + outputToken.address, + outputTokenMintAmount, + minOutputTokenAmount, + ); + return expect(tx).to.revertWith(expectedError); + }); + + it("throws if taker's output token balance decreases", async () => { + const startingOutputTokenBalance = getRandomInteger(0, '100e18'); + const startingInputTokenBalance = getRandomInteger(0, '100e18'); + await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync(); + await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync(); + const inputTokenAmount = getRandomPortion(startingInputTokenBalance); + const minOutputTokenAmount = ZERO_AMOUNT; + const outputTokenFeeAmount = 1; + const callValue = getRandomInteger(1, '1e18'); + const tx = feature + ._transformERC20( + hexUtils.random(), + taker, + inputToken.address, + outputToken.address, + inputTokenAmount, + minOutputTokenAmount, + [ + createMintTokenTransformation({ + outputTokenFeeAmount, + inputTokenBurnAmunt: inputTokenAmount, + }), + ], + ) + .awaitTransactionSuccessAsync({ value: callValue }); + const expectedError = new ZeroExRevertErrors.TransformERC20.NegativeTransformERC20OutputError( + outputToken.address, + outputTokenFeeAmount, + ); + return expect(tx).to.revertWith(expectedError); + }); + + it('can call multiple transformers', async () => { + const startingOutputTokenBalance = getRandomInteger(0, '100e18'); + const startingInputTokenBalance = getRandomInteger(2, '100e18'); + await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync(); + await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync(); + const inputTokenAmount = getRandomPortion(startingInputTokenBalance); + const minOutputTokenAmount = getRandomInteger(2, '1e18'); + const outputTokenMintAmount = minOutputTokenAmount; + const callValue = getRandomInteger(1, '1e18'); + const callDataHash = hexUtils.random(); + // Split the total minting between two transformers. + const transformations = [ + createMintTokenTransformation({ + inputTokenBurnAmunt: 1, + outputTokenMintAmount: 1, + }), + createMintTokenTransformation({ + inputTokenBurnAmunt: inputTokenAmount.minus(1), + outputTokenMintAmount: outputTokenMintAmount.minus(1), + }), + ]; + const receipt = await feature + ._transformERC20( + callDataHash, + taker, + inputToken.address, + outputToken.address, + inputTokenAmount, + minOutputTokenAmount, + transformations, + ) + .awaitTransactionSuccessAsync({ value: callValue }); + verifyEventsFromLogs( + receipt.logs, + [ + { + callDataHash, + taker, + context: wallet.address, + caller: zeroEx.address, + data: transformations[0].data, + inputTokenBalance: inputTokenAmount, + ethBalance: callValue, + }, + { + callDataHash, + taker, + context: wallet.address, + caller: zeroEx.address, + data: transformations[1].data, + inputTokenBalance: inputTokenAmount.minus(1), + ethBalance: callValue, + }, + ], + TestMintTokenERC20TransformerEvents.MintTransform, + ); + }); + + it('fails with third-party transformer', async () => { + const startingOutputTokenBalance = getRandomInteger(0, '100e18'); + const startingInputTokenBalance = getRandomInteger(2, '100e18'); + await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync(); + await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync(); + const inputTokenAmount = getRandomPortion(startingInputTokenBalance); + const minOutputTokenAmount = getRandomInteger(2, '1e18'); + const callValue = getRandomInteger(1, '1e18'); + const callDataHash = hexUtils.random(); + const transformations = [createMintTokenTransformation({ transformer: randomAddress() })]; + const tx = feature + ._transformERC20( + callDataHash, + taker, + inputToken.address, + outputToken.address, + inputTokenAmount, + minOutputTokenAmount, + transformations, + ) + .awaitTransactionSuccessAsync({ value: callValue }); + return expect(tx).to.revertWith(new ZeroExRevertErrors.TransformERC20.InvalidRLPNonceError(NULL_BYTES)); + }); + + it('fails with incorrect transformer RLP nonce', async () => { + const startingOutputTokenBalance = getRandomInteger(0, '100e18'); + const startingInputTokenBalance = getRandomInteger(2, '100e18'); + await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync(); + await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync(); + const inputTokenAmount = getRandomPortion(startingInputTokenBalance); + const minOutputTokenAmount = getRandomInteger(2, '1e18'); + const callValue = getRandomInteger(1, '1e18'); + const callDataHash = hexUtils.random(); + const badRlpNonce = '0x00'; + const transformations = [createMintTokenTransformation({ rlpNonce: badRlpNonce })]; + const tx = feature + ._transformERC20( + callDataHash, + taker, + inputToken.address, + outputToken.address, + inputTokenAmount, + minOutputTokenAmount, + transformations, + ) + .awaitTransactionSuccessAsync({ value: callValue }); + return expect(tx).to.revertWith( + new ZeroExRevertErrors.TransformERC20.UnauthorizedTransformerError( + transformations[0].transformer, + badRlpNonce, + ), + ); + }); + + it('fails with invalid transformer RLP nonce', async () => { + const startingOutputTokenBalance = getRandomInteger(0, '100e18'); + const startingInputTokenBalance = getRandomInteger(2, '100e18'); + await outputToken.mint(taker, startingOutputTokenBalance).awaitTransactionSuccessAsync(); + await inputToken.mint(taker, startingInputTokenBalance).awaitTransactionSuccessAsync(); + const inputTokenAmount = getRandomPortion(startingInputTokenBalance); + const minOutputTokenAmount = getRandomInteger(2, '1e18'); + const callValue = getRandomInteger(1, '1e18'); + const callDataHash = hexUtils.random(); + const badRlpNonce = '0x010203040506'; + const transformations = [createMintTokenTransformation({ rlpNonce: badRlpNonce })]; + const tx = feature + ._transformERC20( + callDataHash, + taker, + inputToken.address, + outputToken.address, + inputTokenAmount, + minOutputTokenAmount, + transformations, + ) + .awaitTransactionSuccessAsync({ value: callValue }); + return expect(tx).to.revertWith(new ZeroExRevertErrors.TransformERC20.InvalidRLPNonceError(badRlpNonce)); + }); + }); +}); diff --git a/contracts/zero-ex/test/flash_wallet_test.ts b/contracts/zero-ex/test/flash_wallet_test.ts new file mode 100644 index 0000000000..97df5cf51e --- /dev/null +++ b/contracts/zero-ex/test/flash_wallet_test.ts @@ -0,0 +1,211 @@ +import { + blockchainTests, + constants, + expect, + getRandomInteger, + randomAddress, + verifyEventsFromLogs, +} from '@0x/contracts-test-utils'; +import { hexUtils, OwnableRevertErrors, StringRevertError, ZeroExRevertErrors } from '@0x/utils'; + +import { artifacts } from './artifacts'; +import { FlashWalletContract, TestCallTargetContract, TestCallTargetEvents } from './wrappers'; + +blockchainTests.resets('FlashWallet', env => { + let owner: string; + let wallet: FlashWalletContract; + let callTarget: TestCallTargetContract; + + before(async () => { + [owner] = await env.getAccountAddressesAsync(); + wallet = await FlashWalletContract.deployFrom0xArtifactAsync( + artifacts.FlashWallet, + env.provider, + { + ...env.txDefaults, + from: owner, + }, + artifacts, + ); + callTarget = await TestCallTargetContract.deployFrom0xArtifactAsync( + artifacts.TestCallTarget, + env.provider, + env.txDefaults, + artifacts, + ); + }); + + const TARGET_RETURN_VALUE = hexUtils.rightPad('0x12345678'); + const REVERTING_DATA = '0x1337'; + + it('owned by deployer', () => { + return expect(wallet.owner().callAsync()).to.eventually.eq(owner); + }); + + describe('executeCall()', () => { + it('non-owner cannot call executeCall()', async () => { + const notOwner = randomAddress(); + const tx = wallet + .executeCall(randomAddress(), hexUtils.random(), getRandomInteger(0, '100e18')) + .callAsync({ from: notOwner }); + return expect(tx).to.revertWith(new OwnableRevertErrors.OnlyOwnerError(notOwner)); + }); + + it('owner can call executeCall()', async () => { + const targetData = hexUtils.random(128); + const receipt = await wallet + .executeCall(callTarget.address, targetData, constants.ZERO_AMOUNT) + .awaitTransactionSuccessAsync({ from: owner }); + verifyEventsFromLogs( + receipt.logs, + [ + { + context: callTarget.address, + sender: wallet.address, + data: targetData, + value: constants.ZERO_AMOUNT, + }, + ], + TestCallTargetEvents.CallTargetCalled, + ); + }); + + it('owner can call executeCall() with attached ETH', async () => { + const targetData = hexUtils.random(128); + const callValue = getRandomInteger(1, '1e18'); + const receipt = await wallet + .executeCall(callTarget.address, targetData, callValue) + .awaitTransactionSuccessAsync({ from: owner, value: callValue }); + verifyEventsFromLogs( + receipt.logs, + [ + { + context: callTarget.address, + sender: wallet.address, + data: targetData, + value: callValue, + }, + ], + TestCallTargetEvents.CallTargetCalled, + ); + }); + + it('owner can call executeCall() can transfer less ETH than attached', async () => { + const targetData = hexUtils.random(128); + const callValue = getRandomInteger(1, '1e18'); + const receipt = await wallet + .executeCall(callTarget.address, targetData, callValue.minus(1)) + .awaitTransactionSuccessAsync({ from: owner, value: callValue }); + verifyEventsFromLogs( + receipt.logs, + [ + { + context: callTarget.address, + sender: wallet.address, + data: targetData, + value: callValue.minus(1), + }, + ], + TestCallTargetEvents.CallTargetCalled, + ); + }); + + it('wallet returns call result', async () => { + const result = await wallet + .executeCall(callTarget.address, hexUtils.random(128), constants.ZERO_AMOUNT) + .callAsync({ from: owner }); + expect(result).to.eq(TARGET_RETURN_VALUE); + }); + + it('wallet wraps call revert', async () => { + const tx = wallet + .executeCall(callTarget.address, REVERTING_DATA, constants.ZERO_AMOUNT) + .callAsync({ from: owner }); + return expect(tx).to.revertWith( + new ZeroExRevertErrors.Wallet.WalletExecuteCallFailedError( + wallet.address, + callTarget.address, + REVERTING_DATA, + constants.ZERO_AMOUNT, + new StringRevertError('TestCallTarget/REVERT').encode(), + ), + ); + }); + + it('wallet can receive ETH', async () => { + await env.web3Wrapper.sendTransactionAsync({ + to: wallet.address, + from: owner, + value: 1, + }); + const bal = await env.web3Wrapper.getBalanceInWeiAsync(wallet.address); + expect(bal).to.bignumber.eq(1); + }); + }); + + describe('executeDelegateCall()', () => { + it('non-owner cannot call executeDelegateCall()', async () => { + const notOwner = randomAddress(); + const tx = wallet.executeDelegateCall(randomAddress(), hexUtils.random()).callAsync({ from: notOwner }); + return expect(tx).to.revertWith(new OwnableRevertErrors.OnlyOwnerError(notOwner)); + }); + + it('owner can call executeDelegateCall()', async () => { + const targetData = hexUtils.random(128); + const receipt = await wallet + .executeDelegateCall(callTarget.address, targetData) + .awaitTransactionSuccessAsync({ from: owner }); + verifyEventsFromLogs( + receipt.logs, + [ + { + context: wallet.address, + sender: owner, + data: targetData, + value: constants.ZERO_AMOUNT, + }, + ], + TestCallTargetEvents.CallTargetCalled, + ); + }); + + it('executeDelegateCall() is payable', async () => { + const targetData = hexUtils.random(128); + const callValue = getRandomInteger(1, '1e18'); + const receipt = await wallet + .executeDelegateCall(callTarget.address, targetData) + .awaitTransactionSuccessAsync({ from: owner, value: callValue }); + verifyEventsFromLogs( + receipt.logs, + [ + { + context: wallet.address, + sender: owner, + data: targetData, + value: callValue, + }, + ], + TestCallTargetEvents.CallTargetCalled, + ); + }); + + it('wallet returns call result', async () => { + const result = await wallet + .executeDelegateCall(callTarget.address, hexUtils.random(128)) + .callAsync({ from: owner }); + expect(result).to.eq(TARGET_RETURN_VALUE); + }); + + it('wallet wraps call revert', async () => { + const tx = wallet.executeDelegateCall(callTarget.address, REVERTING_DATA).callAsync({ from: owner }); + return expect(tx).to.revertWith( + new ZeroExRevertErrors.Wallet.WalletExecuteDelegateCallFailedError( + wallet.address, + callTarget.address, + REVERTING_DATA, + new StringRevertError('TestCallTarget/REVERT').encode(), + ), + ); + }); + }); +}); diff --git a/contracts/zero-ex/test/full_migration_test.ts b/contracts/zero-ex/test/full_migration_test.ts new file mode 100644 index 0000000000..a95474de6c --- /dev/null +++ b/contracts/zero-ex/test/full_migration_test.ts @@ -0,0 +1,165 @@ +import { BaseContract } from '@0x/base-contract'; +import { blockchainTests, constants, expect, randomAddress } from '@0x/contracts-test-utils'; +import { BigNumber, hexUtils, ZeroExRevertErrors } from '@0x/utils'; +import { DataItem, MethodAbi } from 'ethereum-types'; +import * as _ from 'lodash'; + +import { artifacts } from './artifacts'; +import { abis } from './utils/abis'; +import { deployFullFeaturesAsync, FullFeatures, toFeatureAdddresses } from './utils/migration'; +import { + AllowanceTargetContract, + IOwnableContract, + ITokenSpenderContract, + ITransformERC20Contract, + TestFullMigrationContract, + ZeroExContract, +} from './wrappers'; + +const { NULL_ADDRESS } = constants; + +blockchainTests.resets('Full migration', env => { + let owner: string; + let zeroEx: ZeroExContract; + let features: FullFeatures; + let migrator: TestFullMigrationContract; + + before(async () => { + [owner] = await env.getAccountAddressesAsync(); + features = await deployFullFeaturesAsync(env.provider, env.txDefaults); + migrator = await TestFullMigrationContract.deployFrom0xArtifactAsync( + artifacts.TestFullMigration, + env.provider, + env.txDefaults, + artifacts, + env.txDefaults.from as string, + ); + const deployCall = migrator.deploy(owner, toFeatureAdddresses(features)); + zeroEx = new ZeroExContract(await deployCall.callAsync(), env.provider, env.txDefaults); + await deployCall.awaitTransactionSuccessAsync(); + }); + + it('ZeroEx has the correct owner', async () => { + const ownable = new IOwnableContract(zeroEx.address, env.provider, env.txDefaults); + const actualOwner = await ownable.owner().callAsync(); + expect(actualOwner).to.eq(owner); + }); + + it('FullMigration contract self-destructs', async () => { + const dieRecipient = await migrator.dieRecipient().callAsync(); + expect(dieRecipient).to.eq(owner); + }); + + it('Non-deployer cannot call deploy()', async () => { + const notDeployer = randomAddress(); + const tx = migrator.deploy(owner, toFeatureAdddresses(features)).callAsync({ from: notDeployer }); + return expect(tx).to.revertWith('FullMigration/INVALID_SENDER'); + }); + + const FEATURE_FNS = { + TokenSpender: { + contractType: ITokenSpenderContract, + fns: ['_spendERC20Tokens'], + }, + TransformERC20: { + contractType: ITransformERC20Contract, + fns: ['transformERC20', '_transformERC20', 'createTransformWallet', 'getTransformWallet'], + }, + }; + + function createFakeInputs(inputs: DataItem[] | DataItem): any | any[] { + if ((inputs as DataItem[]).length !== undefined) { + return (inputs as DataItem[]).map(i => createFakeInputs(i)); + } + const item = inputs as DataItem; + // TODO(dorothy-zbornak): Support fixed-length arrays. + if (/\[]$/.test(item.type)) { + return _.times(_.random(0, 8), () => + createFakeInputs({ + ...item, + type: item.type.substring(0, item.type.length - 2), + }), + ); + } + if (/^tuple$/.test(item.type)) { + const tuple = {} as any; + for (const comp of item.components as DataItem[]) { + tuple[comp.name] = createFakeInputs(comp); + } + return tuple; + } + if (item.type === 'address') { + return randomAddress(); + } + if (item.type === 'byte') { + return hexUtils.random(1); + } + if (/^bytes$/.test(item.type)) { + return hexUtils.random(_.random(0, 128)); + } + if (/^bytes\d+$/.test(item.type)) { + return hexUtils.random(parseInt(/\d+$/.exec(item.type)![0], 10)); + } + if (/^uint\d+$/.test(item.type)) { + return new BigNumber(hexUtils.random(parseInt(/\d+$/.exec(item.type)![0], 10) / 8)); + } + if (/^int\d+$/.test(item.type)) { + return new BigNumber(hexUtils.random(parseInt(/\d+$/.exec(item.type)![0], 10) / 8)) + .div(2) + .times(_.sample([-1, 1])!); + } + throw new Error(`Unhandled input type: '${item.type}'`); + } + + for (const [featureName, featureInfo] of Object.entries(FEATURE_FNS)) { + describe(`${featureName} feature`, () => { + let contract: BaseContract & { getSelector(name: string): string }; + + before(async () => { + contract = new featureInfo.contractType(zeroEx.address, env.provider, env.txDefaults, abis); + }); + + for (const fn of featureInfo.fns) { + it(`${fn} is registered`, async () => { + const selector = contract.getSelector(fn); + const impl = await zeroEx.getFunctionImplementation(selector).callAsync(); + expect(impl).to.not.eq(NULL_ADDRESS); + }); + + if (fn.startsWith('_')) { + it(`${fn} cannot be called from outside`, async () => { + const method = contract.abi.find( + d => d.type === 'function' && (d as MethodAbi).name === fn, + ) as MethodAbi; + const inputs = createFakeInputs(method.inputs); + const tx = (contract as any)[fn](...inputs).callAsync(); + return expect(tx).to.revertWith( + new ZeroExRevertErrors.Common.OnlyCallableBySelfError(env.txDefaults.from), + ); + }); + } + } + }); + } + + describe("TokenSpender's allowance target", () => { + let allowanceTarget: AllowanceTargetContract; + + before(async () => { + const contract = new ITokenSpenderContract(zeroEx.address, env.provider, env.txDefaults); + allowanceTarget = new AllowanceTargetContract( + await contract.getAllowanceTarget().callAsync(), + env.provider, + env.txDefaults, + ); + }); + + it('is owned by owner', async () => { + return expect(allowanceTarget.owner().callAsync()).to.become(owner); + }); + + it('Proxy is authorized', async () => { + return expect(allowanceTarget.authorized(zeroEx.address).callAsync()).to.become(true); + }); + }); +}); diff --git a/contracts/zero-ex/test/initial_migration_test.ts b/contracts/zero-ex/test/initial_migration_test.ts index bad7a56f62..7772cd33e9 100644 --- a/contracts/zero-ex/test/initial_migration_test.ts +++ b/contracts/zero-ex/test/initial_migration_test.ts @@ -2,6 +2,7 @@ import { blockchainTests, expect, randomAddress } from '@0x/contracts-test-utils import { ZeroExRevertErrors } from '@0x/utils'; import { artifacts } from './artifacts'; +import { BootstrapFeatures, deployBootstrapFeaturesAsync, toFeatureAdddresses } from './utils/migration'; import { IBootstrapContract, InitialMigrationContract, @@ -15,9 +16,11 @@ blockchainTests.resets('Initial migration', env => { let zeroEx: ZeroExContract; let migrator: TestInitialMigrationContract; let bootstrapFeature: IBootstrapContract; + let features: BootstrapFeatures; before(async () => { [owner] = await env.getAccountAddressesAsync(); + features = await deployBootstrapFeaturesAsync(env.provider, env.txDefaults); migrator = await TestInitialMigrationContract.deployFrom0xArtifactAsync( artifacts.TestInitialMigration, env.provider, @@ -31,7 +34,7 @@ blockchainTests.resets('Initial migration', env => { env.txDefaults, {}, ); - const deployCall = migrator.deploy(owner); + const deployCall = migrator.deploy(owner, toFeatureAdddresses(features)); zeroEx = new ZeroExContract(await deployCall.callAsync(), env.provider, env.txDefaults); await deployCall.awaitTransactionSuccessAsync(); }); @@ -43,7 +46,7 @@ blockchainTests.resets('Initial migration', env => { it('Non-deployer cannot call deploy()', async () => { const notDeployer = randomAddress(); - const tx = migrator.deploy(owner).callAsync({ from: notDeployer }); + const tx = migrator.deploy(owner, toFeatureAdddresses(features)).callAsync({ from: notDeployer }); return expect(tx).to.revertWith('InitialMigration/INVALID_SENDER'); }); @@ -67,8 +70,8 @@ blockchainTests.resets('Initial migration', env => { }); it('Bootstrap feature self destructs after deployment', async () => { - const codeSize = await migrator.getCodeSizeOf(bootstrapFeature.address).callAsync(); - expect(codeSize).to.bignumber.eq(0); + const doesExist = await env.web3Wrapper.doesContractExistAtAddressAsync(bootstrapFeature.address); + expect(doesExist).to.eq(false); }); }); diff --git a/contracts/zero-ex/test/utils/abis.ts b/contracts/zero-ex/test/utils/abis.ts new file mode 100644 index 0000000000..4aae346909 --- /dev/null +++ b/contracts/zero-ex/test/utils/abis.ts @@ -0,0 +1,5 @@ +import * as _ from 'lodash'; + +import { artifacts } from '../artifacts'; + +export const abis = _.mapValues(artifacts, v => v.compilerOutput.abi); diff --git a/contracts/zero-ex/test/utils/migration.ts b/contracts/zero-ex/test/utils/migration.ts index f01919a6a1..4054358aae 100644 --- a/contracts/zero-ex/test/utils/migration.ts +++ b/contracts/zero-ex/test/utils/migration.ts @@ -1,15 +1,53 @@ +import { BaseContract } from '@0x/base-contract'; import { SupportedProvider } from '@0x/subproviders'; import { TxData } from 'ethereum-types'; +import * as _ from 'lodash'; import { artifacts } from '../artifacts'; -import { InitialMigrationContract, ZeroExContract } from '../wrappers'; +import { + FullMigrationContract, + InitialMigrationContract, + OwnableContract, + SimpleFunctionRegistryContract, + TokenSpenderContract, + TransformERC20Contract, + ZeroExContract, +} from '../wrappers'; // tslint:disable: completed-docs + +export interface BootstrapFeatures { + registry: SimpleFunctionRegistryContract; + ownable: OwnableContract; +} + +export async function deployBootstrapFeaturesAsync( + provider: SupportedProvider, + txDefaults: Partial, + features: Partial = {}, +): Promise { + return { + registry: + features.registry || + (await SimpleFunctionRegistryContract.deployFrom0xArtifactAsync( + artifacts.SimpleFunctionRegistry, + provider, + txDefaults, + artifacts, + )), + ownable: + features.ownable || + (await OwnableContract.deployFrom0xArtifactAsync(artifacts.Ownable, provider, txDefaults, artifacts)), + }; +} + export async function initialMigrateAsync( owner: string, provider: SupportedProvider, txDefaults: Partial, + features: Partial = {}, ): Promise { + const _features = await deployBootstrapFeaturesAsync(provider, txDefaults, features); const migrator = await InitialMigrationContract.deployFrom0xArtifactAsync( artifacts.InitialMigration, provider, @@ -17,8 +55,74 @@ export async function initialMigrateAsync( artifacts, txDefaults.from as string, ); - const deployCall = migrator.deploy(owner); + const deployCall = migrator.deploy(owner, toFeatureAdddresses(_features)); const zeroEx = new ZeroExContract(await deployCall.callAsync(), provider, {}); await deployCall.awaitTransactionSuccessAsync(); return zeroEx; } + +export interface FullFeatures extends BootstrapFeatures { + tokenSpender: TokenSpenderContract; + transformERC20: TransformERC20Contract; +} + +export interface FullMigrationOpts { + transformDeployer: string; +} + +export async function deployFullFeaturesAsync( + provider: SupportedProvider, + txDefaults: Partial, + features: Partial = {}, + opts: Partial = {}, +): Promise { + return { + ...(await deployBootstrapFeaturesAsync(provider, txDefaults)), + tokenSpender: + features.tokenSpender || + (await TokenSpenderContract.deployFrom0xArtifactAsync( + artifacts.TokenSpender, + provider, + txDefaults, + artifacts, + )), + transformERC20: + features.transformERC20 || + (await TransformERC20Contract.deployFrom0xArtifactAsync( + artifacts.TransformERC20, + provider, + txDefaults, + artifacts, + opts.transformDeployer || (txDefaults.from as string), + )), + }; +} + +export async function fullMigrateAsync( + owner: string, + provider: SupportedProvider, + txDefaults: Partial, + features: Partial = {}, + opts: Partial = {}, +): Promise { + const _features = await deployFullFeaturesAsync(provider, txDefaults, features, opts); + const migrator = await FullMigrationContract.deployFrom0xArtifactAsync( + artifacts.FullMigration, + provider, + txDefaults, + artifacts, + txDefaults.from as string, + ); + const deployCall = migrator.deploy(owner, toFeatureAdddresses(_features)); + const zeroEx = new ZeroExContract(await deployCall.callAsync(), provider, {}); + await deployCall.awaitTransactionSuccessAsync(); + return zeroEx; +} + +// tslint:disable:space-before-function-parent one-line +export function toFeatureAdddresses( + features: T, +): { [name in keyof T]: string } { + // TS can't figure this out. + return _.mapValues(features, (c: BaseContract) => c.address) as any; +} diff --git a/contracts/zero-ex/test/wrappers.ts b/contracts/zero-ex/test/wrappers.ts index 8a12827df6..4e5208d325 100644 --- a/contracts/zero-ex/test/wrappers.ts +++ b/contracts/zero-ex/test/wrappers.ts @@ -3,16 +3,25 @@ * Warning: This file is auto-generated by contracts-gen. Don't edit manually. * ----------------------------------------------------------------------------- */ +export * from '../test/generated-wrappers/allowance_target'; export * from '../test/generated-wrappers/bootstrap'; export * from '../test/generated-wrappers/fixin_common'; +export * from '../test/generated-wrappers/flash_wallet'; +export * from '../test/generated-wrappers/full_migration'; +export * from '../test/generated-wrappers/i_allowance_target'; export * from '../test/generated-wrappers/i_bootstrap'; +export * from '../test/generated-wrappers/i_erc20_transformer'; export * from '../test/generated-wrappers/i_feature'; +export * from '../test/generated-wrappers/i_flash_wallet'; export * from '../test/generated-wrappers/i_ownable'; export * from '../test/generated-wrappers/i_simple_function_registry'; export * from '../test/generated-wrappers/i_test_simple_function_registry_feature'; +export * from '../test/generated-wrappers/i_token_spender'; +export * from '../test/generated-wrappers/i_transform_erc20'; export * from '../test/generated-wrappers/initial_migration'; export * from '../test/generated-wrappers/lib_bootstrap'; export * from '../test/generated-wrappers/lib_common_rich_errors'; +export * from '../test/generated-wrappers/lib_erc20_transformer'; export * from '../test/generated-wrappers/lib_migrate'; export * from '../test/generated-wrappers/lib_ownable_rich_errors'; export * from '../test/generated-wrappers/lib_ownable_storage'; @@ -20,12 +29,26 @@ export * from '../test/generated-wrappers/lib_proxy_rich_errors'; export * from '../test/generated-wrappers/lib_proxy_storage'; export * from '../test/generated-wrappers/lib_simple_function_registry_rich_errors'; export * from '../test/generated-wrappers/lib_simple_function_registry_storage'; +export * from '../test/generated-wrappers/lib_spender_rich_errors'; export * from '../test/generated-wrappers/lib_storage'; +export * from '../test/generated-wrappers/lib_token_spender_storage'; +export * from '../test/generated-wrappers/lib_transform_erc20_rich_errors'; +export * from '../test/generated-wrappers/lib_transform_erc20_storage'; +export * from '../test/generated-wrappers/lib_wallet_rich_errors'; export * from '../test/generated-wrappers/ownable'; export * from '../test/generated-wrappers/simple_function_registry'; +export * from '../test/generated-wrappers/test_call_target'; +export * from '../test/generated-wrappers/test_full_migration'; export * from '../test/generated-wrappers/test_initial_migration'; export * from '../test/generated-wrappers/test_migrator'; +export * from '../test/generated-wrappers/test_mint_token_erc20_transformer'; +export * from '../test/generated-wrappers/test_mintable_erc20_token'; export * from '../test/generated-wrappers/test_simple_function_registry_feature_impl1'; export * from '../test/generated-wrappers/test_simple_function_registry_feature_impl2'; +export * from '../test/generated-wrappers/test_token_spender'; +export * from '../test/generated-wrappers/test_token_spender_erc20_token'; +export * from '../test/generated-wrappers/test_transform_erc20'; export * from '../test/generated-wrappers/test_zero_ex_feature'; +export * from '../test/generated-wrappers/token_spender'; +export * from '../test/generated-wrappers/transform_erc20'; export * from '../test/generated-wrappers/zero_ex'; diff --git a/contracts/zero-ex/tsconfig.json b/contracts/zero-ex/tsconfig.json index f8aa02b184..f6e2e1d2bf 100644 --- a/contracts/zero-ex/tsconfig.json +++ b/contracts/zero-ex/tsconfig.json @@ -3,19 +3,35 @@ "compilerOptions": { "outDir": "lib", "rootDir": ".", "resolveJsonModule": true }, "include": ["./src/**/*", "./test/**/*", "./generated-wrappers/**/*"], "files": [ + "generated-artifacts/FullMigration.json", + "generated-artifacts/IAllowanceTarget.json", + "generated-artifacts/IERC20Transformer.json", + "generated-artifacts/IFlashWallet.json", "generated-artifacts/IOwnable.json", "generated-artifacts/ISimpleFunctionRegistry.json", + "generated-artifacts/ITokenSpender.json", + "generated-artifacts/ITransformERC20.json", + "generated-artifacts/InitialMigration.json", "generated-artifacts/ZeroEx.json", + "test/generated-artifacts/AllowanceTarget.json", "test/generated-artifacts/Bootstrap.json", "test/generated-artifacts/FixinCommon.json", + "test/generated-artifacts/FlashWallet.json", + "test/generated-artifacts/FullMigration.json", + "test/generated-artifacts/IAllowanceTarget.json", "test/generated-artifacts/IBootstrap.json", + "test/generated-artifacts/IERC20Transformer.json", "test/generated-artifacts/IFeature.json", + "test/generated-artifacts/IFlashWallet.json", "test/generated-artifacts/IOwnable.json", "test/generated-artifacts/ISimpleFunctionRegistry.json", "test/generated-artifacts/ITestSimpleFunctionRegistryFeature.json", + "test/generated-artifacts/ITokenSpender.json", + "test/generated-artifacts/ITransformERC20.json", "test/generated-artifacts/InitialMigration.json", "test/generated-artifacts/LibBootstrap.json", "test/generated-artifacts/LibCommonRichErrors.json", + "test/generated-artifacts/LibERC20Transformer.json", "test/generated-artifacts/LibMigrate.json", "test/generated-artifacts/LibOwnableRichErrors.json", "test/generated-artifacts/LibOwnableStorage.json", @@ -23,14 +39,28 @@ "test/generated-artifacts/LibProxyStorage.json", "test/generated-artifacts/LibSimpleFunctionRegistryRichErrors.json", "test/generated-artifacts/LibSimpleFunctionRegistryStorage.json", + "test/generated-artifacts/LibSpenderRichErrors.json", "test/generated-artifacts/LibStorage.json", + "test/generated-artifacts/LibTokenSpenderStorage.json", + "test/generated-artifacts/LibTransformERC20RichErrors.json", + "test/generated-artifacts/LibTransformERC20Storage.json", + "test/generated-artifacts/LibWalletRichErrors.json", "test/generated-artifacts/Ownable.json", "test/generated-artifacts/SimpleFunctionRegistry.json", + "test/generated-artifacts/TestCallTarget.json", + "test/generated-artifacts/TestFullMigration.json", "test/generated-artifacts/TestInitialMigration.json", "test/generated-artifacts/TestMigrator.json", + "test/generated-artifacts/TestMintTokenERC20Transformer.json", + "test/generated-artifacts/TestMintableERC20Token.json", "test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl1.json", "test/generated-artifacts/TestSimpleFunctionRegistryFeatureImpl2.json", + "test/generated-artifacts/TestTokenSpender.json", + "test/generated-artifacts/TestTokenSpenderERC20Token.json", + "test/generated-artifacts/TestTransformERC20.json", "test/generated-artifacts/TestZeroExFeature.json", + "test/generated-artifacts/TokenSpender.json", + "test/generated-artifacts/TransformERC20.json", "test/generated-artifacts/ZeroEx.json" ], "exclude": ["./deploy/solc/solc_bin"] diff --git a/contracts/zero-ex/tslint.json b/contracts/zero-ex/tslint.json index 91bb1bec78..8cdabb0d0c 100644 --- a/contracts/zero-ex/tslint.json +++ b/contracts/zero-ex/tslint.json @@ -2,7 +2,10 @@ "extends": ["@0x/tslint-config"], "rules": { "custom-no-magic-numbers": false, - "max-file-line-count": false + "max-file-line-count": false, + "no-non-null-assertion": false, + "no-unnecessary-type-assertion": false, + "number-literal-format": false }, "linterOptions": { "exclude": ["src/artifacts.ts", "test/artifacts.ts"] diff --git a/packages/subproviders/CHANGELOG.json b/packages/subproviders/CHANGELOG.json index 410aa0f771..3bfb276ae0 100644 --- a/packages/subproviders/CHANGELOG.json +++ b/packages/subproviders/CHANGELOG.json @@ -1,4 +1,13 @@ [ + { + "version": "6.1.0", + "changes": [ + { + "note": "Update ganache-core", + "pr": 2545 + } + ] + }, { "timestamp": 1582623685, "version": "6.0.8", diff --git a/packages/subproviders/package.json b/packages/subproviders/package.json index 9ff9933bbb..035cf975d3 100644 --- a/packages/subproviders/package.json +++ b/packages/subproviders/package.json @@ -48,7 +48,7 @@ "ethereum-types": "^3.1.0", "ethereumjs-tx": "^1.3.5", "ethereumjs-util": "^5.1.1", - "ganache-core": "^2.9.0-istanbul.0", + "ganache-core": "^2.10.2", "hdkey": "^0.7.1", "json-rpc-error": "2.0.0", "lodash": "^4.17.11", diff --git a/packages/utils/CHANGELOG.json b/packages/utils/CHANGELOG.json index a1d8f197c8..ff55eb6f39 100644 --- a/packages/utils/CHANGELOG.json +++ b/packages/utils/CHANGELOG.json @@ -17,6 +17,10 @@ { "note": "`instanceof Array` => `Array.isArray`", "pr": 2567 + }, + { + "note": "Add more `ZeroExRevertErrors`", + "pr": 2545 } ] }, diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 0470ef443a..e311c57da5 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -49,4 +49,7 @@ export const ZeroExRevertErrors = { Proxy: require('./revert_errors/zero-ex/proxy_revert_errors'), SimpleFunctionRegistry: require('./revert_errors/zero-ex/simple_function_registry_revert_errors'), Ownable: require('./revert_errors/zero-ex/ownable_revert_errors'), + Spender: require('./revert_errors/zero-ex/spender_revert_errors'), + TransformERC20: require('./revert_errors/zero-ex/transform_erc20_revert_errors'), + Wallet: require('./revert_errors/zero-ex/wallet_revert_errors'), }; diff --git a/packages/utils/src/revert_error.ts b/packages/utils/src/revert_error.ts index 7ca1dfcd9b..04051f03a3 100644 --- a/packages/utils/src/revert_error.ts +++ b/packages/utils/src/revert_error.ts @@ -9,7 +9,16 @@ import { BigNumber } from './configured_bignumber'; // tslint:disable: max-classes-per-file -type ArgTypes = string | BigNumber | number | boolean | BigNumber[] | string[] | number[] | boolean[]; +type ArgTypes = + | string + | BigNumber + | number + | boolean + | BigNumber[] + | string[] + | number[] + | boolean[] + | Array; type ValueMap = ObjectMap; type RevertErrorDecoder = (hex: string) => ValueMap; diff --git a/packages/utils/src/revert_errors/zero-ex/common_revert_errors.ts b/packages/utils/src/revert_errors/zero-ex/common_revert_errors.ts index dfa95003c5..c3f4091ee8 100644 --- a/packages/utils/src/revert_errors/zero-ex/common_revert_errors.ts +++ b/packages/utils/src/revert_errors/zero-ex/common_revert_errors.ts @@ -9,6 +9,13 @@ export class OnlyCallableBySelfError extends RevertError { } } +// This is identical to the one in utils. +// export class IllegalReentrancyError extends RevertError { +// constructor() { +// super('IllegalReentrancyError', 'IllegalReentrancyError()', {}); +// } +// } + const types = [OnlyCallableBySelfError]; // Register the types we've defined. diff --git a/packages/utils/src/revert_errors/zero-ex/ownable_revert_errors.ts b/packages/utils/src/revert_errors/zero-ex/ownable_revert_errors.ts index ea1ed271d8..4daf91f40a 100644 --- a/packages/utils/src/revert_errors/zero-ex/ownable_revert_errors.ts +++ b/packages/utils/src/revert_errors/zero-ex/ownable_revert_errors.ts @@ -16,7 +16,23 @@ export class MigrateCallFailedError extends RevertError { } } -const types = [AlreadyMigratingError, MigrateCallFailedError]; +export class OnlyOwnerError extends RevertError { + constructor(sender?: string, owner?: string) { + super('OnlyOwnerError', 'OnlyOwnerError(address sender, bytes owner)', { + sender, + owner, + }); + } +} + +// This is identical to the one in utils. +// export class TransferOwnerToZeroError extends RevertError { +// constructor() { +// super('TransferOwnerToZeroError', 'TransferOwnerToZeroError()', {}); +// } +// } + +const types = [AlreadyMigratingError, MigrateCallFailedError, OnlyOwnerError]; // Register the types we've defined. for (const type of types) { diff --git a/packages/utils/src/revert_errors/zero-ex/spender_revert_errors.ts b/packages/utils/src/revert_errors/zero-ex/spender_revert_errors.ts new file mode 100644 index 0000000000..67e230b4de --- /dev/null +++ b/packages/utils/src/revert_errors/zero-ex/spender_revert_errors.ts @@ -0,0 +1,26 @@ +import { RevertError } from '../../revert_error'; +import { Numberish } from '../../types'; + +// tslint:disable:max-classes-per-file +export class SpenderERC20TransferFromFailedError extends RevertError { + constructor(token?: string, owner?: string, to?: string, amount?: Numberish, errorData?: string) { + super( + 'SpenderERC20TransferFromFailedError', + 'SpenderERC20TransferFromFailedError(address token, address owner, address to, uint256 amount, bytes errorData)', + { + token, + owner, + to, + amount, + errorData, + }, + ); + } +} + +const types = [SpenderERC20TransferFromFailedError]; + +// Register the types we've defined. +for (const type of types) { + RevertError.registerType(type); +} diff --git a/packages/utils/src/revert_errors/zero-ex/transform_erc20_revert_errors.ts b/packages/utils/src/revert_errors/zero-ex/transform_erc20_revert_errors.ts new file mode 100644 index 0000000000..f8133bee3a --- /dev/null +++ b/packages/utils/src/revert_errors/zero-ex/transform_erc20_revert_errors.ts @@ -0,0 +1,153 @@ +import { RevertError } from '../../revert_error'; +import { Numberish } from '../../types'; + +// tslint:disable:max-classes-per-file +export class InsufficientEthAttachedError extends RevertError { + constructor(ethAttached?: Numberish, ethNeeded?: Numberish) { + super('InsufficientEthAttachedError', 'InsufficientEthAttachedError(uint256 ethAttached, uint256 ethNeeded)', { + ethAttached, + ethNeeded, + }); + } +} + +export class IncompleteTransformERC20Error extends RevertError { + constructor(outputToken?: string, outputTokenAmount?: Numberish, minOutputTokenAmount?: Numberish) { + super( + 'IncompleteTransformERC20Error', + 'IncompleteTransformERC20Error(address outputToken, uint256 outputTokenAmount, uint256 minOutputTokenAmount)', + { + outputToken, + outputTokenAmount, + minOutputTokenAmount, + }, + ); + } +} + +export class NegativeTransformERC20OutputError extends RevertError { + constructor(outputToken?: string, outputTokenLostAmount?: Numberish) { + super( + 'NegativeTransformERC20OutputError', + 'NegativeTransformERC20OutputError(address outputToken, uint256 outputTokenLostAmount)', + { + outputToken, + outputTokenLostAmount, + }, + ); + } +} + +export class UnauthorizedTransformerError extends RevertError { + constructor(transformer?: string, rlpNonce?: string) { + super('UnauthorizedTransformerError', 'UnauthorizedTransformerError(address transformer, bytes rlpNonce)', { + transformer, + rlpNonce, + }); + } +} + +export class InvalidRLPNonceError extends RevertError { + constructor(rlpNonce?: string) { + super('InvalidRLPNonceError', 'InvalidRLPNonceError(bytes rlpNonce)', { rlpNonce }); + } +} + +export class IncompleteFillSellQuoteError extends RevertError { + constructor(sellToken?: string, soldAmount?: Numberish, sellAmount?: Numberish) { + super( + 'IncompleteFillSellQuoteError', + 'IncompleteFillSellQuoteError(address sellToken, address[] soldAmount, uint256[] sellAmount)', + { + sellToken, + soldAmount, + sellAmount, + }, + ); + } +} + +export class IncompleteFillBuyQuoteError extends RevertError { + constructor(buyToken?: string, boughtAmount?: Numberish, buyAmount?: Numberish) { + super( + 'IncompleteFillBuyQuoteError', + 'IncompleteFillBuyQuoteError(address buyToken, address[] boughtAmount, uint256[] buyAmount)', + { + buyToken, + boughtAmount, + buyAmount, + }, + ); + } +} + +export class InsufficientTakerTokenError extends RevertError { + constructor(tokenBalance?: Numberish, tokensNeeded?: Numberish) { + super( + 'InsufficientTakerTokenError', + 'InsufficientTakerTokenError(uint256 tokenBalance, uint256 tokensNeeded)', + { + tokenBalance, + tokensNeeded, + }, + ); + } +} + +export class InsufficientProtocolFeeError extends RevertError { + constructor(ethBalance?: Numberish, ethNeeded?: Numberish) { + super('InsufficientProtocolFeeError', 'InsufficientProtocolFeeError(uint256 ethBalance, uint256 ethNeeded)', { + ethBalance, + ethNeeded, + }); + } +} + +export class InvalidERC20AssetDataError extends RevertError { + constructor(assetData?: string) { + super('InvalidERC20AssetDataError', 'InvalidERC20AssetDataError(bytes assetData)', { + assetData, + }); + } +} + +export class WrongNumberOfTokensReceivedError extends RevertError { + constructor(actual?: Numberish, expected?: Numberish) { + super( + 'WrongNumberOfTokensReceivedError', + 'WrongNumberOfTokensReceivedError(uint256 actual, uint256 expected)', + { + actual, + expected, + }, + ); + } +} + +export class InvalidTokenReceivedError extends RevertError { + constructor(token?: string) { + super('InvalidTokenReceivedError', 'InvalidTokenReceivedError(address token)', { + token, + }); + } +} + +const types = [ + InsufficientEthAttachedError, + IncompleteTransformERC20Error, + NegativeTransformERC20OutputError, + UnauthorizedTransformerError, + InvalidRLPNonceError, + IncompleteFillSellQuoteError, + IncompleteFillBuyQuoteError, + InsufficientTakerTokenError, + InsufficientProtocolFeeError, + InvalidERC20AssetDataError, + WrongNumberOfTokensReceivedError, + InvalidTokenReceivedError, +]; + +// Register the types we've defined. +for (const type of types) { + RevertError.registerType(type); +} diff --git a/packages/utils/src/revert_errors/zero-ex/wallet_revert_errors.ts b/packages/utils/src/revert_errors/zero-ex/wallet_revert_errors.ts new file mode 100644 index 0000000000..a3a47582ba --- /dev/null +++ b/packages/utils/src/revert_errors/zero-ex/wallet_revert_errors.ts @@ -0,0 +1,41 @@ +import { RevertError } from '../../revert_error'; +import { Numberish } from '../../types'; + +// tslint:disable:max-classes-per-file +export class WalletExecuteCallFailedError extends RevertError { + constructor(wallet?: string, callTarget?: string, callData?: string, callValue?: Numberish, errorData?: string) { + super( + 'WalletExecuteCallFailedError', + 'WalletExecuteCallFailedError(address wallet, address callTarget, bytes callData, uint256 callValue, bytes errorData)', + { + wallet, + callTarget, + callData, + callValue, + errorData, + }, + ); + } +} + +export class WalletExecuteDelegateCallFailedError extends RevertError { + constructor(wallet?: string, callTarget?: string, callData?: string, errorData?: string) { + super( + 'WalletExecuteDelegateCallFailedError', + 'WalletExecuteDelegateCallFailedError(address wallet, address callTarget, bytes callData, bytes errorData)', + { + wallet, + callTarget, + callData, + errorData, + }, + ); + } +} + +const types = [WalletExecuteCallFailedError, WalletExecuteDelegateCallFailedError]; + +// Register the types we've defined. +for (const type of types) { + RevertError.registerType(type); +} diff --git a/packages/web3-wrapper/CHANGELOG.json b/packages/web3-wrapper/CHANGELOG.json index de4774fa98..be1b24f260 100644 --- a/packages/web3-wrapper/CHANGELOG.json +++ b/packages/web3-wrapper/CHANGELOG.json @@ -1,4 +1,17 @@ [ + { + "version": "7.1.0", + "changes": [ + { + "note": "Add `getAccountNonce()` to `Web3Wrapper`", + "pr": 2545 + }, + { + "note": "Update ganache-core", + "pr": 2545 + } + ] + }, { "timestamp": 1582623685, "version": "7.0.7", diff --git a/packages/web3-wrapper/package.json b/packages/web3-wrapper/package.json index 90513c015d..9da73b3af7 100644 --- a/packages/web3-wrapper/package.json +++ b/packages/web3-wrapper/package.json @@ -47,7 +47,7 @@ "chai-as-promised": "^7.1.0", "chai-bignumber": "^3.0.0", "dirty-chai": "^2.0.1", - "ganache-core": "^2.9.0-istanbul.0", + "ganache-core": "^2.10.2", "make-promises-safe": "^1.1.0", "mocha": "^6.2.0", "npm-run-all": "^4.1.2", diff --git a/packages/web3-wrapper/src/web3_wrapper.ts b/packages/web3-wrapper/src/web3_wrapper.ts index d83c11ec03..36567c5d05 100644 --- a/packages/web3-wrapper/src/web3_wrapper.ts +++ b/packages/web3-wrapper/src/web3_wrapper.ts @@ -360,6 +360,27 @@ export class Web3Wrapper { const blockNumber = utils.convertHexToNumberOrNull(blockNumberHex); return blockNumber as number; } + /** + * Fetches the nonce for an account (transaction count for EOAs). + * @param address Address of account. + * @param defaultBlock Block height at which to make the call. Defaults to `latest` + * @returns Account nonce. + */ + public async getAccountNonceAsync(address: string, defaultBlock?: BlockParam): Promise { + assert.isETHAddressHex('address', address); + if (defaultBlock !== undefined) { + Web3Wrapper._assertBlockParam(defaultBlock); + } + const marshalledDefaultBlock = marshaller.marshalBlockParam(defaultBlock); + const encodedAddress = marshaller.marshalAddress(address); + const nonceHex = await this.sendRawPayloadAsync({ + method: 'eth_getTransactionCount', + params: [encodedAddress, marshalledDefaultBlock], + }); + assert.isHexString('nonce', nonceHex); + // tslint:disable-next-line:custom-no-magic-numbers + return parseInt(nonceHex.substr(2), 16); + } /** * Fetch a specific Ethereum block without transaction data * @param blockParam The block you wish to fetch (blockHash, blockNumber or blockLiteral) diff --git a/packages/web3-wrapper/test/web3_wrapper_test.ts b/packages/web3-wrapper/test/web3_wrapper_test.ts index 7322f55481..dfb05211a0 100644 --- a/packages/web3-wrapper/test/web3_wrapper_test.ts +++ b/packages/web3-wrapper/test/web3_wrapper_test.ts @@ -35,8 +35,7 @@ describe('Web3Wrapper tests', () => { describe('#getNodeVersionAsync', () => { it('gets the node version', async () => { const nodeVersion = await web3Wrapper.getNodeVersionAsync(); - const NODE_VERSION = 'EthereumJS TestRPC/v2.9.0-istanbul.0/ethereum-js'; - expect(nodeVersion).to.be.equal(NODE_VERSION); + expect(nodeVersion).to.be.match(/EthereumJS TestRPC\/.+\/ethereum-js$/); }); }); describe('#getNetworkIdAsync', () => { diff --git a/yarn.lock b/yarn.lock index 2e29c3acc7..4f9e5e929f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1971,6 +1971,13 @@ dependencies: "@babel/types" "^7.3.0" +"@types/bignumber.js@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/bignumber.js/-/bignumber.js-5.0.0.tgz#d9f1a378509f3010a3255e9cc822ad0eeb4ab969" + integrity sha512-0DH7aPGCClywOFaxxjE6UwpN2kQYe9LwuDQMv+zYA97j5GkOMo8e66LYT+a8JYU7jfmUFRZLa9KycxHDsKXJCA== + dependencies: + bignumber.js "*" + "@types/bip39@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@types/bip39/-/bip39-2.4.0.tgz#eee31a14abc8ebbb41a1ff14575c447b18346cbc" @@ -2285,6 +2292,25 @@ dependencies: "@types/yargs-parser" "*" +"@web3-js/scrypt-shim@^0.1.0": + version "0.1.0" + resolved "https://registry.yarnpkg.com/@web3-js/scrypt-shim/-/scrypt-shim-0.1.0.tgz#0bf7529ab6788311d3e07586f7d89107c3bea2cc" + integrity sha512-ZtZeWCc/s0nMcdx/+rZwY1EcuRdemOK9ag21ty9UsHkFxsNb/AaoucUz0iPuyGe0Ku+PFuRmWZG7Z7462p9xPw== + dependencies: + scryptsy "^2.1.0" + semver "^6.3.0" + +"@web3-js/websocket@^1.0.29": + version "1.0.30" + resolved "https://registry.yarnpkg.com/@web3-js/websocket/-/websocket-1.0.30.tgz#9ea15b7b582cf3bf3e8bc1f4d3d54c0731a87f87" + integrity sha512-fDwrD47MiDrzcJdSeTLF75aCcxVVt8B1N74rA+vh2XCAvFy4tEWJjtnUtj2QG7/zlQ6g9cQ88bZFBxwd9/FmtA== + dependencies: + debug "^2.2.0" + es5-ext "^0.10.50" + nan "^2.14.0" + typedarray-to-buffer "^3.1.5" + yaeti "^0.0.6" + "@webassemblyjs/ast@1.7.8": version "1.7.8" resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.8.tgz#f31f480debeef957f01b623f27eabc695fa4fe8f" @@ -3802,6 +3828,10 @@ big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" +bignumber.js@*, bignumber.js@~9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075" + bignumber.js@7.2.1: version "7.2.1" resolved "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f" @@ -3814,10 +3844,6 @@ bignumber.js@~8.0.2: version "8.0.2" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-8.0.2.tgz#d8c4e1874359573b1ef03011a2d861214aeef137" -bignumber.js@~9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075" - binary-extensions@^1.0.0: version "1.11.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" @@ -6691,6 +6717,13 @@ ethashjs@~0.0.7: ethereumjs-util "^4.0.1" miller-rabin "^4.0.0" +ethereum-bloom-filters@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.7.tgz#b7b80735e385dbb7f944ce6b4533e24511306060" + integrity sha512-cDcJJSJ9GMAcURiAWO3DxIEhTL/uWqlQnvgKpuYQzYPrt/izuGU+1ntQmHt0IRq6ADoSYHFnB+aCEFIldjhkMQ== + dependencies: + js-sha3 "^0.8.0" + ethereum-common@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/ethereum-common/-/ethereum-common-0.2.0.tgz#13bf966131cce1eeade62a1b434249bb4cb120ca" @@ -6749,13 +6782,14 @@ ethereumjs-account@^2.0.3: ethereumjs-util "^4.0.1" rlp "^2.0.0" -ethereumjs-block@2.2.0, ethereumjs-block@~2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ethereumjs-block/-/ethereumjs-block-2.2.0.tgz#8c6c3ab4a5eff0a16d9785fbeedbe643f4dbcbef" +ethereumjs-block@2.2.2, ethereumjs-block@^2.2.2, ethereumjs-block@~2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/ethereumjs-block/-/ethereumjs-block-2.2.2.tgz#c7654be7e22df489fda206139ecd63e2e9c04965" + integrity sha512-2p49ifhek3h2zeg/+da6XpdFR3GlqY3BIEiqxGF8j9aSRIgkb7M1Ky+yULBKJOu8PAZxfhsYA+HxUk2aCQp3vg== dependencies: async "^2.0.1" - ethereumjs-common "^1.1.0" - ethereumjs-tx "^1.2.2" + ethereumjs-common "^1.5.0" + ethereumjs-tx "^2.1.1" ethereumjs-util "^5.0.0" merkle-patricia-tree "^2.1.2" @@ -6769,6 +6803,16 @@ ethereumjs-block@^1.2.2, ethereumjs-block@^1.4.1, ethereumjs-block@^1.6.0, ether ethereumjs-util "^5.0.0" merkle-patricia-tree "^2.1.2" +ethereumjs-block@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ethereumjs-block/-/ethereumjs-block-2.2.0.tgz#8c6c3ab4a5eff0a16d9785fbeedbe643f4dbcbef" + dependencies: + async "^2.0.1" + ethereumjs-common "^1.1.0" + ethereumjs-tx "^1.2.2" + ethereumjs-util "^5.0.0" + merkle-patricia-tree "^2.1.2" + ethereumjs-blockchain@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/ethereumjs-blockchain/-/ethereumjs-blockchain-4.0.1.tgz#db113dfed4fcc5197d223391f10adbc5a1b3536b" @@ -6784,6 +6828,22 @@ ethereumjs-blockchain@^4.0.1: rlp "^2.2.2" semaphore "^1.1.0" +ethereumjs-blockchain@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/ethereumjs-blockchain/-/ethereumjs-blockchain-4.0.3.tgz#e013034633a30ad2006728e8e2b21956b267b773" + integrity sha512-0nJWbyA+Gu0ZKZr/cywMtB/77aS/4lOVsIKbgUN2sFQYscXO5rPbUfrEe7G2Zhjp86/a0VqLllemDSTHvx3vZA== + dependencies: + async "^2.6.1" + ethashjs "~0.0.7" + ethereumjs-block "~2.2.2" + ethereumjs-common "^1.5.0" + ethereumjs-util "~6.1.0" + flow-stoplight "^1.0.0" + level-mem "^3.0.1" + lru-cache "^5.1.1" + rlp "^2.2.2" + semaphore "^1.1.0" + ethereumjs-blockstream@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/ethereumjs-blockstream/-/ethereumjs-blockstream-7.0.0.tgz#b8d7b6257dd1100bc6ddb36d6bef58c2490f9999" @@ -6792,6 +6852,10 @@ ethereumjs-blockstream@^7.0.0: source-map-support "0.5.6" uuid "3.2.1" +ethereumjs-common@1.5.0, ethereumjs-common@^1.3.1, ethereumjs-common@^1.3.2, ethereumjs-common@^1.5.0: + version "1.5.0" + resolved "https://registry.npmjs.org/ethereumjs-common/-/ethereumjs-common-1.5.0.tgz#d3e82fc7c47c0cef95047f431a99485abc9bb1cd" + ethereumjs-common@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ethereumjs-common/-/ethereumjs-common-1.1.0.tgz#5ec9086c314d619d8f05e79a0525829fcb0e93cb" @@ -6800,16 +6864,13 @@ ethereumjs-common@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/ethereumjs-common/-/ethereumjs-common-1.3.1.tgz#a5cffac41beb7ad393283b2e5aa71fadf8a9cc73" -ethereumjs-common@^1.3.1, ethereumjs-common@^1.3.2: - version "1.5.0" - resolved "https://registry.npmjs.org/ethereumjs-common/-/ethereumjs-common-1.5.0.tgz#d3e82fc7c47c0cef95047f431a99485abc9bb1cd" - -ethereumjs-tx@1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz#88323a2d875b10549b8347e09f4862b546f3d89a" +ethereumjs-tx@2.1.2, ethereumjs-tx@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ethereumjs-tx/-/ethereumjs-tx-2.1.2.tgz#5dfe7688bf177b45c9a23f86cf9104d47ea35fed" + integrity sha512-zZEK1onCeiORb0wyCXUvg94Ve5It/K6GD1K+26KfFKodiBiS6d9lfCXlUKGBBdQ+bv7Day+JK0tj1K+BeNFRAw== dependencies: - ethereum-common "^0.0.18" - ethereumjs-util "^5.0.0" + ethereumjs-common "^1.5.0" + ethereumjs-util "^6.0.0" ethereumjs-tx@^1.1.1, ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2, ethereumjs-tx@^1.3.3, ethereumjs-tx@^1.3.5: version "1.3.5" @@ -6844,6 +6905,18 @@ ethereumjs-util@6.1.0, ethereumjs-util@^6.0.0, ethereumjs-util@^6.1.0, ethereumj safe-buffer "^5.1.1" secp256k1 "^3.0.1" +ethereumjs-util@6.2.0, ethereumjs-util@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.2.0.tgz#23ec79b2488a7d041242f01e25f24e5ad0357960" + dependencies: + "@types/bn.js" "^4.11.3" + bn.js "^4.11.0" + create-hash "^1.1.2" + ethjs-util "0.1.6" + keccak "^2.0.0" + rlp "^2.2.3" + secp256k1 "^3.0.1" + ethereumjs-util@^4.0.1, ethereumjs-util@^4.3.0: version "4.5.0" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-4.5.0.tgz#3e9428b317eebda3d7260d854fddda954b1f1bc6" @@ -6866,31 +6939,20 @@ ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereum safe-buffer "^5.1.1" secp256k1 "^3.0.1" -ethereumjs-util@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.2.0.tgz#23ec79b2488a7d041242f01e25f24e5ad0357960" - dependencies: - "@types/bn.js" "^4.11.3" - bn.js "^4.11.0" - create-hash "^1.1.2" - ethjs-util "0.1.6" - keccak "^2.0.0" - rlp "^2.2.3" - secp256k1 "^3.0.1" - -ethereumjs-vm@4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/ethereumjs-vm/-/ethereumjs-vm-4.1.0.tgz#359ed3592636390a5b2909a28d955c908830daa5" +ethereumjs-vm@4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ethereumjs-vm/-/ethereumjs-vm-4.1.3.tgz#dc8eb45f47d775da9f0b2437d5e20896fdf66f37" + integrity sha512-RTrD0y7My4O6Qr1P2ZIsMfD6RzL6kU/RhBZ0a5XrPzAeR61crBS7or66ohDrvxDI/rDBxMi+6SnsELih6fzalw== dependencies: async "^2.1.2" async-eventemitter "^0.2.2" core-js-pure "^3.0.1" ethereumjs-account "^3.0.0" - ethereumjs-block "~2.2.0" - ethereumjs-blockchain "^4.0.1" - ethereumjs-common "^1.3.2" - ethereumjs-tx "^2.1.1" - ethereumjs-util "^6.1.0" + ethereumjs-block "^2.2.2" + ethereumjs-blockchain "^4.0.3" + ethereumjs-common "^1.5.0" + ethereumjs-tx "^2.1.2" + ethereumjs-util "^6.2.0" fake-merkle-patricia-tree "^1.0.1" functional-red-black-tree "^1.0.1" merkle-patricia-tree "^2.3.2" @@ -7917,9 +7979,10 @@ ganache-cli@6.8.0-istanbul.0: source-map-support "0.5.12" yargs "13.2.4" -ganache-core@^2.9.0-istanbul.0: - version "2.9.0-istanbul.0" - resolved "https://registry.npmjs.org/ganache-core/-/ganache-core-2.9.0-istanbul.0.tgz#bc336c770775a2b9fb06f5cae827088ecc194283" +ganache-core@^2.10.2: + version "2.10.2" + resolved "https://registry.yarnpkg.com/ganache-core/-/ganache-core-2.10.2.tgz#17c171c6c0195b6734a0dd741a9d2afd4f74e292" + integrity sha512-4XEO0VsqQ1+OW7Za5fQs9/Kk7o8M0T1sRfFSF8h9NeJ2ABaqMO5waqxf567ZMcSkRKaTjUucBSz83xNfZv1HDg== dependencies: abstract-leveldown "3.0.0" async "2.6.2" @@ -7931,10 +7994,11 @@ ganache-core@^2.9.0-istanbul.0: eth-sig-util "2.3.0" ethereumjs-abi "0.6.7" ethereumjs-account "3.0.0" - ethereumjs-block "2.2.0" - ethereumjs-tx "1.3.7" - ethereumjs-util "6.1.0" - ethereumjs-vm "4.1.0" + ethereumjs-block "2.2.2" + ethereumjs-common "1.5.0" + ethereumjs-tx "2.1.2" + ethereumjs-util "6.2.0" + ethereumjs-vm "4.1.3" heap "0.2.6" level-sublevel "6.6.4" levelup "3.1.1" @@ -7947,7 +8011,7 @@ ganache-core@^2.9.0-istanbul.0: websocket "1.0.29" optionalDependencies: ethereumjs-wallet "0.6.3" - web3 "1.2.1" + web3 "1.2.4" gauge@~1.2.5: version "1.2.7" @@ -10383,6 +10447,11 @@ js-sha3@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.7.0.tgz#0a5c57b36f79882573b2d84051f8bb85dd1bd63a" +js-sha3@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" @@ -14757,16 +14826,17 @@ scrypt@^6.0.2: dependencies: nan "^2.0.8" -scryptsy@2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz#8d1e8d0c025b58fdd25b6fa9a0dc905ee8faa790" - scryptsy@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/scryptsy/-/scryptsy-1.2.1.tgz#a3225fa4b2524f802700761e2855bdf3b2d92163" dependencies: pbkdf2 "^3.0.3" +scryptsy@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/scryptsy/-/scryptsy-2.1.0.tgz#8d1e8d0c025b58fdd25b6fa9a0dc905ee8faa790" + integrity sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w== + secp256k1@^3.0.1: version "3.5.0" resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-3.5.0.tgz#677d3b8a8e04e1a5fa381a1ae437c54207b738d0" @@ -14847,10 +14917,6 @@ semver-sort@0.0.4: version "5.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" -semver@6.2.0: - version "6.2.0" - resolved "https://registry.npmjs.org/semver/-/semver-6.2.0.tgz#4d813d9590aaf8a9192693d6c85b9344de5901db" - semver@^5.5.1: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" @@ -14859,7 +14925,7 @@ semver@^5.6.0, semver@^5.7.0: version "5.7.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" -semver@^6.0.0, semver@^6.2.0: +semver@^6.0.0, semver@^6.2.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" @@ -17041,10 +17107,12 @@ wcwidth@^1.0.0: dependencies: defaults "^1.0.3" -web3-bzz@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-bzz/-/web3-bzz-1.2.1.tgz#c3bd1e8f0c02a13cd6d4e3c3e9e1713f144f6f0d" +web3-bzz@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.2.4.tgz#a4adb7a8cba3d260de649bdb1f14ed359bfb3821" + integrity sha512-MqhAo/+0iQSMBtt3/QI1rU83uvF08sYq8r25+OUZ+4VtihnYsmkkca+rdU0QbRyrXY2/yGIpI46PFdh0khD53A== dependencies: + "@types/node" "^10.12.18" got "9.6.0" swarm-js "0.1.39" underscore "1.9.1" @@ -17057,13 +17125,14 @@ web3-core-helpers@1.0.0-beta.34: web3-eth-iban "1.0.0-beta.34" web3-utils "1.0.0-beta.34" -web3-core-helpers@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-core-helpers/-/web3-core-helpers-1.2.1.tgz#f5f32d71c60a4a3bd14786118e633ce7ca6d5d0d" +web3-core-helpers@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.2.4.tgz#ffd425861f4d66b3f38df032afdb39ea0971fc0f" + integrity sha512-U7wbsK8IbZvF3B7S+QMSNP0tni/6VipnJkB0tZVEpHEIV2WWeBHYmZDnULWcsS/x/jn9yKhJlXIxWGsEAMkjiw== dependencies: underscore "1.9.1" - web3-eth-iban "1.2.1" - web3-utils "1.2.1" + web3-eth-iban "1.2.4" + web3-utils "1.2.4" web3-core-helpers@2.0.0-alpha: version "2.0.0-alpha" @@ -17075,15 +17144,16 @@ web3-core-helpers@2.0.0-alpha: web3-eth-iban "2.0.0-alpha" web3-utils "2.0.0-alpha" -web3-core-method@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-core-method/-/web3-core-method-1.2.1.tgz#9df1bafa2cd8be9d9937e01c6a47fc768d15d90a" +web3-core-method@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.2.4.tgz#a0fbc50b8ff5fd214021435cc2c6d1e115807aed" + integrity sha512-8p9kpL7di2qOVPWgcM08kb+yKom0rxRCMv6m/K+H+yLSxev9TgMbCgMSbPWAHlyiF3SJHw7APFKahK5Z+8XT5A== dependencies: underscore "1.9.1" - web3-core-helpers "1.2.1" - web3-core-promievent "1.2.1" - web3-core-subscriptions "1.2.1" - web3-utils "1.2.1" + web3-core-helpers "1.2.4" + web3-core-promievent "1.2.4" + web3-core-subscriptions "1.2.4" + web3-utils "1.2.4" web3-core-method@2.0.0-alpha: version "2.0.0-alpha" @@ -17098,30 +17168,33 @@ web3-core-method@2.0.0-alpha: web3-core-subscriptions "2.0.0-alpha" web3-utils "2.0.0-alpha" -web3-core-promievent@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-core-promievent/-/web3-core-promievent-1.2.1.tgz#003e8a3eb82fb27b6164a6d5b9cad04acf733838" +web3-core-promievent@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.2.4.tgz#75e5c0f2940028722cdd21ba503ebd65272df6cb" + integrity sha512-gEUlm27DewUsfUgC3T8AxkKi8Ecx+e+ZCaunB7X4Qk3i9F4C+5PSMGguolrShZ7Zb6717k79Y86f3A00O0VAZw== dependencies: any-promise "1.3.0" eventemitter3 "3.1.2" -web3-core-requestmanager@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-core-requestmanager/-/web3-core-requestmanager-1.2.1.tgz#fa2e2206c3d738db38db7c8fe9c107006f5c6e3d" +web3-core-requestmanager@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.2.4.tgz#0a7020a23fb91c6913c611dfd3d8c398d1e4b4a8" + integrity sha512-eZJDjyNTDtmSmzd3S488nR/SMJtNnn/GuwxnMh3AzYCqG3ZMfOylqTad2eYJPvc2PM5/Gj1wAMQcRpwOjjLuPg== dependencies: underscore "1.9.1" - web3-core-helpers "1.2.1" - web3-providers-http "1.2.1" - web3-providers-ipc "1.2.1" - web3-providers-ws "1.2.1" + web3-core-helpers "1.2.4" + web3-providers-http "1.2.4" + web3-providers-ipc "1.2.4" + web3-providers-ws "1.2.4" -web3-core-subscriptions@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-core-subscriptions/-/web3-core-subscriptions-1.2.1.tgz#8c2368a839d4eec1c01a4b5650bbeb82d0e4a099" +web3-core-subscriptions@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.2.4.tgz#0dc095b5cfd82baa527a39796e3515a846b21b99" + integrity sha512-3D607J2M8ymY9V+/WZq4MLlBulwCkwEjjC2U+cXqgVO1rCyVqbxZNCmHyNYHjDDCxSEbks9Ju5xqJxDSxnyXEw== dependencies: eventemitter3 "3.1.2" underscore "1.9.1" - web3-core-helpers "1.2.1" + web3-core-helpers "1.2.4" web3-core-subscriptions@2.0.0-alpha: version "2.0.0-alpha" @@ -17131,14 +17204,18 @@ web3-core-subscriptions@2.0.0-alpha: eventemitter3 "^3.1.0" lodash "^4.17.11" -web3-core@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-core/-/web3-core-1.2.1.tgz#7278b58fb6495065e73a77efbbce781a7fddf1a9" +web3-core@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.2.4.tgz#2df13b978dcfc59c2abaa887d27f88f21ad9a9d6" + integrity sha512-CHc27sMuET2cs1IKrkz7xzmTdMfZpYswe7f0HcuyneTwS1yTlTnHyqjAaTy0ZygAb/x4iaVox+Gvr4oSAqSI+A== dependencies: - web3-core-helpers "1.2.1" - web3-core-method "1.2.1" - web3-core-requestmanager "1.2.1" - web3-utils "1.2.1" + "@types/bignumber.js" "^5.0.0" + "@types/bn.js" "^4.11.4" + "@types/node" "^12.6.1" + web3-core-helpers "1.2.4" + web3-core-method "1.2.4" + web3-core-requestmanager "1.2.4" + web3-utils "1.2.4" web3-core@2.0.0-alpha: version "2.0.0-alpha" @@ -17152,13 +17229,14 @@ web3-core@2.0.0-alpha: web3-providers "2.0.0-alpha" web3-utils "2.0.0-alpha" -web3-eth-abi@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.2.1.tgz#9b915b1c9ebf82f70cca631147035d5419064689" +web3-eth-abi@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.2.4.tgz#5b73e5ef70b03999227066d5d1310b168845e2b8" + integrity sha512-8eLIY4xZKoU3DSVu1pORluAw9Ru0/v4CGdw5so31nn+7fR8zgHMgwbFe0aOqWQ5VU42PzMMXeIJwt4AEi2buFg== dependencies: ethers "4.0.0-beta.3" underscore "1.9.1" - web3-utils "1.2.1" + web3-utils "1.2.4" web3-eth-abi@^1.0.0-beta.24: version "1.0.0-beta.34" @@ -17169,47 +17247,52 @@ web3-eth-abi@^1.0.0-beta.24: web3-core-helpers "1.0.0-beta.34" web3-utils "1.0.0-beta.34" -web3-eth-accounts@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-eth-accounts/-/web3-eth-accounts-1.2.1.tgz#2741a8ef337a7219d57959ac8bd118b9d68d63cf" +web3-eth-accounts@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.2.4.tgz#ada6edc49542354328a85cafab067acd7f88c288" + integrity sha512-04LzT/UtWmRFmi4hHRewP5Zz43fWhuHiK5XimP86sUQodk/ByOkXQ3RoXyGXFMNoRxdcAeRNxSfA2DpIBc9xUw== dependencies: + "@web3-js/scrypt-shim" "^0.1.0" any-promise "1.3.0" crypto-browserify "3.12.0" eth-lib "0.2.7" - scryptsy "2.1.0" - semver "6.2.0" + ethereumjs-common "^1.3.2" + ethereumjs-tx "^2.1.1" underscore "1.9.1" uuid "3.3.2" - web3-core "1.2.1" - web3-core-helpers "1.2.1" - web3-core-method "1.2.1" - web3-utils "1.2.1" + web3-core "1.2.4" + web3-core-helpers "1.2.4" + web3-core-method "1.2.4" + web3-utils "1.2.4" -web3-eth-contract@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-eth-contract/-/web3-eth-contract-1.2.1.tgz#3542424f3d341386fd9ff65e78060b85ac0ea8c4" +web3-eth-contract@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.2.4.tgz#68ef7cc633232779b0a2c506a810fbe903575886" + integrity sha512-b/9zC0qjVetEYnzRA1oZ8gF1OSSUkwSYi5LGr4GeckLkzXP7osEnp9lkO/AQcE4GpG+l+STnKPnASXJGZPgBRQ== dependencies: + "@types/bn.js" "^4.11.4" underscore "1.9.1" - web3-core "1.2.1" - web3-core-helpers "1.2.1" - web3-core-method "1.2.1" - web3-core-promievent "1.2.1" - web3-core-subscriptions "1.2.1" - web3-eth-abi "1.2.1" - web3-utils "1.2.1" + web3-core "1.2.4" + web3-core-helpers "1.2.4" + web3-core-method "1.2.4" + web3-core-promievent "1.2.4" + web3-core-subscriptions "1.2.4" + web3-eth-abi "1.2.4" + web3-utils "1.2.4" -web3-eth-ens@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-eth-ens/-/web3-eth-ens-1.2.1.tgz#a0e52eee68c42a8b9865ceb04e5fb022c2d971d5" +web3-eth-ens@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.2.4.tgz#b95b3aa99fb1e35c802b9e02a44c3046a3fa065e" + integrity sha512-g8+JxnZlhdsCzCS38Zm6R/ngXhXzvc3h7bXlxgKU4coTzLLoMpgOAEz71GxyIJinWTFbLXk/WjNY0dazi9NwVw== dependencies: eth-ens-namehash "2.0.8" underscore "1.9.1" - web3-core "1.2.1" - web3-core-helpers "1.2.1" - web3-core-promievent "1.2.1" - web3-eth-abi "1.2.1" - web3-eth-contract "1.2.1" - web3-utils "1.2.1" + web3-core "1.2.4" + web3-core-helpers "1.2.4" + web3-core-promievent "1.2.4" + web3-eth-abi "1.2.4" + web3-eth-contract "1.2.4" + web3-utils "1.2.4" web3-eth-iban@1.0.0-beta.34: version "1.0.0-beta.34" @@ -17218,12 +17301,13 @@ web3-eth-iban@1.0.0-beta.34: bn.js "4.11.6" web3-utils "1.0.0-beta.34" -web3-eth-iban@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-eth-iban/-/web3-eth-iban-1.2.1.tgz#2c3801718946bea24e9296993a975c80b5acf880" +web3-eth-iban@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.2.4.tgz#8e0550fd3fd8e47a39357d87fe27dee9483ee476" + integrity sha512-D9HIyctru/FLRpXakRwmwdjb5bWU2O6UE/3AXvRm6DCOf2e+7Ve11qQrPtaubHfpdW3KWjDKvlxV9iaFv/oTMQ== dependencies: bn.js "4.11.8" - web3-utils "1.2.1" + web3-utils "1.2.4" web3-eth-iban@2.0.0-alpha: version "2.0.0-alpha" @@ -17233,41 +17317,45 @@ web3-eth-iban@2.0.0-alpha: bn.js "4.11.8" web3-utils "2.0.0-alpha" -web3-eth-personal@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-eth-personal/-/web3-eth-personal-1.2.1.tgz#244e9911b7b482dc17c02f23a061a627c6e47faf" +web3-eth-personal@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.2.4.tgz#3224cca6851c96347d9799b12c1b67b2a6eb232b" + integrity sha512-5Russ7ZECwHaZXcN3DLuLS7390Vzgrzepl4D87SD6Sn1DHsCZtvfdPIYwoTmKNp69LG3mORl7U23Ga5YxqkICw== dependencies: - web3-core "1.2.1" - web3-core-helpers "1.2.1" - web3-core-method "1.2.1" - web3-net "1.2.1" - web3-utils "1.2.1" + "@types/node" "^12.6.1" + web3-core "1.2.4" + web3-core-helpers "1.2.4" + web3-core-method "1.2.4" + web3-net "1.2.4" + web3-utils "1.2.4" -web3-eth@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-eth/-/web3-eth-1.2.1.tgz#b9989e2557c73a9e8ffdc107c6dafbe72c79c1b0" +web3-eth@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.2.4.tgz#24c3b1f1ac79351bbfb808b2ab5c585fa57cdd00" + integrity sha512-+j+kbfmZsbc3+KJpvHM16j1xRFHe2jBAniMo1BHKc3lho6A8Sn9Buyut6odubguX2AxoRArCdIDCkT9hjUERpA== dependencies: underscore "1.9.1" - web3-core "1.2.1" - web3-core-helpers "1.2.1" - web3-core-method "1.2.1" - web3-core-subscriptions "1.2.1" - web3-eth-abi "1.2.1" - web3-eth-accounts "1.2.1" - web3-eth-contract "1.2.1" - web3-eth-ens "1.2.1" - web3-eth-iban "1.2.1" - web3-eth-personal "1.2.1" - web3-net "1.2.1" - web3-utils "1.2.1" + web3-core "1.2.4" + web3-core-helpers "1.2.4" + web3-core-method "1.2.4" + web3-core-subscriptions "1.2.4" + web3-eth-abi "1.2.4" + web3-eth-accounts "1.2.4" + web3-eth-contract "1.2.4" + web3-eth-ens "1.2.4" + web3-eth-iban "1.2.4" + web3-eth-personal "1.2.4" + web3-net "1.2.4" + web3-utils "1.2.4" -web3-net@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-net/-/web3-net-1.2.1.tgz#edd249503315dd5ab4fa00220f6509d95bb7ab10" +web3-net@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.2.4.tgz#1d246406d3aaffbf39c030e4e98bce0ca5f25458" + integrity sha512-wKOsqhyXWPSYTGbp7ofVvni17yfRptpqoUdp3SC8RAhDmGkX6irsiT9pON79m6b3HUHfLoBilFQyt/fTUZOf7A== dependencies: - web3-core "1.2.1" - web3-core-method "1.2.1" - web3-utils "1.2.1" + web3-core "1.2.4" + web3-core-method "1.2.4" + web3-utils "1.2.4" web3-provider-engine@14.0.6: version "14.0.6" @@ -17344,28 +17432,31 @@ web3-provider-engine@^13.3.2: xhr "^2.2.0" xtend "^4.0.1" -web3-providers-http@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-providers-http/-/web3-providers-http-1.2.1.tgz#c93ea003a42e7b894556f7e19dd3540f947f5013" +web3-providers-http@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.2.4.tgz#514fcad71ae77832c2c15574296282fbbc5f4a67" + integrity sha512-dzVCkRrR/cqlIrcrWNiPt9gyt0AZTE0J+MfAu9rR6CyIgtnm1wFUVVGaxYRxuTGQRO4Dlo49gtoGwaGcyxqiTw== dependencies: - web3-core-helpers "1.2.1" + web3-core-helpers "1.2.4" xhr2-cookies "1.1.0" -web3-providers-ipc@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-providers-ipc/-/web3-providers-ipc-1.2.1.tgz#017bfc687a8fc5398df2241eb98f135e3edd672c" +web3-providers-ipc@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.2.4.tgz#9d6659f8d44943fb369b739f48df09092be459bd" + integrity sha512-8J3Dguffin51gckTaNrO3oMBo7g+j0UNk6hXmdmQMMNEtrYqw4ctT6t06YOf9GgtOMjSAc1YEh3LPrvgIsR7og== dependencies: oboe "2.1.4" underscore "1.9.1" - web3-core-helpers "1.2.1" + web3-core-helpers "1.2.4" -web3-providers-ws@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-providers-ws/-/web3-providers-ws-1.2.1.tgz#2d941eaf3d5a8caa3214eff8dc16d96252b842cb" +web3-providers-ws@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.2.4.tgz#099ee271ee03f6ea4f5df9cfe969e83f4ce0e36f" + integrity sha512-F/vQpDzeK+++oeeNROl1IVTufFCwCR2hpWe5yRXN0ApLwHqXrMI7UwQNdJ9iyibcWjJf/ECbauEEQ8CHgE+MYQ== dependencies: + "@web3-js/websocket" "^1.0.29" underscore "1.9.1" - web3-core-helpers "1.2.1" - websocket "github:web3-js/WebSocket-Node#polyfill/globalThis" + web3-core-helpers "1.2.4" web3-providers@2.0.0-alpha: version "2.0.0-alpha" @@ -17383,14 +17474,15 @@ web3-providers@2.0.0-alpha: websocket "^1.0.28" xhr2-cookies "1.1.0" -web3-shh@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-shh/-/web3-shh-1.2.1.tgz#4460e3c1e07faf73ddec24ccd00da46f89152b0c" +web3-shh@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.2.4.tgz#5c8ff5ab624a3b14f08af0d24d2b16c10e9f70dd" + integrity sha512-z+9SCw0dE+69Z/Hv8809XDbLj7lTfEv9Sgu8eKEIdGntZf4v7ewj5rzN5bZZSz8aCvfK7Y6ovz1PBAu4QzS4IQ== dependencies: - web3-core "1.2.1" - web3-core-method "1.2.1" - web3-core-subscriptions "1.2.1" - web3-net "1.2.1" + web3-core "1.2.4" + web3-core-method "1.2.4" + web3-core-subscriptions "1.2.4" + web3-net "1.2.4" web3-typescript-typings@^0.10.2: version "0.10.2" @@ -17410,15 +17502,17 @@ web3-utils@1.0.0-beta.34: underscore "1.8.3" utf8 "2.1.1" -web3-utils@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3-utils/-/web3-utils-1.2.1.tgz#21466e38291551de0ab34558de21512ac4274534" +web3-utils@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.2.4.tgz#96832a39a66b05bf8862a5b0bdad2799d709d951" + integrity sha512-+S86Ip+jqfIPQWvw2N/xBQq5JNqCO0dyvukGdJm8fEWHZbckT4WxSpHbx+9KLEWY4H4x9pUwnoRkK87pYyHfgQ== dependencies: bn.js "4.11.8" eth-lib "0.2.7" + ethereum-bloom-filters "^1.0.6" ethjs-unit "0.1.6" number-to-bn "1.7.0" - randomhex "0.1.5" + randombytes "^2.1.0" underscore "1.9.1" utf8 "3.0.0" @@ -17437,17 +17531,19 @@ web3-utils@2.0.0-alpha: randombytes "^2.1.0" utf8 "2.1.1" -web3@1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/web3/-/web3-1.2.1.tgz#5d8158bcca47838ab8c2b784a2dee4c3ceb4179b" +web3@1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/web3/-/web3-1.2.4.tgz#6e7ab799eefc9b4648c2dab63003f704a1d5e7d9" + integrity sha512-xPXGe+w0x0t88Wj+s/dmAdASr3O9wmA9mpZRtixGZxmBexAF0MjfqYM+MS4tVl5s11hMTN3AZb8cDD4VLfC57A== dependencies: - web3-bzz "1.2.1" - web3-core "1.2.1" - web3-eth "1.2.1" - web3-eth-personal "1.2.1" - web3-net "1.2.1" - web3-shh "1.2.1" - web3-utils "1.2.1" + "@types/node" "^12.6.1" + web3-bzz "1.2.4" + web3-core "1.2.4" + web3-eth "1.2.4" + web3-eth-personal "1.2.4" + web3-net "1.2.4" + web3-shh "1.2.4" + web3-utils "1.2.4" webidl-conversions@^4.0.2: version "4.0.2" @@ -17606,16 +17702,6 @@ websocket@^1.0.26: typedarray-to-buffer "^3.1.5" yaeti "^0.0.6" -"websocket@github:web3-js/WebSocket-Node#polyfill/globalThis": - version "1.0.29" - resolved "https://codeload.github.com/web3-js/WebSocket-Node/tar.gz/905deb4812572b344f5801f8c9ce8bb02799d82e" - dependencies: - debug "^2.2.0" - es5-ext "^0.10.50" - nan "^2.14.0" - typedarray-to-buffer "^3.1.5" - yaeti "^0.0.6" - whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: version "1.0.5" resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"