diff --git a/contracts/coordinator/package.json b/contracts/coordinator/package.json index 876577909c..6031a73247 100644 --- a/contracts/coordinator/package.json +++ b/contracts/coordinator/package.json @@ -54,6 +54,7 @@ "@0x/dev-utils": "^2.3.0", "@0x/sol-compiler": "^3.1.12", "@0x/tslint-config": "^3.0.1", + "@0x/utils": "^4.3.1", "@types/lodash": "4.14.104", "@types/mocha": "^5.2.7", "@types/node": "*", diff --git a/contracts/staking/contracts/src/core/MixinPools.sol b/contracts/staking/contracts/src/core/MixinPools.sol index 6fbe10ba08..14eaea23c8 100644 --- a/contracts/staking/contracts/src/core/MixinPools.sol +++ b/contracts/staking/contracts/src/core/MixinPools.sol @@ -24,6 +24,7 @@ import "../immutable/MixinConstants.sol"; import "../interfaces/IStakingEvents.sol"; import "./MixinRewardVault.sol"; import "./MixinSignatureValidator.sol"; +import "../libs/LibEIP712Hash.sol"; contract MixinPools is SafeMath, @@ -79,7 +80,7 @@ contract MixinPools is ); require( - _isValidSignature(hashSignedByMaker, makerAddress, makerSignature), + _isValidMakerSignature(poolId, makerAddress, makerSignature), "INVALID_MAKER_SIGNATURE" ); @@ -101,14 +102,32 @@ contract MixinPools is _unrecordMaker(poolId, makerAddress); } - /* - function _isValidMakerSignature(address makerAddress, bytes memory makerSignature) + function _isValidMakerSignature(bytes32 poolId, address makerAddress, bytes memory makerSignature) internal + view returns (bool isValid) { - + bytes32 approvalHash = _getStakingPoolApprovalMessageHash(poolId, makerAddress); + isValid = _isValidSignature(approvalHash, makerAddress, makerSignature); + return isValid; + } + + function _getStakingPoolApprovalMessageHash(bytes32 poolId, address makerAddress) + internal + view + returns (bytes32 approvalHash) + { + StakingPoolApproval memory approval = StakingPoolApproval({ + poolId: poolId, + makerAddress: makerAddress + }); + + // Hash approval message and check signer address + address verifierAddress = address(this); + approvalHash = LibEIP712Hash._hashStakingPoolApprovalMessage(approval, verifierAddress, CHAIN_ID); + + return approvalHash; } - */ function _getMakerPoolId(address makerAddress) internal diff --git a/contracts/staking/contracts/src/core/MixinSignatureValidator.sol b/contracts/staking/contracts/src/core/MixinSignatureValidator.sol index 15763a299f..2c4cc876e4 100644 --- a/contracts/staking/contracts/src/core/MixinSignatureValidator.sol +++ b/contracts/staking/contracts/src/core/MixinSignatureValidator.sol @@ -16,10 +16,12 @@ pragma solidity ^0.5.5; import "@0x/contracts-utils/contracts/src/LibBytes.sol"; import "../interfaces/IStructs.sol"; import "../interfaces/IWallet.sol"; +import "../immutable/MixinConstants.sol"; contract MixinSignatureValidator is - IStructs + IStructs, + MixinConstants { using LibBytes for bytes; @@ -121,17 +123,7 @@ contract MixinSignatureValidator is // Signature verified by wallet contract. // If used with an order, the maker of the order is the wallet contract. } else if (signatureType == SignatureType.Wallet) { - isValid = isValidWalletSignature( - hash, - signerAddress, - signature - ); - return isValid; - - // Signature verified by wallet contract. - // If used with an order, the maker of the order is the wallet contract. - } else if (signatureType == SignatureType.Wallet) { - isValid = isValidWalletSignature( + isValid = _isValidWalletSignature( hash, signerAddress, signature @@ -153,7 +145,7 @@ contract MixinSignatureValidator is /// and defines its own signature verification method. /// @param signature Proof that the hash has been signed by signer. /// @return True if signature is valid for given wallet.. - function isValidWalletSignature( + function _isValidWalletSignature( bytes32 hash, address walletAddress, bytes memory signature @@ -162,36 +154,28 @@ contract MixinSignatureValidator is view returns (bool isValid) { + // contruct hash as bytes, so that it is a valid EIP-1271 payload + bytes memory hashAsBytes = new bytes(32); + assembly { + mstore(add(hashAsBytes, 32), hash) + } + + // Static call `isValidSignature` in the destination wallet bytes memory callData = abi.encodeWithSelector( IWallet(walletAddress).isValidSignature.selector, hash, signature ); - assembly { - let cdStart := add(callData, 32) - let success := staticcall( - gas, // forward all gas - walletAddress, // address of Wallet contract - cdStart, // pointer to start of input - mload(callData), // length of input - cdStart, // write output over input - 32 // output size is 32 bytes - ) + (bool success, bytes memory result) = walletAddress.staticcall(callData); - switch success - case 0 { - // Revert with `Error("WALLET_ERROR")` - mstore(0, 0x08c379a000000000000000000000000000000000000000000000000000000000) - mstore(32, 0x0000002000000000000000000000000000000000000000000000000000000000) - mstore(64, 0x0000000c57414c4c45545f4552524f5200000000000000000000000000000000) - mstore(96, 0) - revert(0, 100) - } - case 1 { - // Signature is valid if call did not revert and returned true - isValid := mload(cdStart) - } - } + // Sanity check call and extract the magic value + require( + success, + "WALLET_ERROR" + ); + bytes4 magicValue = result.readBytes4(0); + + isValid = (magicValue == EIP1271_MAGIC_VALUE); return isValid; } } \ No newline at end of file diff --git a/contracts/staking/contracts/src/immutable/MixinConstants.sol b/contracts/staking/contracts/src/immutable/MixinConstants.sol index 46b434ef38..48aa6d5f11 100644 --- a/contracts/staking/contracts/src/immutable/MixinConstants.sol +++ b/contracts/staking/contracts/src/immutable/MixinConstants.sol @@ -35,6 +35,9 @@ contract MixinConstants { uint64 constant public INITIAL_TIMELOCK_PERIOD = INITIAL_EPOCH; + // bytes4(keccak256("isValidSignature(bytes,bytes)") + bytes4 constant internal EIP1271_MAGIC_VALUE = 0x20c13b0b; + uint64 constant public EPOCH_PERIOD_IN_SECONDS = 1000; // @TODO SET FOR DEPLOYMENT uint64 constant public TIMELOCK_PERIOD_IN_EPOCHS = 3; // @TODO SET FOR DEPLOYMENT @@ -42,4 +45,6 @@ contract MixinConstants { uint256 constant public COBB_DOUGLAS_ALPHA_DENOMINATOR = 6; // @TODO SET FOR DEPLOYMENT uint256 constant public REWARD_PAYOUT_DELEGATED_STAKE_PERCENT_VALUE = 90; // @TODO SET FOR DEPLOYMENT + + uint256 constant public CHAIN_ID = 1; // @TODO SET FOR DEPLOYMENT } diff --git a/contracts/staking/contracts/src/interfaces/IStructs.sol b/contracts/staking/contracts/src/interfaces/IStructs.sol index 2d3fc0991c..59ebf50146 100644 --- a/contracts/staking/contracts/src/interfaces/IStructs.sol +++ b/contracts/staking/contracts/src/interfaces/IStructs.sol @@ -31,6 +31,11 @@ interface IStructs { NSignatureTypes // 0x05, number of signature types. Always leave at end. } + struct StakingPoolApproval { + bytes32 poolId; + address makerAddress; + } + struct Timelock { uint64 lockedAt; uint96 total; diff --git a/contracts/staking/contracts/src/interfaces/IWallet.sol b/contracts/staking/contracts/src/interfaces/IWallet.sol index 359ac371bc..6f6be7b3ef 100644 --- a/contracts/staking/contracts/src/interfaces/IWallet.sol +++ b/contracts/staking/contracts/src/interfaces/IWallet.sol @@ -16,15 +16,17 @@ pragma solidity ^0.5.5; interface IWallet /* is EIP-1271 */ { - /// @dev Verifies that a signature is valid. - /// @param hash Message hash that is signed. - /// @param signature Proof of signing. - /// @return The function selector for this function + /// @dev Should return whether the signature provided is valid for the provided data + /// @param data Arbitrary length data signed on the behalf of address(this) + /// @param signature Signature byte array associated with _data + /// + /// MUST return the bytes4 magic value 0x20c13b0b when function passes. + /// MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5) + /// MUST allow external calls function isValidSignature( - bytes32 hash, - bytes calldata signature - ) + bytes calldata data, + bytes calldata signature) external view - returns (bytes32 isValidSignatureSelector); + returns (bytes4 magicValue); } \ No newline at end of file diff --git a/contracts/staking/contracts/src/libs/LibEIP712Hash.sol b/contracts/staking/contracts/src/libs/LibEIP712Hash.sol new file mode 100644 index 0000000000..42d3f5f792 --- /dev/null +++ b/contracts/staking/contracts/src/libs/LibEIP712Hash.sol @@ -0,0 +1,100 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.5; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-utils/contracts/src/LibEIP712.sol"; +import "../interfaces/IStructs.sol"; + + +library LibEIP712Hash +{ + // EIP712 Domain Name value for the Staking contract + string constant internal EIP712_STAKING_DOMAIN_NAME = "0x Protocol Staking"; + + // EIP712 Domain Version value for the Staking contract + string constant internal EIP712_STAKING_DOMAIN_VERSION = "1.0.0"; + + // Hash for the EIP712 StakingPool approval message + // keccak256(abi.encodePacked( + // "StakingPoolApproval(", + // "bytes32 poolId,", + // "address makerAddress", + // ")" + // )); + bytes32 constant internal EIP712_STAKING_POOL_APPROVAL_SCHEMA_HASH = 0x9b699f12ef1c0f7b43076182dcccc0c548c9a784cfcf27114f98d684e06826b6; + + /// @dev Calculated the EIP712 hash of the StakingPool approval mesasage using the domain separator of this contract. + /// @param approval StakingPool approval message containing the transaction hash, transaction signature, and expiration of the approval. + /// @return EIP712 hash of the StakingPool approval message with the domain separator of this contract. + function _hashStakingPoolApprovalMessage( + IStructs.StakingPoolApproval memory approval, + address verifierAddress, + uint256 chainId + ) + internal + pure + returns (bytes32 approvalHash) + { + approvalHash = _hashEIP712StakingMessage( + _hashStakingPoolApproval(approval), + verifierAddress, + chainId + ); + return approvalHash; + } + + /// @dev Calculates EIP712 encoding for a hash struct in the EIP712 domain + /// of this contract. + /// @param hashStruct The EIP712 hash struct. + /// @return EIP712 hash applied to this EIP712 Domain. + function _hashEIP712StakingMessage( + bytes32 hashStruct, + address verifierAddress, + uint256 chainId + ) + internal + pure + returns (bytes32 result) + { + bytes32 eip712StakingDomainHash = LibEIP712._hashEIP712Domain( + EIP712_STAKING_DOMAIN_NAME, + EIP712_STAKING_DOMAIN_VERSION, + chainId, + verifierAddress + ); + return LibEIP712._hashEIP712Message(eip712StakingDomainHash, hashStruct); + } + + /// @dev Calculated the EIP712 hash of the StakingPool approval mesasage with no domain separator. + /// @param approval StakingPool approval message containing the transaction hash, transaction signature, and expiration of the approval. + /// @return EIP712 hash of the StakingPool approval message with no domain separator. + function _hashStakingPoolApproval(IStructs.StakingPoolApproval memory approval) + internal + pure + returns (bytes32 result) + { + result = keccak256(abi.encodePacked( + EIP712_STAKING_POOL_APPROVAL_SCHEMA_HASH, + approval.poolId, + approval.makerAddress + )); + return result; + } +} diff --git a/contracts/staking/contracts/src/wrappers/MixinPoolsWrapper.sol b/contracts/staking/contracts/src/wrappers/MixinPoolsWrapper.sol index 5891f5ed8e..405045c12a 100644 --- a/contracts/staking/contracts/src/wrappers/MixinPoolsWrapper.sol +++ b/contracts/staking/contracts/src/wrappers/MixinPoolsWrapper.sol @@ -89,4 +89,20 @@ contract MixinPoolsWrapper is makerAddresses = _getMakerAddressesForPool(makerId); return makerAddresses; } + + function isValidMakerSignature(bytes32 poolId, address makerAddress, bytes calldata makerSignature) + external + view + returns (bool isValid) + { + return _isValidMakerSignature(poolId, makerAddress, makerSignature); + } + + function getStakingPoolApprovalMessageHash(bytes32 poolId, address makerAddress) + external + view + returns (bytes32 approvalHash) + { + return _getStakingPoolApprovalMessageHash(poolId, makerAddress); + } } diff --git a/contracts/staking/contracts/src/wrappers/MixinSignatureValidatorWrapper.sol b/contracts/staking/contracts/src/wrappers/MixinSignatureValidatorWrapper.sol index 41dce33df5..ca5efd0e4c 100644 --- a/contracts/staking/contracts/src/wrappers/MixinSignatureValidatorWrapper.sol +++ b/contracts/staking/contracts/src/wrappers/MixinSignatureValidatorWrapper.sol @@ -14,6 +14,7 @@ pragma solidity ^0.5.5; import "../core/MixinSignatureValidator.sol"; +import "../libs/LibEIP712Hash.sol"; contract MixinSignatureValidatorWrapper is @@ -28,9 +29,9 @@ contract MixinSignatureValidatorWrapper is function isValidSignature( bytes32 hash, address signerAddress, - bytes memory signature + bytes calldata signature ) - internal + external view returns (bool isValid) { diff --git a/contracts/staking/contracts/test/TestSignatureValidator.sol b/contracts/staking/contracts/test/TestSignatureValidator.sol new file mode 100644 index 0000000000..5959708924 --- /dev/null +++ b/contracts/staking/contracts/test/TestSignatureValidator.sol @@ -0,0 +1,40 @@ +/* + + Copyright 2018 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.5; +pragma experimental ABIEncoderV2; + +import "@0x/contracts-exchange-libs/contracts/src/LibEIP712ExchangeDomain.sol"; +import "../src/MixinSignatureValidator.sol"; +import "../src/MixinTransactions.sol"; +import "../src/MixinExchangeRichErrors.sol"; + + +contract TestSignatureValidator is + LibEIP712ExchangeDomain, + MixinSignatureValidator, + MixinTransactions, + MixinExchangeRichErrors +{ + + // solhint-disable no-empty-blocks + constructor (uint256 chainId) + public + LibEIP712ExchangeDomain(chainId, address(0)) + {} +} diff --git a/contracts/staking/package.json b/contracts/staking/package.json index 0b7aead8ba..94e182bc6e 100644 --- a/contracts/staking/package.json +++ b/contracts/staking/package.json @@ -57,6 +57,7 @@ "@0x/tslint-config": "^3.0.1", "@types/lodash": "4.14.104", "@types/node": "*", + "@0x/utils": "^4.3.1", "chai": "^4.0.1", "chai-as-promised": "^7.1.0", "chai-bignumber": "^3.0.0", @@ -71,12 +72,14 @@ "typescript": "3.0.1" }, "dependencies": { - "@0x/base-contract": "^5.0.5", - "@0x/contracts-utils": "^3.1.1", + "@0x/base-contract": "^5.1.0", + "@0x/contracts-test-utils": "^3.1.6", + "@0x/contracts-utils": "3.1.1", + "@0x/order-utils": "^7.2.0", "@0x/types": "^2.2.2", "@0x/typescript-typings": "^4.2.2", "@0x/utils": "^4.3.1", - "@0x/web3-wrapper": "^6.0.5", + "@0x/web3-wrapper": "^6.0.6", "ethereum-types": "^2.1.2", "ethereumjs-util": "^5.1.1", "lodash": "^4.17.11" diff --git a/contracts/staking/test/core_test.ts b/contracts/staking/test/core_test.ts index 5fe184ec85..a1e34b219c 100644 --- a/contracts/staking/test/core_test.ts +++ b/contracts/staking/test/core_test.ts @@ -29,6 +29,7 @@ describe('Staking Core', () => { // constants const ZRX_TOKEN_DECIMALS = new BigNumber(18); // tokens & addresses + let accounts: string[]; let owner: string; let exchange: string; let stakers: string[]; @@ -48,7 +49,7 @@ describe('Staking Core', () => { }); before(async () => { // create accounts - const accounts = await web3Wrapper.getAvailableAddressesAsync(); + accounts = await web3Wrapper.getAvailableAddressesAsync(); owner = accounts[0]; exchange = accounts[1]; stakers = accounts.slice(2, 5); @@ -774,7 +775,7 @@ describe('Staking Core', () => { expect(ownerRewardFloatingPoint).to.be.bignumber.equal(expectedOwnerReward); }); - it('pool management', async() => { + it.only('pool management', async() => { // create first pool const operatorAddress = stakers[0]; const operatorShare = 39; @@ -786,8 +787,15 @@ describe('Staking Core', () => { expect(nextPoolId).to.be.equal(expectedNextPoolId); // add maker to pool const makerAddress = makers[0]; - const makerSignature = "0x"; - await stakingWrapper.addMakerToPoolAsync(poolId, makerAddress, "0x00", makerSignature, operatorAddress); + const makerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(makerAddress)]; + const makerApproval = stakingWrapper.getSignedApprovalForStakingPool(poolId, makerAddress, makerPrivateKey); + + const onchainHash = await stakingWrapper.getStakingPoolApprovalMessageHashAsync(poolId, makerAddress); + console.log(`** ON-CHAIN HASH **\n`, onchainHash); + + console.log(JSON.stringify(makerApproval, null , 4)); + await stakingWrapper.addMakerToPoolAsync(poolId, makerAddress, "0x00", makerApproval.signature, operatorAddress); + throw new Error(`ADDED SUCCESSFULLY!`); // check the pool id of the maker const poolIdOfMaker = await stakingWrapper.getMakerPoolId(makerAddress); expect(poolIdOfMaker).to.be.equal(poolId); @@ -796,15 +804,16 @@ describe('Staking Core', () => { expect(makerAddressesForPool).to.be.deep.equal([makerAddress]); // try to add the same maker address again await expectTransactionFailedAsync( - stakingWrapper.addMakerToPoolAsync(poolId, makerAddress, "0x00", makerSignature, operatorAddress), + stakingWrapper.addMakerToPoolAsync(poolId, makerAddress, "0x00", makerApproval.signature, operatorAddress), RevertReason.MakerAddressAlreadyRegistered ); // try to add a new maker address from an address other than the pool operator const notOperatorAddress = owner; const anotherMakerAddress = makers[1]; - const anotherMakerSignature = "0x"; + const anotherMakerPrivateKey = constants.TESTRPC_PRIVATE_KEYS[accounts.indexOf(anotherMakerAddress)]; + const anotherMakerApproval = stakingWrapper.getSignedApprovalForStakingPool(poolId, anotherMakerAddress, anotherMakerPrivateKey); await expectTransactionFailedAsync( - stakingWrapper.addMakerToPoolAsync(poolId, anotherMakerAddress, "0x00", anotherMakerSignature, notOperatorAddress), + stakingWrapper.addMakerToPoolAsync(poolId, anotherMakerAddress, "0x00", anotherMakerApproval.signature, notOperatorAddress), RevertReason.OnlyCallableByPoolOperator ); // try to remove the maker address from an address other than the operator diff --git a/contracts/staking/test/utils/ApprovalFactory.ts b/contracts/staking/test/utils/ApprovalFactory.ts new file mode 100644 index 0000000000..21f5771807 --- /dev/null +++ b/contracts/staking/test/utils/ApprovalFactory.ts @@ -0,0 +1,42 @@ +import { signingUtils } from '@0x/contracts-test-utils'; +import { SignatureType } from '@0x/types'; +import { BigNumber } from '@0x/utils'; +import * as ethUtil from 'ethereumjs-util'; + +import { hashUtils } from './hash_utils'; +import { SignedStakingPoolApproval } from './types'; + +export class ApprovalFactory { + private readonly _privateKey: Buffer; + private readonly _verifyingContractAddress: string; + private readonly _chainId: number; + + constructor(privateKey: Buffer, verifyingContractAddress: string, chainId: number) { + this._privateKey = privateKey; + this._verifyingContractAddress = verifyingContractAddress; + this._chainId = chainId; + } + + public newSignedApproval( + poolId: string, + makerAddress: string, + signatureType: SignatureType = SignatureType.EthSign, + ): SignedStakingPoolApproval { + const approvalHashBuff = hashUtils.getStakingPoolApprovalHashBuffer( + poolId, + makerAddress, + this._verifyingContractAddress, + this._chainId + ); + console.log('*** APPROVAL HASH ***\n', approvalHashBuff); + const signatureBuff = signingUtils.signMessage(approvalHashBuff, this._privateKey, signatureType); + const signedApproval = { + makerAddress, + poolId, + verifyingContractAddress: this._verifyingContractAddress, + chainId: this._chainId, + signature: ethUtil.addHexPrefix(signatureBuff.toString('hex')), + }; + return signedApproval; + } +} diff --git a/contracts/staking/test/utils/constants.ts b/contracts/staking/test/utils/constants.ts index 41777aed31..dba0f10f81 100644 --- a/contracts/staking/test/utils/constants.ts +++ b/contracts/staking/test/utils/constants.ts @@ -10,4 +10,5 @@ export const constants = { INITIAL_TIMELOCK_PERIOD: new BigNumber(0), EPOCH_PERIOD_IN_SECONDS: new BigNumber(1000), // @TODO SET FOR DEPLOYMENT*/ TIMELOCK_PERIOD_IN_EPOCHS: new BigNumber(3), // @TODO SET FOR DEPLOYMENT + CHAIN_ID: 1, }; \ No newline at end of file diff --git a/contracts/staking/test/utils/hash_utils.ts b/contracts/staking/test/utils/hash_utils.ts new file mode 100644 index 0000000000..4e4faf8015 --- /dev/null +++ b/contracts/staking/test/utils/hash_utils.ts @@ -0,0 +1,32 @@ +import { eip712Utils } from '@0x/order-utils'; +import { signTypedDataUtils } from '@0x/utils'; +import * as _ from 'lodash'; + +export const hashUtils = { + getStakingPoolApprovalHashBuffer( + poolId: string, + makerAddress: string, + verifyingContractAddress: string, + chainId: number + ): Buffer { + const typedData = eip712Utils.createStakingPoolApprovalTypedData( + poolId, + makerAddress, + verifyingContractAddress, + chainId + ); + const hashBuffer = signTypedDataUtils.generateTypedDataHash(typedData); + return hashBuffer; + }, + getStakingPoolApprovalHashHex( + poolId: string, + makerAddress: string, + verifyingContractAddress: string, + chainId: number + ): string { + const hashHex = `0x${hashUtils + .getStakingPoolApprovalHashBuffer(poolId, makerAddress, verifyingContractAddress, chainId) + .toString('hex')}`; + return hashHex; + }, +}; diff --git a/contracts/staking/test/utils/staking_wrapper.ts b/contracts/staking/test/utils/staking_wrapper.ts index 84b6c5d5b5..25b7e500f7 100644 --- a/contracts/staking/test/utils/staking_wrapper.ts +++ b/contracts/staking/test/utils/staking_wrapper.ts @@ -1,14 +1,18 @@ -import { constants, LogDecoder, txDefaults } from '@0x/contracts-test-utils'; +import { LogDecoder, txDefaults } from '@0x/contracts-test-utils'; import { BigNumber } from '@0x/utils'; import { Web3Wrapper } from '@0x/web3-wrapper'; import * as chai from 'chai'; import { assetDataUtils } from '@0x/order-utils'; +import { SignatureType } from '@0x/types'; import { LogWithDecodedArgs, Provider, TransactionReceiptWithDecodedLogs } from 'ethereum-types'; import { artifacts as erc20Artifacts, DummyERC20TokenContract } from '@0x/contracts-erc20'; import { ERC20ProxyContract } from '@0x/contracts-asset-proxy'; import * as _ from 'lodash'; import { artifacts, StakingEEventArgs, StakingContract, StakingProxyContract, ZrxVaultContract, RewardVaultContract, LibMathTestContract } from '../../src'; +import { ApprovalFactory } from './ApprovalFactory'; +import { SignedStakingPoolApproval } from './types'; +import { constants } from './constants'; const expect = chai.expect; @@ -272,6 +276,41 @@ export class StakingWrapper { const makerAddresses = this.getStakingContract().getMakerAddressesForPool.getABIDecodedReturnData(returndata); return makerAddresses; } + public async isValidMakerSignatureAsync(poolId: string, makerAddress: string, makerSignature: string): Promise { + const calldata = this.getStakingContract().isValidMakerSignature.getABIEncodedTransactionData(poolId, makerAddress, makerSignature); + const returndata = await this._callAsync(calldata); + const isValid = this.getStakingContract().isValidMakerSignature.getABIDecodedReturnData(returndata); + return isValid; + } + public async getStakingPoolApprovalMessageHashAsync(poolId: string, makerAddress: string): Promise { + const calldata = this.getStakingContract().getStakingPoolApprovalMessageHash.getABIEncodedTransactionData(poolId, makerAddress); + const returndata = await this._callAsync(calldata); + const messageHash = this.getStakingContract().getStakingPoolApprovalMessageHash.getABIDecodedReturnData(returndata); + return messageHash; + } + public getSignedApprovalForStakingPool(poolId: string, makerAddress: string, makerPrivateKey: Buffer, signatureType: SignatureType = SignatureType.EthSign): SignedStakingPoolApproval { + const signedStakingPoolApproval = this.getSignedApprovalForStakingPoolFlexible( + poolId, + makerAddress, + makerPrivateKey, + this.getStakingProxyContract().address, + constants.CHAIN_ID, + signatureType + ); + return signedStakingPoolApproval; + } + public getSignedApprovalForStakingPoolFlexible( + poolId: string, + makerAddress: string, + makerPrivateKey: Buffer, + verifierAddress: string, + chainId: number, + signatureType: SignatureType = SignatureType.EthSign, + ): SignedStakingPoolApproval { + const approvalFactory = new ApprovalFactory(makerPrivateKey, verifierAddress, chainId); + const signedStakingPoolApproval = approvalFactory.newSignedApproval(poolId, makerAddress, signatureType); + return signedStakingPoolApproval; + } ///// EPOCHS ///// public async goToNextEpochAsync(): Promise { const calldata = this.getStakingContract().finalizeFees.getABIEncodedTransactionData(); diff --git a/contracts/staking/test/utils/types.ts b/contracts/staking/test/utils/types.ts new file mode 100644 index 0000000000..dd52138b64 --- /dev/null +++ b/contracts/staking/test/utils/types.ts @@ -0,0 +1,12 @@ +import { BigNumber } from '@0x/utils'; + +export interface StakingPoolApproval { + makerAddress: string, + poolId: string, + verifyingContractAddress: string, + chainId: number +} + +export interface SignedStakingPoolApproval extends StakingPoolApproval { + signature: string; +} \ No newline at end of file diff --git a/packages/order-utils/src/constants.ts b/packages/order-utils/src/constants.ts index 8cbf2b1134..9cdd85b677 100644 --- a/packages/order-utils/src/constants.ts +++ b/packages/order-utils/src/constants.ts @@ -149,6 +149,15 @@ export const constants = { { name: 'approvalExpirationTimeSeconds', type: 'uint256' }, ], }, + STAKING_DOMAIN_NAME: '0x Protocol Staking', + STAKING_DOMAIN_VERSION: '1.0.0', + STAKING_POOL_APPROVAL_SCHEMA: { + name: 'StakingPoolApproval', + parameters: [ + { name: 'poolId', type: 'bytes32' }, + { name: 'makerAddress', type: 'address' }, + ], + }, ERC20_METHOD_ABI, ERC721_METHOD_ABI, MULTI_ASSET_METHOD_ABI, diff --git a/packages/order-utils/src/eip712_utils.ts b/packages/order-utils/src/eip712_utils.ts index aa482c575e..db3620c635 100644 --- a/packages/order-utils/src/eip712_utils.ts +++ b/packages/order-utils/src/eip712_utils.ts @@ -125,4 +125,34 @@ export const eip712Utils = { ); return typedData; }, + /** + * Creates an Coordiantor typedData EIP712TypedData object for use with the Coordinator extension contract + * @return A typed data object + */ + createStakingPoolApprovalTypedData: ( + poolId: string, + makerAddress: string, + verifyingContractAddress: string, + chainId: number + ): EIP712TypedData => { + const domain = { + name: constants.STAKING_DOMAIN_NAME, + version: constants.STAKING_DOMAIN_VERSION, + verifyingContractAddress, + chainId + }; + const approval = { + poolId, + makerAddress, + }; + const typedData = eip712Utils.createTypedData( + constants.STAKING_POOL_APPROVAL_SCHEMA.name, + { + StakingPoolApproval: constants.STAKING_POOL_APPROVAL_SCHEMA.parameters, + }, + approval, + domain, + ); + return typedData; + }, }; diff --git a/packages/utils/src/sign_typed_data_utils.ts b/packages/utils/src/sign_typed_data_utils.ts index d1df23228a..d82434208b 100644 --- a/packages/utils/src/sign_typed_data_utils.ts +++ b/packages/utils/src/sign_typed_data_utils.ts @@ -69,6 +69,8 @@ export const signTypedDataUtils = { let deps = signTypedDataUtils._findDependencies(primaryType, types); deps = deps.filter(d => d !== primaryType); deps = [primaryType].concat(deps.sort()); + console.log('*** DEPS ****\n', JSON.stringify(deps, null, 4)); + console.log('*** TYPES ****\n', JSON.stringify(types, null, 4)); let result = ''; for (const dep of deps) { result += `${dep}(${types[dep].map(({ name, type }) => `${type} ${name}`).join(',')})`; diff --git a/yarn.lock b/yarn.lock index 70dac18ab8..849deff7c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -649,6 +649,13 @@ dependencies: "@0x/base-contract" "^4.0.3" +"@0x/abi-gen-wrappers@^4.2.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@0x/abi-gen-wrappers/-/abi-gen-wrappers-4.3.0.tgz#b36616b129f44072474a7d5739299e4b1068e3d4" + integrity sha512-KnpoHbbqfidECl4hTGOvBaCj1N3LASHcdNO38yOMo1eO4B0H+kuyDVFjN3nbNt7lnXTPJ+DdaQpoDVQwySdEDQ== + dependencies: + "@0x/base-contract" "^5.1.0" + "@0x/abi-gen@^2.0.9": version "2.1.1" resolved "https://registry.yarnpkg.com/@0x/abi-gen/-/abi-gen-2.1.1.tgz#2ca9072e64a2a46b6149aaea434f09d5dbf0866f" @@ -697,13 +704,13 @@ ethers "~4.0.4" lodash "^4.17.11" -"@0x/contract-addresses@^2.2.1": +"@0x/contract-addresses@^2.2.1", "@0x/contract-addresses@^2.3.1": version "2.3.3" resolved "https://registry.yarnpkg.com/@0x/contract-addresses/-/contract-addresses-2.3.3.tgz#8cf009e7668c2fccca416177c85f3f6612c724c6" dependencies: lodash "^4.17.11" -"@0x/contract-artifacts@^1.3.0": +"@0x/contract-artifacts@^1.3.0", "@0x/contract-artifacts@^1.5.0": version "1.5.1" resolved "https://registry.npmjs.org/@0x/contract-artifacts/-/contract-artifacts-1.5.1.tgz#6fba56a1d3e2d5d897a75fcfa432e49e2ebb17a7" @@ -746,6 +753,21 @@ ethereumjs-util "^5.1.1" lodash "^4.17.5" +"@0x/contracts-utils@3.1.1": + version "3.1.1" + resolved "https://registry.yarnpkg.com/@0x/contracts-utils/-/contracts-utils-3.1.1.tgz#d3d70ec4f5f9e10d85abc7d0c1b2b7bb34f38927" + dependencies: + "@0x/base-contract" "^5.0.5" + "@0x/order-utils" "^7.2.0" + "@0x/types" "^2.2.2" + "@0x/typescript-typings" "^4.2.2" + "@0x/utils" "^4.3.1" + "@0x/web3-wrapper" "^6.0.5" + bn.js "^4.11.8" + ethereum-types "^2.1.2" + ethereumjs-util "^5.1.1" + lodash "^4.17.11" + "@0x/coordinator-server@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@0x/coordinator-server/-/coordinator-server-0.1.3.tgz#5fbb7c11bb641aa5386797769cab9a68a7d15b79" @@ -797,6 +819,29 @@ ethers "~4.0.4" lodash "^4.17.11" +"@0x/order-utils@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@0x/order-utils/-/order-utils-7.2.0.tgz#c73d81e3225e9ec7736f9789e14388c9fe2b831c" + integrity sha512-P1tVRKyUvfU/yjYS+bbK+InZr0V2wPlX7nTp6ORTuPWd5zjWrRN7rpq50qvgY+UZCeajcQh9g4Dt1l+LWot/AA== + dependencies: + "@0x/abi-gen-wrappers" "^4.2.0" + "@0x/assert" "^2.0.9" + "@0x/base-contract" "^5.0.5" + "@0x/contract-addresses" "^2.3.1" + "@0x/contract-artifacts" "^1.5.0" + "@0x/json-schemas" "^3.0.9" + "@0x/types" "^2.2.2" + "@0x/typescript-typings" "^4.2.2" + "@0x/utils" "^4.3.1" + "@0x/web3-wrapper" "^6.0.5" + "@types/node" "*" + bn.js "^4.11.8" + ethereum-types "^2.1.2" + ethereumjs-abi "0.6.5" + ethereumjs-util "^5.1.1" + ethers "~4.0.4" + lodash "^4.17.11" + "@0x/subproviders@^4.1.1": version "4.1.2" resolved "https://registry.yarnpkg.com/@0x/subproviders/-/subproviders-4.1.2.tgz#ab7bb0f482b11ccb4615fb5dd8ca85199cd0ae23"