diff --git a/bin/contracts-test.sh b/bin/contracts-test.sh index 3c42e1625e..905b6546f7 100755 --- a/bin/contracts-test.sh +++ b/bin/contracts-test.sh @@ -5,5 +5,5 @@ set -e echo contracts-test cd contracts -yarn test || true # FIXME: after test merges done +yarn unit-test cd .. diff --git a/bin/deploy-contracts.sh b/bin/deploy-contracts.sh index 8353223d47..27bb783571 100755 --- a/bin/deploy-contracts.sh +++ b/bin/deploy-contracts.sh @@ -23,7 +23,6 @@ ERC20_ADDR_NEW_VALUE=`grep "TEST_ERC20" deploy.log` GOVERNANCE_GENESIS_TX_HASH_NEW_VALUE=`grep "GOVERNANCE_GENESIS_TX_HASH" deploy.log` GOVERNANCE_ADDR_NEW_VALUE=`grep "GOVERNANCE_ADDR" deploy.log` VERIFIER_ADDR_NEW_VALUE=`grep "VERIFIER_ADDR" deploy.log` -PRIORITY_QUEUE_ADDR_NEW_VALUE=`grep "PRIORITY_QUEUE_ADDR" deploy.log` if [[ ! -z "$CONTRACT_ADDR_NEW_VALUE" ]] then export LABEL=$ZKSYNC_ENV-Contract_deploy-`date +%Y-%m-%d-%H%M%S` @@ -37,7 +36,6 @@ then python3 bin/replace-env-variable.py ./$ENV_FILE $GOVERNANCE_GENESIS_TX_HASH_NEW_VALUE python3 bin/replace-env-variable.py ./$ENV_FILE $GOVERNANCE_ADDR_NEW_VALUE python3 bin/replace-env-variable.py ./$ENV_FILE $VERIFIER_ADDR_NEW_VALUE - python3 bin/replace-env-variable.py ./$ENV_FILE $PRIORITY_QUEUE_ADDR_NEW_VALUE else echo "Contract deployment failed" exit 1 diff --git a/bin/prepare-test-contracts.sh b/bin/prepare-test-contracts.sh index dfdc2e53d3..f36e87821f 100755 --- a/bin/prepare-test-contracts.sh +++ b/bin/prepare-test-contracts.sh @@ -10,7 +10,6 @@ OUT_DIR=./contracts/contracts/generated rm -rf $OUT_DIR mkdir -p $OUT_DIR cp $IN_DIR/Governance.sol $OUT_DIR/GovernanceTest.sol -cp $IN_DIR/PriorityQueue.sol $OUT_DIR/PriorityQueueTest.sol cp $IN_DIR/Verifier.sol $OUT_DIR/VerifierTest.sol cp $IN_DIR/Franklin.sol $OUT_DIR/FranklinTest.sol cp $IN_DIR/Storage.sol $OUT_DIR/StorageTest.sol @@ -18,13 +17,10 @@ cp $IN_DIR/Config.sol $OUT_DIR/ConfigTest.sol # Rename contracts ssed 's/Governance/GovernanceTest/' -i $OUT_DIR/*.sol -ssed 's/PriorityQueue/PriorityQueueTest/' -i $OUT_DIR/*.sol ssed 's/Verifier/VerifierTest/' -i $OUT_DIR/*.sol ssed 's/Franklin/FranklinTest/' -i $OUT_DIR/*.sol ssed 's/Storage/StorageTest/' -i $OUT_DIR/*.sol ssed 's/Config/ConfigTest/' -i $OUT_DIR/*.sol -# Workaround -> priority queue has FranklinTest in method names. -ssed 's/FranklinTest/Franklin/' -i $OUT_DIR/PriorityQueueTest.sol # Changes solidity constant to provided value diff --git a/contracts/contracts/Bytes.sol b/contracts/contracts/Bytes.sol index 1ebae71f0e..717b09a921 100644 --- a/contracts/contracts/Bytes.sol +++ b/contracts/contracts/Bytes.sol @@ -261,4 +261,24 @@ library Bytes { return tempBytes; } + // Helper function for hex conversion. + function halfByteToHex(byte _byte) internal pure returns (byte _hexByte) { + uint8 numByte = uint8(_byte); + if (numByte >= 0 && numByte <= 9) { + return byte(0x30 + numByte); // ASCII 0-9 + } else if (numByte <= 15) { + return byte(0x57 + numByte); // ASCII a-f + } + } + + // Convert bytes to ASCII hex representation + function bytesToHexASCIIBytes(bytes memory _input) internal pure returns (bytes memory _output) { + bytes memory outStringBytes = new bytes(_input.length * 2); + for (uint i = 0; i < _input.length; ++i) { + outStringBytes[i*2] = halfByteToHex(_input[i] >> 4); + outStringBytes[i*2+1] = halfByteToHex(_input[i] & 0x0f); + } + return outStringBytes; + } + } \ No newline at end of file diff --git a/contracts/contracts/Config.sol b/contracts/contracts/Config.sol index dc7ae38d55..48c1ff425c 100644 --- a/contracts/contracts/Config.sol +++ b/contracts/contracts/Config.sol @@ -36,7 +36,7 @@ contract Config { uint256 constant BASE_FULL_EXIT_GAS = 170000; /// @notice ETH blocks verification expectation - uint256 constant EXPECT_VERIFICATION_IN = 8 * 60 * 100; + uint256 constant EXPECT_VERIFICATION_IN = 2 * 4 * 60 * 24; // Two days /// @notice Max number of unverified blocks. To make sure that all reverted blocks can be copied under block gas limit! uint256 constant MAX_UNVERIFIED_BLOCKS = 4 * 60 * 100; @@ -55,6 +55,6 @@ contract Config { uint256 constant CHANGE_PUBKEY_BYTES = 6 * 8; /// @notice Expiration delta for priority request to be satisfied (in ETH blocks) - uint256 constant PRIORITY_EXPIRATION = 4 * 60 * 24; // One day - + /// NOTE: Priority expiration should be > EXPECT_VERIFICATION_IN, otherwise incorrect block with priority op could not be reverted. + uint256 constant PRIORITY_EXPIRATION = 3 * 4 * 60 * 24; // Two days } diff --git a/contracts/contracts/Franklin.sol b/contracts/contracts/Franklin.sol index 9651478df4..4c924624fe 100644 --- a/contracts/contracts/Franklin.sol +++ b/contracts/contracts/Franklin.sol @@ -37,7 +37,6 @@ contract Franklin is Storage, Config, Events { constructor( address _governanceAddress, address _verifierAddress, - address, // FIXME: remove _priorityQueueAddress in tests address, // FIXME: remove _genesisAccAddress bytes32 _genesisRoot ) public { @@ -174,11 +173,7 @@ contract Franklin is Storage, Config, Events { /// @param _token Token address /// @param _amount Token amount /// @param _franklinAddr Receiver Layer 2 address - function depositERC20( - address _token, - uint128 _amount, - address _franklinAddr - ) external payable { + function depositERC20(address _token, uint128 _amount, address _franklinAddr) external payable { requireActive(); // Fee is: @@ -206,20 +201,17 @@ contract Franklin is Storage, Config, Events { registerSingleWithdrawal(tokenId, _amount); require(IERC20(_token).transfer(msg.sender, _amount), "fw011"); // token transfer failed withdraw } - + /// @notice Register full exit request - pack pubdata, add priority request /// @param _accountId Numerical id of the account /// @param _token Token address, 0 address for ether - function fullExit ( - uint24 _accountId, - address _token - ) external payable { + function fullExit (uint24 _accountId, address _token) external payable { requireActive(); // Fee is: // fee coeff * base tx gas cost * gas price uint256 fee = FEE_GAS_PRICE_MULTIPLIER * BASE_FULL_EXIT_GAS * tx.gasprice; - + uint16 tokenId; if (_token == address(0)) { tokenId = 0; @@ -299,7 +291,7 @@ contract Franklin is Storage, Config, Events { bytes32 _newRoot, bytes calldata _publicData, bytes calldata _ethWitness, - uint64[] calldata _ethWitnessSizes + uint32[] calldata _ethWitnessSizes ) external { requireActive(); require(_blockNumber == totalBlocksCommitted + 1, "fck11"); // only commit next block @@ -310,35 +302,31 @@ contract Franklin is Storage, Config, Events { // Unpack onchain operations and store them. // Get onchain operations start id for global onchain operations counter, // onchain operations number for this block, priority operations number for this block. - uint64 startId = totalOnchainOps; - (uint64 totalProcessed, uint64 priorityNumber) = collectOnchainOps(_publicData, _ethWitness, _ethWitnessSizes); - - // Verify that priority operations from this block are valid - // (their data is similar to data from priority requests mapping) - verifyPriorityOperations(startId, totalProcessed, priorityNumber); - - createCommittedBlock(_blockNumber, _feeAccount, _newRoot, _publicData, startId, totalProcessed, priorityNumber); + uint64 firstOnchainOpId = totalOnchainOps; + uint64 prevTotalCommittedPriorityRequests = totalCommittedPriorityRequests; - totalOnchainOps = startId + totalProcessed; + collectOnchainOps(_publicData, _ethWitness, _ethWitnessSizes); - totalBlocksCommitted += 1; + uint64 nPriorityRequestProcessed = totalCommittedPriorityRequests - prevTotalCommittedPriorityRequests; + uint64 nOnchainOpsProcessed = totalOnchainOps - firstOnchainOpId; - totalCommittedPriorityRequests += priorityNumber; + createCommittedBlock(_blockNumber, _feeAccount, _newRoot, _publicData, firstOnchainOpId, nOnchainOpsProcessed, nPriorityRequestProcessed); + totalBlocksCommitted++; emit BlockCommitted(_blockNumber); } } /// @notice Store committed block structure to the storage. - /// @param _startId - blocks' onchain ops start id in global operations - /// @param _totalProcessed - total number of onchain ops in block - /// @param _priorityNumber - total number of priority onchain ops in block + /// @param _firstOnchainOpId - blocks' onchain ops start id in global operations + /// @param _nOnchainOpsProcessed - total number of onchain ops in block + /// @param _nCommittedPriorityRequests - total number of priority requests in block function createCommittedBlock( uint32 _blockNumber, uint24 _feeAccount, bytes32 _newRoot, bytes memory _publicData, - uint64 _startId, uint64 _totalProcessed, uint64 _priorityNumber + uint64 _firstOnchainOpId, uint64 _nOnchainOpsProcessed, uint64 _nCommittedPriorityRequests ) internal { uint32 blockChunks = uint32(_publicData.length / 8); require(verifier.isBlockSizeSupported(blockChunks), "ccb10"); @@ -355,9 +343,9 @@ contract Franklin is Storage, Config, Events { blocks[_blockNumber] = Block( msg.sender, // validator uint32(block.number), // committed at - _startId, // blocks' onchain ops start id in global operations - _totalProcessed, // total number of onchain ops in block - _priorityNumber, // total number of priority onchain ops in block + _firstOnchainOpId, // blocks' onchain ops start id in global operations + _nOnchainOpsProcessed, // total number of onchain ops in block + _nCommittedPriorityRequests, // total number of priority onchain ops in block commitment, // blocks' commitment _newRoot, // new root blockChunks @@ -368,103 +356,113 @@ contract Franklin is Storage, Config, Events { /// @param _publicData Operations packed in bytes array /// @param _ethWitness Eth witness that was posted with commit /// @param _ethWitnessSizes Amount of eth witness bytes for the corresponding operation. - /// @return onchain operations start id for global onchain operations counter, nchain operations number for this block, priority operations number for this block - function collectOnchainOps(bytes memory _publicData, bytes memory _ethWitness, uint64[] memory _ethWitnessSizes) - internal - returns (uint64 processedOnchainOps, uint64 priorityCount) - { + function collectOnchainOps(bytes memory _publicData, bytes memory _ethWitness, uint32[] memory _ethWitnessSizes) + internal { require(_publicData.length % 8 == 0, "fcs11"); // pubdata length must be a multiple of 8 because each chunk is 8 bytes - uint64 currentOnchainOp = totalOnchainOps; - uint256 currentPointer = 0; - uint64[2] memory currentEthWitness; + uint256 pubdataOffset = 0; + + uint32 ethWitnessOffset = 0; + uint32 processedZKSyncOperation = 0; - while (currentPointer < _publicData.length) { - uint8 opType = uint8(_publicData[currentPointer]); - bytes memory currentEthWitnessBytes = Bytes.slice(_ethWitness, currentEthWitness[1], _ethWitnessSizes[currentEthWitness[0]]); - (uint256 len, uint64 ops, uint64 priority) = processOp( - opType, - currentPointer, + while (pubdataOffset < _publicData.length) { + require(processedZKSyncOperation < _ethWitnessSizes.length, "fcs13"); // eth witness data malformed + bytes memory zksyncOperationETHWitness = Bytes.slice(_ethWitness, ethWitnessOffset, _ethWitnessSizes[processedZKSyncOperation]); + + pubdataOffset += processNextOperation( + pubdataOffset, _publicData, - currentOnchainOp, - currentEthWitnessBytes + zksyncOperationETHWitness ); - currentPointer += len; - processedOnchainOps += ops; - priorityCount += priority; - currentOnchainOp += ops; - currentEthWitness[1] += _ethWitnessSizes[currentEthWitness[0]]; - currentEthWitness[0] += 1; + ethWitnessOffset += _ethWitnessSizes[processedZKSyncOperation]; + processedZKSyncOperation++; } - require(currentPointer == _publicData.length, "fcs12"); // last chunk exceeds pubdata + require(pubdataOffset == _publicData.length, "fcs12"); // last chunk exceeds pubdata } - function verifyChangePubkeySignature(bytes memory _signature, bytes memory _newPkHash, uint32 _nonce, address _ethAddress) internal pure returns (bool) { - uint offset = 0; + /// @notice Verifies ethereum signature for given message and recovers address of the signer + /// @param _signature 65 bytes concatenated. R (32) + S (32) + V (1) + /// @param _message signed message. + /// @return address of the signer + function verifyEthereumSignature(bytes memory _signature, bytes memory _message) internal pure returns (address) { + require(_signature.length == 2*ETH_SIGN_RS_BYTES + 1, "ves10"); // incorrect signature length + uint offset = 0; bytes32 signR = Bytes.bytesToBytes32(Bytes.slice(_signature, offset, ETH_SIGN_RS_BYTES)); offset += ETH_SIGN_RS_BYTES; - bytes32 signS = Bytes.bytesToBytes32(Bytes.slice(_signature, offset, ETH_SIGN_RS_BYTES)); offset += ETH_SIGN_RS_BYTES; - uint8 signV = uint8(_signature[offset]); - bytes memory signedMessage = abi.encodePacked("\x19Ethereum Signed Message:\n24", _nonce, _newPkHash); - address recoveredAddress = ecrecover(keccak256(signedMessage), signV, signR, signS); + return ecrecover(keccak256(_message), signV, signR, signS); + } + + function verifyChangePubkeySignature(bytes memory _signature, bytes memory _newPkHash, uint32 _nonce, address _ethAddress) internal pure returns (bool) { + bytes memory signedMessage = abi.encodePacked("\x19Ethereum Signed Message:\n135"); + signedMessage = abi.encodePacked(signedMessage, "Register ZK Sync pubkey:\n\n"); + signedMessage = abi.encodePacked(signedMessage, "sync:", Bytes.bytesToHexASCIIBytes(_newPkHash), " nonce: 0x", Bytes.bytesToHexASCIIBytes(Bytes.toBytesFromUInt32(_nonce)), "\n\n"); + signedMessage = abi.encodePacked(signedMessage, "Only sign this message for a trusted client!"); + address recoveredAddress = verifyEthereumSignature(_signature, signedMessage); return recoveredAddress == _ethAddress; } /// @notice On the first byte determines the type of operation, if it is an onchain operation - saves it in storage - /// @param _opType Operation type - /// @param _currentPointer Current pointer in pubdata + /// @param _pubdataOffset Current offset in pubdata /// @param _publicData Operation pubdata - /// @param _currentOnchainOp Operation identifier in onchain operations mapping - /// @return operation processed length, and indicators if this operation is an onchain operation and if it is a priority operation (1 if true) - function processOp( - uint8 _opType, - uint256 _currentPointer, + /// @param _currentEthWitness current eth witness for operation + /// @return pubdata bytes processed + function processNextOperation( + uint256 _pubdataOffset, bytes memory _publicData, - uint64 _currentOnchainOp, bytes memory _currentEthWitness - ) internal returns (uint256 processedLen, uint64 processedOnchainOps, uint64 priorityCount) { - uint256 opDataPointer = _currentPointer + 1; // operation type byte + ) internal returns (uint256 _bytesProcessed) { + uint8 opType = uint8(_publicData[_pubdataOffset]); - if (_opType == uint8(Operations.OpType.Noop)) return (NOOP_BYTES, 0, 0); - if (_opType == uint8(Operations.OpType.TransferToNew)) return (TRANSFER_TO_NEW_BYTES, 0, 0); - if (_opType == uint8(Operations.OpType.Transfer)) return (TRANSFER_BYTES, 0, 0); - if (_opType == uint8(Operations.OpType.CloseAccount)) return (CLOSE_ACCOUNT_BYTES, 0, 0); + if (opType == uint8(Operations.OpType.Noop)) return NOOP_BYTES; + if (opType == uint8(Operations.OpType.TransferToNew)) return TRANSFER_TO_NEW_BYTES; + if (opType == uint8(Operations.OpType.Transfer)) return TRANSFER_BYTES; + if (opType == uint8(Operations.OpType.CloseAccount)) return CLOSE_ACCOUNT_BYTES; - if (_opType == uint8(Operations.OpType.Deposit)) { - bytes memory pubData = Bytes.slice(_publicData, opDataPointer, DEPOSIT_BYTES - 1); - onchainOps[_currentOnchainOp] = OnchainOperation( + if (opType == uint8(Operations.OpType.Deposit)) { + bytes memory pubData = Bytes.slice(_publicData, _pubdataOffset + 1, DEPOSIT_BYTES - 1); + onchainOps[totalOnchainOps] = OnchainOperation( Operations.OpType.Deposit, pubData ); - return (DEPOSIT_BYTES, 1, 1); + verifyNextPriorityOperation(onchainOps[totalOnchainOps]); + + totalOnchainOps++; + + return DEPOSIT_BYTES; } - if (_opType == uint8(Operations.OpType.PartialExit)) { - bytes memory pubData = Bytes.slice(_publicData, opDataPointer, PARTIAL_EXIT_BYTES - 1); - onchainOps[_currentOnchainOp] = OnchainOperation( + if (opType == uint8(Operations.OpType.PartialExit)) { + bytes memory pubData = Bytes.slice(_publicData, _pubdataOffset + 1, PARTIAL_EXIT_BYTES - 1); + onchainOps[totalOnchainOps] = OnchainOperation( Operations.OpType.PartialExit, pubData ); - return (PARTIAL_EXIT_BYTES, 1, 0); + totalOnchainOps++; + + return PARTIAL_EXIT_BYTES; } - if (_opType == uint8(Operations.OpType.FullExit)) { - bytes memory pubData = Bytes.slice(_publicData, opDataPointer, FULL_EXIT_BYTES - 1); - onchainOps[_currentOnchainOp] = OnchainOperation( + if (opType == uint8(Operations.OpType.FullExit)) { + bytes memory pubData = Bytes.slice(_publicData, _pubdataOffset + 1, FULL_EXIT_BYTES - 1); + onchainOps[totalOnchainOps] = OnchainOperation( Operations.OpType.FullExit, pubData ); - return (FULL_EXIT_BYTES, 1, 1); + + verifyNextPriorityOperation(onchainOps[totalOnchainOps]); + + totalOnchainOps++; + return FULL_EXIT_BYTES; } - if (_opType == uint8(Operations.OpType.ChangePubKey)) { - Operations.ChangePubKey memory op = Operations.readChangePubKeyPubdata(_publicData, opDataPointer); + if (opType == uint8(Operations.OpType.ChangePubKey)) { + Operations.ChangePubKey memory op = Operations.readChangePubKeyPubdata(_publicData, _pubdataOffset + 1); if (_currentEthWitness.length > 0) { bool valid = verifyChangePubkeySignature(_currentEthWitness, op.pubKeyHash, op.nonce, op.owner); require(valid, "fpp15"); // failed to verify change pubkey hash signature @@ -472,12 +470,12 @@ contract Franklin is Storage, Config, Events { bool valid = keccak256(authFacts[op.owner][op.nonce]) == keccak256(op.pubKeyHash); require(valid, "fpp16"); // new pub key hash is not authenticated properly } - return (CHANGE_PUBKEY_BYTES, 0, 0); + return CHANGE_PUBKEY_BYTES; } revert("fpp14"); // unsupported op } - + /// @notice Creates block commitment from its data /// @param _blockNumber Block number /// @param _feeAccount Account to collect fees @@ -507,37 +505,24 @@ contract Franklin is Storage, Config, Events { return hash; } - /// @notice Check if blocks' priority operations are valid - /// @param _startId Onchain op start id - /// @param _totalProcessed How many ops are procceeded - /// @param _number Priority ops number - function verifyPriorityOperations(uint64 _startId, uint64 _totalProcessed, uint64 _number) internal view { - require(_number <= totalOpenPriorityRequests-totalCommittedPriorityRequests, "fvs11"); // too many priority requests - uint64 start = _startId; - uint64 end = start + _totalProcessed; - - uint64 counter = 0; - for (uint64 current = start; current < end; ++current) { - if (onchainOps[current].opType == Operations.OpType.FullExit || onchainOps[current].opType == Operations.OpType.Deposit) { - OnchainOperation storage op = onchainOps[current]; - - uint64 _priorityRequestId = counter + firstPriorityRequestId + totalCommittedPriorityRequests; - Operations.OpType priorReqType = priorityRequests[_priorityRequestId].opType; - bytes memory priorReqPubdata = priorityRequests[_priorityRequestId].pubData; + function verifyNextPriorityOperation(OnchainOperation memory _onchainOp) internal { + require(totalOpenPriorityRequests > totalCommittedPriorityRequests, "vnp11"); // no more priority requests in queue - require(priorReqType == op.opType, "fvs12"); // incorrect priority op type + uint64 _priorityRequestId = firstPriorityRequestId + totalCommittedPriorityRequests; + Operations.OpType priorReqType = priorityRequests[_priorityRequestId].opType; + bytes memory priorReqPubdata = priorityRequests[_priorityRequestId].pubData; - if (op.opType == Operations.OpType.Deposit) { - require(Operations.depositPubdataMatch(priorReqPubdata, op.pubData), "fvs13"); - } else if (op.opType == Operations.OpType.FullExit) { - require(Operations.fullExitPubdataMatch(priorReqPubdata, op.pubData), "fvs14"); - } else { - revert("fvs15"); // invalid or non-priority operation - } + require(priorReqType == _onchainOp.opType, "nvp12"); // incorrect priority op type - counter++; - } + if (_onchainOp.opType == Operations.OpType.Deposit) { + require(Operations.depositPubdataMatch(priorReqPubdata, _onchainOp.pubData), "vnp13"); + } else if (_onchainOp.opType == Operations.OpType.FullExit) { + require(Operations.fullExitPubdataMatch(priorReqPubdata, _onchainOp.pubData), "vnp14"); + } else { + revert("vnp15"); // invalid or non-priority operation } + + totalCommittedPriorityRequests++; } /// @notice Removes some onchain ops (for example in case of wrong priority comparison) @@ -668,17 +653,11 @@ contract Franklin is Storage, Config, Events { /// @param _proof Proof /// @param _tokenId Verified token id /// @param _amount Amount for owner - function exit( - uint16 _tokenId, - uint128 _amount, - uint256[8] calldata _proof - ) external { + function exit(uint16 _tokenId, uint128 _amount, uint256[8] calldata _proof) external { require(exodusMode, "fet11"); // must be in exodus mode require(exited[msg.sender][_tokenId] == false, "fet12"); // already exited require(verifier.verifyExitProof(blocks[totalBlocksVerified].stateRoot, msg.sender, _tokenId, _amount, _proof), "fet13"); // verification failed - - balancesToWithdraw[msg.sender][_tokenId] += _amount; exited[msg.sender][_tokenId] = true; } diff --git a/contracts/contracts/Governance.sol b/contracts/contracts/Governance.sol index 14f9d93000..ac9511c01e 100644 --- a/contracts/contracts/Governance.sol +++ b/contracts/contracts/Governance.sol @@ -78,7 +78,7 @@ contract Governance is Config { /// @param _tokenId Token id /// @return bool flag that indicates if token id is less than total tokens amount function isValidTokenId(uint16 _tokenId) external view returns (bool) { - return _tokenId < totalTokens + 1; + return _tokenId <= totalTokens; } /// @notice Validate token address diff --git a/contracts/contracts/PriorityQueue.sol b/contracts/contracts/PriorityQueue.sol deleted file mode 100644 index 99fbe910b1..0000000000 --- a/contracts/contracts/PriorityQueue.sol +++ /dev/null @@ -1,18 +0,0 @@ -pragma solidity 0.5.16; - -import "./Bytes.sol"; -import "./Operations.sol"; -import "./Governance.sol"; - -// FIXME: remove this from tests and delete -contract PriorityQueue { - - constructor(address _governanceAddress) public { - - } - - function setFranklinAddress(address _franklinAddress) external { - - } - -} diff --git a/contracts/contracts/Storage.sol b/contracts/contracts/Storage.sol index c519ba8251..66f42e14fc 100644 --- a/contracts/contracts/Storage.sol +++ b/contracts/contracts/Storage.sol @@ -4,8 +4,7 @@ import "../node_modules/openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; import "./Governance.sol"; import "./Verifier.sol"; -import "./PriorityQueue.sol"; - +import "./Operations.sol"; /// @title zkSync storage contract /// @author Matter Labs diff --git a/contracts/contracts/test/BytesTest.sol b/contracts/contracts/test/BytesTest.sol index 8966f878f8..81811b84f6 100644 --- a/contracts/contracts/test/BytesTest.sol +++ b/contracts/contracts/test/BytesTest.sol @@ -19,6 +19,10 @@ contract BytesTest { bytes memory buf = Bytes.toBytesFromUInt24(x); (offset, r) = Bytes.readUInt24(buf, 0); } - + + function bytesToHexConvert(bytes calldata _in) external pure returns (string memory) { + return string(Bytes.bytesToHexASCIIBytes(_in)); + } + } diff --git a/contracts/contracts/test/DummyVerifier.sol b/contracts/contracts/test/DummyVerifier.sol deleted file mode 100644 index b4f4cdf53d..0000000000 --- a/contracts/contracts/test/DummyVerifier.sol +++ /dev/null @@ -1,12 +0,0 @@ -pragma solidity ^0.5.1; - -contract DummyVerifier { - function Verify( - uint256[14] memory, - uint256[] memory, - uint256[8] memory, - uint256[] memory - ) internal pure returns (bool) { - return true; - } -} diff --git a/contracts/contracts/test/OperationsTest.sol b/contracts/contracts/test/OperationsTest.sol index 777e121099..468c1490ba 100644 --- a/contracts/contracts/test/OperationsTest.sol +++ b/contracts/contracts/test/OperationsTest.sol @@ -77,6 +77,18 @@ contract OperationsTest { require(x.tokenId == r.tokenId, "tokenId mismatch"); require(x.amount == r.amount, "amount mismatch"); } - + + function parseDepositFromPubdata(bytes calldata _pubdata) external pure returns (uint16 tokenId, uint128 amount, address owner) { + (, Operations.Deposit memory r) = Operations.readDepositPubdata(_pubdata, 0); + return (r.tokenId, r.amount, r.owner); + } + + function parseFullExitFromPubdata(bytes calldata _pubdata) external pure returns (uint24 accountId, address owner, uint16 tokenId, uint128 amount) { + Operations.FullExit memory r = Operations.readFullExitPubdata(_pubdata, 0); + accountId = r.accountId; + owner = r.owner; + tokenId = r.tokenId; + amount = r.amount; + } } diff --git a/contracts/contracts/test/ZKSyncUnitTest.sol b/contracts/contracts/test/ZKSyncUnitTest.sol new file mode 100644 index 0000000000..6bc9b3b5a4 --- /dev/null +++ b/contracts/contracts/test/ZKSyncUnitTest.sol @@ -0,0 +1,41 @@ +pragma solidity 0.5.16; + +import "../generated/FranklinTest.sol"; + + +contract ZKSyncUnitTest is FranklinTest { + + constructor( + address _governanceAddress, + address _verifierAddress, + address _genesisAccAddress, + bytes32 _genesisRoot + ) FranklinTest(_governanceAddress, _verifierAddress, _genesisAccAddress, _genesisRoot) public{} + + function changePubkeySignatureCheck(bytes calldata _signature, bytes calldata _newPkHash, uint32 _nonce, address _ethAddress) external pure returns (bool) { + return verifyChangePubkeySignature(_signature, _newPkHash, _nonce, _ethAddress); + } + + function setBalanceToWithdraw(address _owner, uint16 _token, uint128 _amount) external { + balancesToWithdraw[_owner][_token] = _amount; + } + + function () payable external{} + + function addPendingWithdrawal(address _to, uint16 _tokenId, uint128 _amount) external { + storeWithdrawalAsPending(_to, _tokenId, _amount); + } + + function testProcessNextOperation( + uint256 _pubdataOffset, + bytes calldata _publicData, + bytes calldata _currentEthWitness, + uint256 _expectedBytesProcessed + ) external { + require(processNextOperation(_pubdataOffset, _publicData, _currentEthWitness) == _expectedBytesProcessed, "bytes processed incorrect"); + } + + function testVerifyEthereumSignature(bytes calldata _signature, bytes calldata _message) external pure returns (address) { + return verifyEthereumSignature(_signature, _message); + } +} diff --git a/contracts/package.json b/contracts/package.json index 06d04a9bb0..247f297310 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -24,13 +24,14 @@ "ts-node": "^8.3.0", "tslint": "^5.18.0", "typescript": "^3.5.3", - "zksync": "^0.3.9" + "zksync": "link:../js/zksync.js" }, "scripts": { "build": "waffle .waffle.json", "test": "yarn build && mocha -r ts-node/register test/**/*.ts", "itest": "yarn build && mocha -r ts-node/register test/unit_tests/**/*.js test/fails_tests.ts", - "unit-test": "yarn build && mocha test/unit_tests/**/*.js", + "unit-test": "yarn build && mocha -r ts-node/register test/unit_tests/**/*", + "unit-test-watch": "waffle .waffle.json && mocha -r ts-node/register test/unit_tests/**/* --watch --watch-extensions ts --watch-extensions 'js'", "deploy": "yarn build && ts-node scripts/testnet-deploy.ts --deploy --publish", "deploy-no-build": "ts-node scripts/testnet-deploy.ts --deploy --publish", "deploy-test-no-build": "ts-node scripts/testnet-deploy.ts --deploy --test", diff --git a/contracts/scripts/revert-reason.ts b/contracts/scripts/revert-reason.ts index 489e01344f..4f210eb1a5 100644 --- a/contracts/scripts/revert-reason.ts +++ b/contracts/scripts/revert-reason.ts @@ -1,10 +1,9 @@ import {ethers} from "ethers"; -import {franklinContractCode, governanceContractCode, priorityQueueContractCode} from "../src.ts/deploy"; +import {franklinContractCode, governanceContractCode} from "../src.ts/deploy"; import {Interface} from "ethers/utils"; const provider = new ethers.providers.JsonRpcProvider(process.env.WEB3_URL); const wallet = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, "m/44'/60'/0'/0/1").connect(provider); const franklinInterface = new Interface(franklinContractCode.interface); -const priorityQueueInterface = new Interface(priorityQueueContractCode.interface); const governanceInterface = new Interface(governanceContractCode.interface); function hex_to_ascii(str1) { @@ -53,9 +52,6 @@ async function reason() { for (let log of receipt.logs){ let parsedLog = franklinInterface.parseLog(log); - if (!parsedLog) { - parsedLog = priorityQueueInterface.parseLog(log); - } if (!parsedLog) { parsedLog = governanceInterface.parseLog(log); } diff --git a/contracts/scripts/testnet-deploy.ts b/contracts/scripts/testnet-deploy.ts index b3a799f755..ba4714d2e8 100644 --- a/contracts/scripts/testnet-deploy.ts +++ b/contracts/scripts/testnet-deploy.ts @@ -33,25 +33,26 @@ async function main() { if (args.deploy) { let timer = Date.now(); - await deployer.deployGovernance(); + const governance = await deployer.deployGovernance(); + console.log(`GOVERNANCE_GENESIS_TX_HASH=${governance.deployTransaction.hash}`); + console.log(`GOVERNANCE_ADDR=${governance.address}`); console.log(`Governance contract deployed, time: ${(Date.now() - timer) / 1000} secs`); timer = Date.now(); - await deployer.deployPriorityQueue(); - console.log(`Priority queue contract deployed, time: ${(Date.now() - timer) / 1000} secs`); - - timer = Date.now(); - await deployer.deployVerifier(); + const verifier = await deployer.deployVerifier(); + console.log(`VERIFIER_ADDR=${verifier.address}`); console.log(`Verifier contract deployed, time: ${(Date.now() - timer) / 1000} secs`); timer = Date.now(); - await deployer.deployFranklin(); + const mainContract = await deployer.deployFranklin(); + console.log(`CONTRACT_GENESIS_TX_HASH=${mainContract.deployTransaction.hash}`); + console.log(`CONTRACT_ADDR=${mainContract.address}`); console.log(`Main contract deployed, time: ${(Date.now() - timer) / 1000} secs`); - const governance = await deployer.getDeployedContract('Governance'); await governance.setValidator(process.env.OPERATOR_ETH_ADDRESS, true); const erc20 = await addTestERC20Token(wallet, governance); + console.log("TEST_ERC20=" + erc20.address); await mintTestERC20Token(testWallet, erc20); } @@ -60,14 +61,12 @@ async function main() { if (process.env.ETH_NETWORK === 'localhost') { await Promise.all([ deployer.postContractToTesseracts("Governance"), - deployer.postContractToTesseracts("PriorityQueue"), deployer.postContractToTesseracts("Verifier"), deployer.postContractToTesseracts("Franklin"), ]); } else { // sequentially, since etherscan has request limit await deployer.publishSourceCodeToEtherscan("Governance"); - await deployer.publishSourceCodeToEtherscan("PriorityQueue"); await deployer.publishSourceCodeToEtherscan("Verifier"); await deployer.publishSourceCodeToEtherscan("Franklin"); } diff --git a/contracts/src.ts/deploy.ts b/contracts/src.ts/deploy.ts index 4d78526880..d2d2805f02 100644 --- a/contracts/src.ts/deploy.ts +++ b/contracts/src.ts/deploy.ts @@ -18,12 +18,10 @@ export const ERC20MintableContract = function () { export const franklinContractCode = require(`../build/Franklin`); export const verifierContractCode = require(`../build/Verifier`); export const governanceContractCode = require(`../build/Governance`); -export const priorityQueueContractCode = require(`../build/PriorityQueue`); export const franklinTestContractCode = require('../build/FranklinTest'); export const verifierTestContractCode = require('../build/VerifierTest'); export const governanceTestContractCode = require('../build/GovernanceTest'); -export const priorityQueueTestContractCode = require('../build/PriorityQueueTest'); import { ImportsFsEngine } from '@resolver-engine/imports-fs'; import { gatherSources } from '@resolver-engine/imports'; @@ -65,14 +63,12 @@ export class Deployer { constructor(public wallet: ethers.Wallet, isTest: boolean) { this.bytecodes = { Governance: isTest ? governanceTestContractCode : governanceContractCode, - PriorityQueue: isTest ? priorityQueueTestContractCode : priorityQueueContractCode, Verifier: isTest ? verifierTestContractCode : verifierContractCode, Franklin: isTest ? franklinTestContractCode : franklinContractCode, }; this.addresses = { Governance: process.env.GOVERNANCE_ADDR, - PriorityQueue: process.env.PRIORITY_QUEUE_ADDR, Verifier: process.env.VERIFIER_ADDR, Franklin: process.env.CONTRACT_ADDR, }; @@ -94,7 +90,6 @@ export class Deployer { 'Franklin': [ this.addresses.Governance, this.addresses.Verifier, - this.addresses.PriorityQueue, process.env.OPERATOR_FRANKLIN_ADDRESS, process.env.GENESIS_ROOT || ethers.constants.HashZero, ] @@ -123,24 +118,10 @@ export class Deployer { this.constructorArgs('Governance'), { gasLimit: 3000000 } ); - console.log(`GOVERNANCE_GENESIS_TX_HASH=${governance.deployTransaction.hash}`); - console.log(`GOVERNANCE_ADDR=${governance.address}`); this.addresses.Governance = governance.address; return governance; } - async deployPriorityQueue() { - let priorityQueue = await deployContract( - this.wallet, - this.bytecodes.PriorityQueue, - this.constructorArgs('PriorityQueue'), - { gasLimit: 5000000 } - ); - console.log(`PRIORITY_QUEUE_ADDR=${priorityQueue.address}`); - this.addresses.PriorityQueue = priorityQueue.address; - return priorityQueue; - } - async deployVerifier() { let verifier = await deployContract( this.wallet, @@ -148,7 +129,6 @@ export class Deployer { this.constructorArgs('Verifier'), { gasLimit: 2000000 } ); - console.log(`VERIFIER_ADDR=${verifier.address}`); this.addresses.Verifier = verifier.address; return verifier; } @@ -160,8 +140,6 @@ export class Deployer { this.constructorArgs('Franklin'), { gasLimit: 6000000} ); - console.log(`CONTRACT_GENESIS_TX_HASH=${franklin.deployTransaction.hash}`); - console.log(`CONTRACT_ADDR=${franklin.address}`); this.addresses.Franklin = franklin.address; return franklin; } @@ -241,7 +219,6 @@ export async function addTestERC20Token(wallet, governance) { try { let erc20 = await deployContract(wallet, ERC20MintableContract, []); await erc20.mint(wallet.address, parseEther("3000000000")); - console.log("TEST_ERC20=" + erc20.address); await (await governance.addToken(erc20.address)).wait(); return erc20; } catch (err) { @@ -262,7 +239,6 @@ export async function addTestNotApprovedERC20Token(wallet) { try { let erc20 = await deployContract(wallet, ERC20MintableContract, []); await erc20.mint(wallet.address, bigNumberify("1000000000")); - console.log("TEST_ERC20=" + erc20.address); return erc20 } catch (err) { console.log("Add token error:" + err); diff --git a/contracts/test/helpers.ts b/contracts/test/helpers.ts index 841b702711..d7dfe0e9b4 100644 --- a/contracts/test/helpers.ts +++ b/contracts/test/helpers.ts @@ -34,104 +34,76 @@ export async function cancelOustandingDepositsForExodus( provider, wallet, franklinDeployedContract, - priorityQueueDeployedContract, expectedToCancel, actualToCancel, expectedBalanceToWithdraw, revertCode, ) { - if (!revertCode) { - const oldOpenPriorityRequests = await priorityQueueDeployedContract.totalOpenPriorityRequests(); - const oldCommittedPriorityRequests = await priorityQueueDeployedContract.totalCommittedPriorityRequests(); - const oldFirstPriorityRequestId = await priorityQueueDeployedContract.firstPriorityRequestId(); + const oldOpenPriorityRequests = await franklinDeployedContract.totalOpenPriorityRequests(); + const oldCommittedPriorityRequests = await franklinDeployedContract.totalCommittedPriorityRequests(); + const oldFirstPriorityRequestId = await franklinDeployedContract.firstPriorityRequestId(); - const tx = await franklinDeployedContract.cancelOutstandingDepositsForExodusMode(expectedToCancel); - await tx.wait(); + const receipt = await executeTransaction(async () => { + return await franklinDeployedContract.cancelOutstandingDepositsForExodusMode(expectedToCancel); + }, revertCode); - const newOpenPriorityRequests = await priorityQueueDeployedContract.totalOpenPriorityRequests(); - const newCommittedPriorityRequests = await priorityQueueDeployedContract.totalCommittedPriorityRequests(); - const newFirstPriorityRequestId = await priorityQueueDeployedContract.firstPriorityRequestId(); + if (receipt) { + const newOpenPriorityRequests = await franklinDeployedContract.totalOpenPriorityRequests(); + const newCommittedPriorityRequests = await franklinDeployedContract.totalCommittedPriorityRequests(); + const newFirstPriorityRequestId = await franklinDeployedContract.firstPriorityRequestId(); expect(oldOpenPriorityRequests - newOpenPriorityRequests).equal(actualToCancel); expect(oldCommittedPriorityRequests - newCommittedPriorityRequests).equal(0); expect(newFirstPriorityRequestId - oldFirstPriorityRequestId).equal(actualToCancel); const balanceToWithdraw = await franklinDeployedContract.balancesToWithdraw(wallet.address, 0); - expect(balanceToWithdraw).equal(expectedBalanceToWithdraw); - } else { - const tx = await franklinDeployedContract.cancelOutstandingDepositsForExodusMode( - expectedToCancel, - {gasLimit: bigNumberify("6000000")}, - ); - const receipt = await tx.wait() - .catch(() => { - }); + expect(balanceToWithdraw, "Exodus mode deposit cancel balance mismatch").equal(expectedBalanceToWithdraw); + } +} - if (receipt && receipt.status) { - expect(receipt.status, `transaction should fail ${receipt.transactionHash}`).not.eq(1); +async function executeTransaction(txClosure, revertCode) { + let receipt; + try { + const tx = await txClosure(); + receipt = await tx.wait(); + if (revertCode) { + expect(receipt.status, `expected transaction fail with code: ${revertCode}`).not.eq(1); } + } catch (e) { + if (e.name === "RuntimeError") { + const txHash = e.transactionHash; + const revertReason = e.results[txHash].reason; - const code = await provider.call(tx, tx.blockNumber); - const reason = hex_to_ascii(code.substr(138)); - - expect(reason.substring(0, 5)).equal(revertCode); + expect(revertReason, `expected transaction revert`).eq(revertCode); + } else { + throw e; + } } + return receipt; } export async function postEthDeposit( provider, wallet, franklinDeployedContract, - priorityQueueDeployedContract, depositAmount, fee, franklinAddress, txValue, revertCode, ) { - const franklinAddressBinary = Buffer.from(franklinAddress, "hex"); - - const oldOpenPriorityRequests = await priorityQueueDeployedContract.totalOpenPriorityRequests(); - const oldCommittedPriorityRequests = await priorityQueueDeployedContract.totalCommittedPriorityRequests(); - const oldFirstPriorityRequestId = await priorityQueueDeployedContract.firstPriorityRequestId(); - - const tx = await franklinDeployedContract.depositETH( - depositAmount, - franklinAddressBinary, - {value: txValue, gasLimit: bigNumberify("6000000")}, - ); - - if (!revertCode) { - const receipt = await tx.wait(); + const receipt = await executeTransaction(async () => { + return franklinDeployedContract.depositETH( + depositAmount, + franklinAddress, + {value: txValue, gasLimit: bigNumberify("6000000")}, + ); + }, revertCode); + if (receipt) { const event = receipt.events[1].args; - - expect(event.owner).equal(wallet.address); + expect(event.owner).equal(franklinAddress); expect(event.tokenId).equal(0); expect(event.amount).equal(depositAmount); - // FIXME: not passing: expect(event.fee).equal(fee); - - expect(event.franklinAddress).equal("0x" + franklinAddress); - - const newOpenPriorityRequests = await priorityQueueDeployedContract.totalOpenPriorityRequests(); - const newCommittedPriorityRequests = await priorityQueueDeployedContract.totalCommittedPriorityRequests(); - const newFirstPriorityRequestId = await priorityQueueDeployedContract.firstPriorityRequestId(); - - expect(newOpenPriorityRequests - oldOpenPriorityRequests).equal(1); - expect(newCommittedPriorityRequests - oldCommittedPriorityRequests).equal(0); - expect(newFirstPriorityRequestId - oldFirstPriorityRequestId).equal(0); - - } else { - const receipt = await tx.wait() - .catch(() => { - }); - if (receipt && receipt.status) { - expect(receipt.status, `tx should fail ${receipt.transactionHash}`).not.eq(1); - } - - const code = await provider.call(tx, tx.blockNumber); - const reason = hex_to_ascii(code.substr(138)); - - expect(reason.substring(0, 5)).equal(revertCode); } } @@ -139,7 +111,6 @@ export async function postErc20Deposit( provider, wallet, franklinDeployedContract, - priorityQueueDeployedContract, token, depositAmount, fee, @@ -149,104 +120,66 @@ export async function postErc20Deposit( ) { await token.approve(franklinDeployedContract.address, depositAmount); - const franklinAddressBinary = Buffer.from(franklinAddress, "hex"); - - const oldOpenPriorityRequests = await priorityQueueDeployedContract.totalOpenPriorityRequests(); - const oldCommittedPriorityRequests = await priorityQueueDeployedContract.totalCommittedPriorityRequests(); - const oldFirstPriorityRequestId = await priorityQueueDeployedContract.firstPriorityRequestId(); - - const tx = await franklinDeployedContract.depositERC20( - token.address, - depositAmount, - franklinAddressBinary, - {value: txValue, gasLimit: bigNumberify("6000000")}, - ); - - if (!revertCode) { - const receipt = await tx.wait(); - const event = receipt.events[3].args; - - expect(event.owner).equal(wallet.address); + const receipt = await executeTransaction(async () => { + return franklinDeployedContract.depositERC20( + token.address, + depositAmount, + franklinAddress, + {value: txValue, gasLimit: bigNumberify("6000000")}, + ); + }, revertCode); + if (receipt) { + const event = receipt.events[1].args; + expect(event.owner).equal(franklinAddress); expect(event.amount).equal(depositAmount); - //FIXME: expect(event.fee).equal(fee); - expect(event.franklinAddress).equal("0x" + franklinAddress); - - const newOpenPriorityRequests = await priorityQueueDeployedContract.totalOpenPriorityRequests(); - const newCommittedPriorityRequests = await priorityQueueDeployedContract.totalCommittedPriorityRequests(); - const newFirstPriorityRequestId = await priorityQueueDeployedContract.firstPriorityRequestId(); - - expect(newOpenPriorityRequests - oldOpenPriorityRequests).equal(1); - expect(newCommittedPriorityRequests - oldCommittedPriorityRequests).equal(0); - expect(newFirstPriorityRequestId - oldFirstPriorityRequestId).equal(0); - - //console.log("Posted new deposit"); - } else { - const receipt = await tx.wait() - .catch(() => { - }); - if (receipt && receipt.status) { - expect(receipt.status, `tx should fail ${receipt.transactionHash}`).not.eq(1); - } - - const code = await provider.call(tx, tx.blockNumber); - const reason = hex_to_ascii(code.substr(138)); - - expect(reason.substring(0, 5)).equal(revertCode); } } export async function postBlockCommit( - provider, wallet, franklinDeployedContract, - blockNumber, - feeAcc, - newRoot, - pubData, + commitArgs: { + blockNumber, + feeAcc, + newRoot, + pubData, + witnessData, + witnessSizes, + }, onchainOperationsNumber, priorityOperationsNumber, commitment, revertCode, triggerExodus = false, ) { - const root = Buffer.from(newRoot, "hex"); - const tx = await franklinDeployedContract.commitBlock( - blockNumber, - feeAcc, - root, - pubData, - { - gasLimit: bigNumberify("500000"), - }, - ); - if (!revertCode) { - - const commitReceipt = await tx.wait(); - const commitEvents = commitReceipt.events; + const root = Buffer.from(commitArgs.newRoot, "hex"); + const receipt = await executeTransaction(async () => { + return await franklinDeployedContract.commitBlock( + commitArgs.blockNumber, + commitArgs.feeAcc, + root, + commitArgs.pubData, + commitArgs.witnessData, commitArgs.witnessSizes, + { + gasLimit: bigNumberify("500000"), + }, + ); + }, revertCode); + if (receipt) { + const commitEvents = receipt.events; const commitedEvent1 = commitEvents[0]; if (!triggerExodus) { - expect(commitedEvent1.args.blockNumber).equal(blockNumber); - expect((await franklinDeployedContract.blocks(blockNumber)).onchainOperations).equal(onchainOperationsNumber); - expect((await franklinDeployedContract.blocks(blockNumber)).priorityOperations).equal(priorityOperationsNumber); - //FIXME: why is this failing on ganache? - //expect((await franklinDeployedContract.blocks(blockNumber)).commitment).equal(commitment); - expect((await franklinDeployedContract.blocks(blockNumber)).stateRoot).equal("0x" + newRoot); - expect((await franklinDeployedContract.blocks(blockNumber)).validator).equal(wallet.address); + // expect(commitedEvent1.args.blockNumber).equal(blockNumber); + // expect((await franklinDeployedContract.blocks(blockNumber)).onchainOperations).equal(onchainOperationsNumber); + // expect((await franklinDeployedContract.blocks(blockNumber)).priorityOperations).equal(priorityOperationsNumber); + // //FIXME: why is this failing on ganache? + // //expect((await franklinDeployedContract.blocks(blockNumber)).commitment).equal(commitment); + // expect((await franklinDeployedContract.blocks(blockNumber)).stateRoot).equal("0x" + newRoot); + // expect((await franklinDeployedContract.blocks(blockNumber)).validator).equal(wallet.address); } else { expect(commitedEvent1.event, "ExodusEvent expected").eq("ExodusMode"); } - } else { - const receipt = await tx.wait() - .catch(() => { - }); - if (receipt && receipt.status) { - expect(receipt.status, `tx should fail ${receipt.transactionHash}`).not.eq(1); - } - - const code = await provider.call(tx, tx.blockNumber); - const reason = hex_to_ascii(code.substr(138)); - expect(reason.substring(0, 5)).equal(revertCode); } } @@ -291,25 +224,15 @@ export async function withdrawEthFromContract( revertCode, ) { const oldBalance = await wallet.getBalance(); - const exitTx = await franklinDeployedContract.withdrawETH(balanceToWithdraw, { - gasLimit: bigNumberify("6000000"), - }); - if (!revertCode) { - const exitTxReceipt = await exitTx.wait(); - const gasUsed = exitTxReceipt.gasUsed.mul(await provider.getGasPrice()); + const receipt = await executeTransaction(async () => { + return franklinDeployedContract.withdrawETH(balanceToWithdraw, { + gasLimit: bigNumberify("6000000"), + }); + }, revertCode); + if (receipt) { + const gasUsed = receipt.gasUsed.mul(await provider.getGasPrice()); const newBalance = await wallet.getBalance(); expect(newBalance.sub(oldBalance).add(gasUsed)).eq(balanceToWithdraw); - } else { - const receipt = await exitTx.wait() - .catch(() => { - }); - if (receipt && receipt.status) { - expect(receipt.status, `tx should fail ${receipt.transactionHash}`).not.eq(1); - } - - const code = await provider.call(exitTx, exitTx.blockNumber); - const reason = hex_to_ascii(code.substr(138)); - expect(reason.substring(0, 5)).equal(revertCode); } } @@ -324,64 +247,45 @@ export async function withdrawErcFromContract( ) { const rollupBalance = await franklinDeployedContract.balancesToWithdraw(wallet.address, tokenId); const oldBalance = await token.balanceOf(wallet.address); - const exitTx = await franklinDeployedContract.withdrawERC20( - token.address, - balanceToWithdraw, - {gasLimit: bigNumberify("500000")}, - ); - if (!revertCode) { - await exitTx.wait(); + + + const receipt = await executeTransaction(async () => { + return franklinDeployedContract.withdrawERC20( + token.address, + balanceToWithdraw, + {gasLimit: bigNumberify("500000")}, + ); + }, revertCode); + if (receipt) { const newBalance = await token.balanceOf(wallet.address); const newRollupBalance = await franklinDeployedContract.balancesToWithdraw(wallet.address, tokenId); expect(rollupBalance - newRollupBalance).equal(bigNumberify(balanceToWithdraw)); expect(newBalance.sub(oldBalance)).eq(balanceToWithdraw); - } else { - const receipt = await exitTx.wait() - .catch(() => { - }); - if (receipt && receipt.status) { - expect(receipt.status, `tx should fail ${receipt.transactionHash}`).not.eq(1); - } - - const code = await provider.call(exitTx, exitTx.blockNumber); - const reason = hex_to_ascii(code.substr(138)); - expect(reason.substring(0, 5)).equal(revertCode); } } export async function postFullExit( provider, franklinDeployedContract, - priorityQueueDeployedContract, accountId, tokenAddress, value, revertCode, ) { - const beforeTotalOpenRequests = await priorityQueueDeployedContract.totalOpenPriorityRequests(); - const tx = await franklinDeployedContract.fullExit( - accountId, - tokenAddress, - { - gasLimit: bigNumberify("500000"), - value, - }, - ); - if (!revertCode) { - await tx.wait(); - const afterTotalOpenRequests = await priorityQueueDeployedContract.totalOpenPriorityRequests(); + const beforeTotalOpenRequests = await franklinDeployedContract.totalOpenPriorityRequests(); + const receipt = await executeTransaction(async () => { + return await franklinDeployedContract.fullExit( + accountId, + tokenAddress, + { + gasLimit: bigNumberify("500000"), + value, + }, + ); + }, revertCode); + if (receipt) { + const afterTotalOpenRequests = await franklinDeployedContract.totalOpenPriorityRequests(); expect(afterTotalOpenRequests - beforeTotalOpenRequests).equal(1); - } else { - const receipt = await tx.wait() - .catch(() => { - }); - if (receipt && receipt.status) { - expect(receipt.status, `tx should fail ${receipt.transactionHash}`).not.eq(1); - } - - const code = await provider.call(tx, tx.blockNumber); - const reason = hex_to_ascii(code.substr(138)); - expect(reason.substring(0, 5)).equal(revertCode); } } diff --git a/contracts/test/unit_tests/bytes_test.js b/contracts/test/unit_tests/bytes_test.js index ec74ace266..ae20af923e 100644 --- a/contracts/test/unit_tests/bytes_test.js +++ b/contracts/test/unit_tests/bytes_test.js @@ -26,12 +26,12 @@ describe("Bytes unit test", function () { }); it("should fail to read bytes beyond range", async () => { - let revertReason = await getCallRevertReason( () => bytesTestContract.read("0x0102030405060708", 8, 2) ) + let {revertReason} = await getCallRevertReason( () => bytesTestContract.read("0x0102030405060708", 8, 2) ) expect(revertReason).equal("bse11") }); it("should fail to read too many bytes", async () => { - let revertReason = await getCallRevertReason( () => bytesTestContract.read("0x0102030405060708", 4, 5) ) + let {revertReason} = await getCallRevertReason( () => bytesTestContract.read("0x0102030405060708", 4, 5) ) expect(revertReason).equal("bse11") }); @@ -44,4 +44,14 @@ describe("Bytes unit test", function () { expect(r.offset).equal(3) }); + it("should convert to hex", async () => { + const x = Buffer.alloc(256); + for (let b = 0; b < 255; b++) { + x[b] = b + } + let hexString = x.toString("hex").toLowerCase(); + let r = await bytesTestContract.bytesToHexConvert(x); + expect(r).eq(hexString); + }); + }); diff --git a/contracts/test/unit_tests/common.js b/contracts/test/unit_tests/common.js index 25a960fa64..8724f70cd7 100644 --- a/contracts/test/unit_tests/common.js +++ b/contracts/test/unit_tests/common.js @@ -3,6 +3,8 @@ const { expect, use } = require("chai") const { createMockProvider, getWallets, solidity, deployContract } = require("ethereum-waffle"); const { bigNumberify, parseEther, hexlify, formatEther } = require("ethers/utils"); +const IERC20_INTERFACE = require("openzeppelin-solidity/build/contracts/IERC20"); + // For: geth // const provider = new ethers.providers.JsonRpcProvider(process.env.WEB3_URL); @@ -28,12 +30,13 @@ async function deployTestContract(file) { async function getCallRevertReason(f) { let revertReason = "VM did not revert" + let result; try { - let r = await f() + result = await f(); } catch(e) { revertReason = (e.reason && e.reason[0]) || e.results[e.hashes[0]].reason } - return revertReason + return {revertReason, result}; } module.exports = { @@ -41,5 +44,6 @@ module.exports = { wallet, exitWallet, deployTestContract, - getCallRevertReason + getCallRevertReason, + IERC20_INTERFACE } \ No newline at end of file diff --git a/contracts/test/unit_tests/governance_test.js b/contracts/test/unit_tests/governance_test.js index d5dd489af4..1f008a6ace 100644 --- a/contracts/test/unit_tests/governance_test.js +++ b/contracts/test/unit_tests/governance_test.js @@ -16,7 +16,7 @@ describe("Governance unit test", function () { it("checking correctness of using MAX_AMOUNT_OF_REGISTERED_TOKENS constant", async () => { let MAX_AMOUNT_OF_REGISTERED_TOKENS = await testContract.get_MAX_AMOUNT_OF_REGISTERED_TOKENS(); for (let step = 1; step <= MAX_AMOUNT_OF_REGISTERED_TOKENS + 1; step++) { - let revertReason = await getCallRevertReason( () => testContract.addToken("0x" + step.toString().padStart(40, '0')) ) + let {revertReason} = await getCallRevertReason( () => testContract.addToken("0x" + step.toString().padStart(40, '0')) ) if (step != MAX_AMOUNT_OF_REGISTERED_TOKENS + 1) { expect(revertReason).equal("VM did not revert") } diff --git a/contracts/test/unit_tests/zksync_test.ts b/contracts/test/unit_tests/zksync_test.ts new file mode 100644 index 0000000000..48d66b67d1 --- /dev/null +++ b/contracts/test/unit_tests/zksync_test.ts @@ -0,0 +1,604 @@ +import { + addTestERC20Token, addTestNotApprovedERC20Token, + franklinTestContractCode, + governanceTestContractCode, mintTestERC20Token, + verifierTestContractCode, Deployer +} from "../../src.ts/deploy"; +import {BigNumber, bigNumberify, BigNumberish, parseEther} from "ethers/utils"; +import {ETHProxy} from "zksync"; +import {Address, TokenAddress} from "zksync/build/types"; +import {AddressZero} from "ethers/constants"; +import {Contract, ethers} from "ethers"; + +const { expect } = require("chai") +const { deployContract } = require("ethereum-waffle"); +const { wallet, exitWallet, deployTestContract, getCallRevertReason, IERC20_INTERFACE} = require("./common"); +import * as zksync from "zksync"; + +const TEST_PRIORITY_EXPIRATION = 16; + + +describe("ZK Sync signature verification unit tests", function () { + this.timeout(50000); + + let testContract; + let randomWallet = ethers.Wallet.createRandom(); + before(async () => { + testContract = await deployContract(wallet, require('../../build/ZKSyncUnitTest'), [AddressZero, AddressZero, AddressZero, Buffer.alloc(32, 0)], { + gasLimit: 6000000, + }); + }); + + it("pubkey hash signature verification success", async () => { + const pubkeyHash = "sync:fefefefefefefefefefefefefefefefefefefefe"; + const nonce = 0x11223344; + const signature = await zksync.utils.signChangePubkeyMessage(randomWallet, pubkeyHash, nonce); + let {revertReason, result} = await getCallRevertReason(() => + testContract.changePubkeySignatureCheck(signature, pubkeyHash.replace("sync:", "0x"), nonce, randomWallet.address)); + expect(result).eq(true); + }); + + it("pubkey hash signature verification incorrect nonce", async () => { + const incorrectNonce = 0x11223345; + const pubkeyHash = "sync:fefefefefefefefefefefefefefefefefefefefe"; + const nonce = 0x11223344; + const signature = await zksync.utils.signChangePubkeyMessage(randomWallet, pubkeyHash, nonce); + let {result} = await getCallRevertReason(() => + testContract.changePubkeySignatureCheck(signature, pubkeyHash.replace("sync:", "0x"), incorrectNonce, randomWallet.address)); + expect(result).eq(false); + }); + + it("pubkey hash signature verification incorrect pubkey hash", async () => { + const incorrectPubkeyHash = "sync:aaaafefefefefefefefefefefefefefefefefefe"; + const pubkeyHash = "sync:fefefefefefefefefefefefefefefefefefefefe"; + const nonce = 0x11223344; + const signature = await zksync.utils.signChangePubkeyMessage(randomWallet, pubkeyHash, nonce); + let {result} = await getCallRevertReason(() => + testContract.changePubkeySignatureCheck(signature, incorrectPubkeyHash.replace("sync:", "0x"), nonce, randomWallet.address)); + expect(result).eq(false); + }); + + it("pubkey hash signature verification incorrect signer", async () => { + const incorrectSignerAddress = wallet.address; + const pubkeyHash = "sync:fefefefefefefefefefefefefefefefefefefefe"; + const nonce = 0x11223344; + const signature = await zksync.utils.signChangePubkeyMessage(randomWallet, pubkeyHash, nonce); + let {result} = await getCallRevertReason(() => + testContract.changePubkeySignatureCheck(signature, pubkeyHash.replace("sync:", "0x"), nonce, incorrectSignerAddress)); + expect(result).eq(false); + }); + + it("signature verification success", async () => { + for(const message of [Buffer.from("msg", "ascii"), Buffer.alloc(0), Buffer.alloc(10, 1)]) { + const signature = await wallet.signMessage(message); + const sinedMessage = Buffer.concat([Buffer.from(`\x19Ethereum Signed Message:\n${message.length}`, "ascii"), message]); + const address = await testContract.testVerifyEthereumSignature(signature, sinedMessage); + expect(address, `address mismatch, message ${message.toString("hex")}`).eq(wallet.address); + } + }); +}); + +describe("ZK priority queue ops unit tests", function () { + this.timeout(50000); + + let zksyncContract; + let tokenContract; + let ethProxy; + let operationTestContract; + before(async () => { + const deployer = new Deployer(wallet, true); + const governanceDeployedContract = await deployer.deployGovernance(); + await deployer.deployVerifier(); + process.env.OPERATOR_FRANKLIN_ADDRESS = wallet.address; + zksyncContract = await deployer.deployFranklin(); + await governanceDeployedContract.setValidator(wallet.address, true); + tokenContract = await addTestERC20Token(wallet, governanceDeployedContract); + await mintTestERC20Token(wallet, tokenContract); + ethProxy = new ETHProxy(wallet.provider, {mainContract: zksyncContract.address, govContract: governanceDeployedContract.address}); + + operationTestContract = await deployTestContract('../../build/OperationsTest'); + }); + + async function performDeposit(to: Address, token: TokenAddress, depositAmount: BigNumber, feeInEth: BigNumberish) { + const openedRequests = await zksyncContract.totalOpenPriorityRequests(); + const depositOwner = wallet.address; + + let tx; + if (token === ethers.constants.AddressZero) { + tx = await zksyncContract.depositETH(depositAmount, depositOwner, {value: depositAmount.add(feeInEth)}); + } else { + tx = await zksyncContract.depositERC20(token, depositAmount, depositOwner, {value: feeInEth}); + } + const receipt = await tx.wait(); + + const deadlineBlock = receipt.blockNumber + TEST_PRIORITY_EXPIRATION; + + let priorityQueueEvent; + for (const event of receipt.logs) { + const parsedLog = zksyncContract.interface.parseLog(event); + if (parsedLog && parsedLog.name === "NewPriorityRequest") { + priorityQueueEvent = parsedLog; + break; + } + } + expect(priorityQueueEvent.name, "event name").eq("NewPriorityRequest"); + expect(priorityQueueEvent.values.sender, "sender address").eq(wallet.address); + expect(priorityQueueEvent.values.serialId, "request id").eq(openedRequests); + expect(priorityQueueEvent.values.opType, "request type").eq(1); + expect(priorityQueueEvent.values.fee.toString(), "request fee").eq(feeInEth.toString()); + expect(priorityQueueEvent.values.expirationBlock, "expiration block").eq(deadlineBlock); + const parsedDepositPubdata = await operationTestContract.parseDepositFromPubdata(priorityQueueEvent.values.pubData); + + expect(parsedDepositPubdata.tokenId, "parsed token id").eq(await ethProxy.resolveTokenId(token)); + expect(parsedDepositPubdata.amount.toString(), "parsed amount").eq(depositAmount.toString()); + expect(parsedDepositPubdata.owner, "parsed owner").eq(depositOwner); + } + + async function performFullExitRequest(accountId: number, token: TokenAddress, feeInEth: BigNumberish) { + const openedRequests = await zksyncContract.totalOpenPriorityRequests(); + const tx = await zksyncContract.fullExit(accountId, token, {value: feeInEth}); + const receipt = await tx.wait(); + + const deadlineBlock = receipt.blockNumber + TEST_PRIORITY_EXPIRATION; + + let priorityQueueEvent; + for (const event of receipt.logs) { + const parsedLog = zksyncContract.interface.parseLog(event); + if (parsedLog && parsedLog.name === "NewPriorityRequest") { + priorityQueueEvent = parsedLog; + break; + } + } + expect(priorityQueueEvent.name, "event name").eq("NewPriorityRequest"); + expect(priorityQueueEvent.values.sender, "sender address").eq(wallet.address); + expect(priorityQueueEvent.values.serialId, "request id").eq(openedRequests); + expect(priorityQueueEvent.values.opType, "request type").eq(6); + expect(priorityQueueEvent.values.fee.toString(), "request fee").eq(feeInEth.toString()); + expect(priorityQueueEvent.values.expirationBlock, "expiration block").eq(deadlineBlock); + + const parsedFullExitPubdata = await operationTestContract.parseFullExitFromPubdata(priorityQueueEvent.values.pubData); + expect(parsedFullExitPubdata.accountId, "parsed account id").eq(accountId); + expect(parsedFullExitPubdata.owner, "parsed owner").eq(wallet.address); + expect(parsedFullExitPubdata.tokenId, "parsed token id").eq(await ethProxy.resolveTokenId(token)); + expect(parsedFullExitPubdata.amount.toString(), "parsed amount").eq("0"); + } + + it("success ETH deposits", async () => { + zksyncContract.connect(wallet); + const tokenAddress = ethers.constants.AddressZero; + const depositAmount = parseEther("1.0"); + const fee = await ethProxy.estimateDepositFeeInETHToken(tokenAddress); + + await performDeposit(wallet.address, tokenAddress, depositAmount, fee); + await performDeposit(ethers.Wallet.createRandom().address, tokenAddress, depositAmount, fee); + }); + + it("success ERC20 deposits", async () => { + zksyncContract.connect(wallet); + const tokenAddress = tokenContract.address; + const depositAmount = parseEther("1.0"); + const fee = await ethProxy.estimateDepositFeeInETHToken(tokenAddress); + + tokenContract.connect(wallet); + await tokenContract.approve(zksyncContract.address, depositAmount); + await performDeposit(wallet.address, tokenAddress, depositAmount, fee); + await tokenContract.approve(zksyncContract.address, depositAmount); + await performDeposit(ethers.Wallet.createRandom().address, tokenAddress, depositAmount, fee); + }); + + it("success FullExit request", async () => { + zksyncContract.connect(wallet); + const accountId = 1; + const fee = await ethProxy.estimateEmergencyWithdrawFeeInETHToken(); + + await performFullExitRequest(accountId, ethers.constants.AddressZero, fee); + await performFullExitRequest(accountId, tokenContract.address, fee); + }); +}); + +async function onchainBalance(ethWallet: ethers.Wallet, token: Address): Promise { + if (token === ethers.constants.AddressZero) { + return ethWallet.getBalance(); + } else { + const erc20contract = new Contract( + token, + IERC20_INTERFACE.abi, + ethWallet, + ); + return bigNumberify(await erc20contract.balanceOf(ethWallet.address)); + } +} + +describe("ZK Sync withdraw unit tests", function () { + this.timeout(50000); + + let zksyncContract; + let tokenContract; + let incorrectTokenContract; + let ethProxy; + before(async () => { + const deployer = new Deployer(wallet, true); + const governanceDeployedContract = await deployer.deployGovernance(); + await deployer.deployVerifier(); + process.env.OPERATOR_FRANKLIN_ADDRESS = wallet.address; + deployer.bytecodes.Franklin = require("../../build/ZKSyncUnitTest"); + zksyncContract = await deployer.deployFranklin(); + await governanceDeployedContract.setValidator(wallet.address, true); + tokenContract = await addTestERC20Token(wallet, governanceDeployedContract); + await mintTestERC20Token(wallet, tokenContract); + ethProxy = new ETHProxy(wallet.provider, {mainContract: zksyncContract.address, govContract: governanceDeployedContract.address}); + + incorrectTokenContract = await addTestNotApprovedERC20Token(wallet); + await mintTestERC20Token(wallet, tokenContract); + }); + + async function performWithdraw(ethWallet: ethers.Wallet, token: TokenAddress, tokenId: number, amount: BigNumber) { + let gasFee: BigNumber; + const balanceBefore = await onchainBalance(ethWallet, token); + const contractBalanceBefore = bigNumberify(await zksyncContract.balancesToWithdraw(ethWallet.address, tokenId)); + if (token === ethers.constants.AddressZero) { + const tx = await zksyncContract.withdrawETH(amount); + const receipt = await tx.wait(); + gasFee = receipt.gasUsed.mul(await ethWallet.provider.getGasPrice()); + } else { + await zksyncContract.withdrawERC20(token, amount); + } + const balanceAfter = await onchainBalance(ethWallet, token); + + const expectedBalance = token == AddressZero ? balanceBefore.add(amount).sub(gasFee) : balanceBefore.add(amount); + expect(balanceAfter.toString(), "withdraw account balance mismatch").eq(expectedBalance.toString()); + + const contractBalanceAfter = bigNumberify(await zksyncContract.balancesToWithdraw(ethWallet.address, tokenId)); + const expectedContractBalance = contractBalanceBefore.sub(amount); + expect(contractBalanceAfter.toString(), "withdraw contract balance mismatch").eq(expectedContractBalance.toString()); + } + + it("Withdraw ETH success", async () => { + zksyncContract.connect(wallet); + const withdrawAmount = parseEther("1.0"); + + const sendETH = await wallet.sendTransaction({to: zksyncContract.address, value: withdrawAmount.mul(2)}); + await sendETH.wait(); + + await zksyncContract.setBalanceToWithdraw(wallet.address, 0, withdrawAmount); + await performWithdraw(wallet, AddressZero, 0, withdrawAmount); + + await zksyncContract.setBalanceToWithdraw(wallet.address, 0, withdrawAmount); + await performWithdraw(wallet, AddressZero, 0, withdrawAmount.div(2)); + await performWithdraw(wallet, AddressZero, 0, withdrawAmount.div(2)); + }); + + it("Withdraw ETH incorrect ammount", async () => { + zksyncContract.connect(wallet); + const withdrawAmount = parseEther("1.0"); + + const sendETH = await wallet.sendTransaction({to: zksyncContract.address, value: withdrawAmount}); + await sendETH.wait(); + + await zksyncContract.setBalanceToWithdraw(wallet.address, 0, withdrawAmount); + const {revertReason} = await getCallRevertReason( async () => await performWithdraw(wallet, AddressZero, 0, withdrawAmount.add(1))); + expect(revertReason, "wrong revert reason").eq("frw11"); + }); + + it("Withdraw ERC20 success", async () => { + zksyncContract.connect(wallet); + const withdrawAmount = parseEther("1.0"); + + const sendERC20 = await tokenContract.transfer(zksyncContract.address, withdrawAmount.mul(2)); + await sendERC20.wait(); + const tokenId = await ethProxy.resolveTokenId(tokenContract.address); + + await zksyncContract.setBalanceToWithdraw(wallet.address, tokenId, withdrawAmount); + await performWithdraw(wallet, tokenContract.address, tokenId, withdrawAmount); + + await zksyncContract.setBalanceToWithdraw(wallet.address, tokenId, withdrawAmount); + await performWithdraw(wallet, tokenContract.address, tokenId, withdrawAmount.div(2)); + await performWithdraw(wallet, tokenContract.address, tokenId, withdrawAmount.div(2)); + }); + + it("Withdraw ERC20 incorrect amount", async () => { + zksyncContract.connect(wallet); + const withdrawAmount = parseEther("1.0"); + + const sendERC20 = await tokenContract.transfer(zksyncContract.address, withdrawAmount); + await sendERC20.wait(); + const tokenId = await ethProxy.resolveTokenId(tokenContract.address); + + await zksyncContract.setBalanceToWithdraw(wallet.address, tokenId, withdrawAmount); + + const {revertReason} = await getCallRevertReason( async () => await performWithdraw(wallet, tokenContract.address, tokenId, withdrawAmount.add(1))); + expect(revertReason, "wrong revert reason").eq("frw11"); + }); + + it("Withdraw ERC20 unsupported token", async () => { + zksyncContract.connect(wallet); + const withdrawAmount = parseEther("1.0"); + + const {revertReason} = await getCallRevertReason( async () => await performWithdraw(wallet, incorrectTokenContract.address, 1, withdrawAmount.add(1))); + expect(revertReason, "wrong revert reason").eq("gvs12"); + }); + + it("Complete pending withdawals, eth, known erc20", async () => { + zksyncContract.connect(wallet); + const withdrawAmount = parseEther("1.0"); + const withdrawsToCancel = 5; + + await wallet.sendTransaction({to: zksyncContract.address, value: withdrawAmount}); + await tokenContract.transfer(zksyncContract.address, withdrawAmount); + + + for (const tokenAddress of [AddressZero, tokenContract.address]) { + const tokenId = await ethProxy.resolveTokenId(tokenAddress); + + await zksyncContract.setBalanceToWithdraw(exitWallet.address, tokenId, 0); + await zksyncContract.addPendingWithdrawal(exitWallet.address, tokenId, withdrawAmount.div(2)); + await zksyncContract.addPendingWithdrawal(exitWallet.address, tokenId, withdrawAmount.div(2)); + + const onchainBalBefore = await onchainBalance(exitWallet, tokenAddress); + + await zksyncContract.completeWithdrawals(withdrawsToCancel); + + const onchainBalAfter = await onchainBalance(exitWallet, tokenAddress); + + expect(onchainBalAfter.sub(onchainBalBefore)).eq(withdrawAmount.toString()); + } + }); +}); + +describe("ZK Sync auth pubkey onchain unit tests", function () { + this.timeout(50000); + + let zksyncContract; + let tokenContract; + let ethProxy; + before(async () => { + const deployer = new Deployer(wallet, true); + const governanceDeployedContract = await deployer.deployGovernance(); + await deployer.deployVerifier(); + process.env.OPERATOR_FRANKLIN_ADDRESS = wallet.address; + deployer.bytecodes.Franklin = require("../../build/ZKSyncUnitTest"); + zksyncContract = await deployer.deployFranklin(); + await governanceDeployedContract.setValidator(wallet.address, true); + tokenContract = await addTestERC20Token(wallet, governanceDeployedContract); + await mintTestERC20Token(wallet, tokenContract); + ethProxy = new ETHProxy(wallet.provider, {mainContract: zksyncContract.address, govContract: governanceDeployedContract.address}); + }); + + it("Auth pubkey success", async () => { + zksyncContract.connect(wallet); + + const nonce = 0x1234; + const pubkeyHash = "0xfefefefefefefefefefefefefefefefefefefefe"; + + const receipt = await (await zksyncContract.authPubkeyHash(pubkeyHash, nonce)).wait(); + let authEvent; + for (const event of receipt.logs) { + const parsedLog = zksyncContract.interface.parseLog(event); + if (parsedLog && parsedLog.name === "FactAuth") { + authEvent = parsedLog; + break; + } + } + + expect(authEvent.values.sender, "event sender incorrect").eq(wallet.address); + expect(authEvent.values.nonce, "event nonce incorrect").eq(nonce); + expect(authEvent.values.fact, "event fact incorrect").eq(pubkeyHash); + }); + + it("Auth pubkey rewrite fail", async () => { + zksyncContract.connect(wallet); + + const nonce = 0xdead; + const pubkeyHash = "0xfefefefefefefefefefefefefefefefefefefefe"; + + await zksyncContract.authPubkeyHash(pubkeyHash, nonce); + // + const otherPubkeyHash = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + const {revertReason} = await getCallRevertReason(async () => await zksyncContract.authPubkeyHash(otherPubkeyHash, nonce) ); + expect(revertReason, "revert reason incorrect").eq("ahf11"); + }); + + it("Auth pubkey incorrect length fail", async () => { + zksyncContract.connect(wallet); + const nonce = 0x7656; + const shortPubkeyHash = "0xfefefefefefefefefefefefefefefefefefefe"; + const longPubkeyHash = "0xfefefefefefefefefefefefefefefefefefefefefe"; + + for (const pkHash of [shortPubkeyHash, longPubkeyHash]) { + const {revertReason} = await getCallRevertReason(async () => await zksyncContract.authPubkeyHash(shortPubkeyHash, nonce) ); + expect(revertReason, "revert reason incorrect").eq("ahf10"); + } + }); +}); + +describe("ZK Sync test process next operation", function () { + this.timeout(50000); + + let zksyncContract; + let tokenContract; + let incorrectTokenContract; + let ethProxy; + before(async () => { + const deployer = new Deployer(wallet, true); + const governanceDeployedContract = await deployer.deployGovernance(); + await deployer.deployVerifier(); + process.env.OPERATOR_FRANKLIN_ADDRESS = wallet.address; + deployer.bytecodes.Franklin = require("../../build/ZKSyncUnitTest"); + zksyncContract = await deployer.deployFranklin(); + await governanceDeployedContract.setValidator(wallet.address, true); + tokenContract = await addTestERC20Token(wallet, governanceDeployedContract); + await mintTestERC20Token(wallet, tokenContract); + ethProxy = new ETHProxy(wallet.provider, {mainContract: zksyncContract.address, govContract: governanceDeployedContract.address}); + }); + + it("Process noop", async () => { + zksyncContract.connect(wallet); + + const committedPriorityRequestsBefore = await zksyncContract.totalCommittedPriorityRequests(); + const totalOnchainOpsBefore = await zksyncContract.totalOnchainOps(); + + const pubdata = Buffer.alloc(8, 0); + await zksyncContract.testProcessNextOperation(0, pubdata, "0x", pubdata.length); + + const committedPriorityRequestsAfter = await zksyncContract.totalCommittedPriorityRequests(); + const totalOnchainOpsAfter = await zksyncContract.totalOnchainOps(); + expect(committedPriorityRequestsAfter, "priority request number").eq(committedPriorityRequestsBefore); + expect(totalOnchainOpsAfter, "committed onchain ops number").eq(totalOnchainOpsBefore); + }); + + it("Process transfer", async () => { + zksyncContract.connect(wallet); + + const committedPriorityRequestsBefore = await zksyncContract.totalCommittedPriorityRequests(); + const totalOnchainOpsBefore = await zksyncContract.totalOnchainOps(); + + const pubdata = Buffer.alloc(8 * 2, 0xff); + pubdata[0] = 0x05; + await zksyncContract.testProcessNextOperation(0, pubdata, "0x", pubdata.length); + + const committedPriorityRequestsAfter = await zksyncContract.totalCommittedPriorityRequests(); + const totalOnchainOpsAfter = await zksyncContract.totalOnchainOps(); + expect(committedPriorityRequestsAfter, "priority request number").eq(committedPriorityRequestsBefore); + expect(totalOnchainOpsAfter, "committed onchain ops number").eq(totalOnchainOpsBefore); + }); + it("Process transfer to new", async () => { + zksyncContract.connect(wallet); + + const committedPriorityRequestsBefore = await zksyncContract.totalCommittedPriorityRequests(); + const totalOnchainOpsBefore = await zksyncContract.totalOnchainOps(); + + const pubdata = Buffer.alloc(8 * 5, 0xff); + pubdata[0] = 0x02; + await zksyncContract.testProcessNextOperation(0, pubdata, "0x", pubdata.length); + + const committedPriorityRequestsAfter = await zksyncContract.totalCommittedPriorityRequests(); + const totalOnchainOpsAfter = await zksyncContract.totalOnchainOps(); + expect(committedPriorityRequestsAfter, "priority request number").eq(committedPriorityRequestsBefore); + expect(totalOnchainOpsAfter, "committed onchain ops number").eq(totalOnchainOpsBefore); + }); + + it("Process deposit", async () => { + zksyncContract.connect(wallet); + const depositAmount = parseEther("0.7"); + + await zksyncContract.depositETH(depositAmount, wallet.address, {value: parseEther("0.8")}); + + const committedPriorityRequestsBefore = await zksyncContract.totalCommittedPriorityRequests(); + const totalOnchainOpsBefore = await zksyncContract.totalOnchainOps(); + + // construct deposit pubdata + const pubdata = Buffer.alloc(8 * 6, 0); + pubdata[0] = 0x01; + pubdata.writeUIntBE(0xaabbff, 1, 3); + Buffer.from(depositAmount.toHexString().substr(2).padStart(16 * 2, "0"), "hex").copy(pubdata, 6); + Buffer.from(wallet.address.substr(2), "hex").copy(pubdata, 22); + + await zksyncContract.testProcessNextOperation(0, pubdata, "0x", pubdata.length); + + const committedPriorityRequestsAfter = await zksyncContract.totalCommittedPriorityRequests(); + const totalOnchainOpsAfter = await zksyncContract.totalOnchainOps(); + expect(committedPriorityRequestsAfter - 1, "priority request number").eq(committedPriorityRequestsBefore); + expect(totalOnchainOpsAfter - 1, "committed onchain ops number").eq(totalOnchainOpsBefore); + }); + + it("Process partial exit", async () => { + zksyncContract.connect(wallet); + + const committedPriorityRequestsBefore = await zksyncContract.totalCommittedPriorityRequests(); + const totalOnchainOpsBefore = await zksyncContract.totalOnchainOps(); + + // construct deposit pubdata + const pubdata = Buffer.alloc(8 * 6, 0); + pubdata[0] = 0x03; + + await zksyncContract.testProcessNextOperation(0, pubdata, "0x", pubdata.length); + + const committedPriorityRequestsAfter = await zksyncContract.totalCommittedPriorityRequests(); + const totalOnchainOpsAfter = await zksyncContract.totalOnchainOps(); + expect(committedPriorityRequestsAfter, "priority request number").eq(committedPriorityRequestsBefore); + expect(totalOnchainOpsAfter - 1, "committed onchain ops number").eq(totalOnchainOpsBefore); + }); + + it("Process full exit", async () => { + zksyncContract.connect(wallet); + const tokenId = 0x01; + const fullExitAmount = parseEther("0.7"); + const accountId = 0xaabbff; + + await zksyncContract.fullExit(accountId, tokenContract.address, {value: parseEther("0.1")}); + + const committedPriorityRequestsBefore = await zksyncContract.totalCommittedPriorityRequests(); + const totalOnchainOpsBefore = await zksyncContract.totalOnchainOps(); + + // construct deposit pubdata + const pubdata = Buffer.alloc(8 * 6, 0); + pubdata[0] = 0x06; + pubdata.writeUIntBE(accountId, 1, 3); + Buffer.from(wallet.address.substr(2), "hex").copy(pubdata, 4); + pubdata.writeUInt16BE(tokenId, 24); + Buffer.from(fullExitAmount.toHexString().substr(2).padStart(16 * 2, "0"), "hex").copy(pubdata, 26); + + await zksyncContract.testProcessNextOperation(0, pubdata, "0x", pubdata.length); + + const committedPriorityRequestsAfter = await zksyncContract.totalCommittedPriorityRequests(); + const totalOnchainOpsAfter = await zksyncContract.totalOnchainOps(); + expect(committedPriorityRequestsAfter - 1, "priority request number").eq(committedPriorityRequestsBefore); + expect(totalOnchainOpsAfter - 1, "committed onchain ops number").eq(totalOnchainOpsBefore); + }); + + it("Change pubkey with auth", async () => { + zksyncContract.connect(wallet); + + const nonce = 0x1234; + const pubkeyHash = "0xfefefefefefefefefefefefefefefefefefefefe"; + await zksyncContract.authPubkeyHash(pubkeyHash, nonce); + + const accountId = 0xffee12; + + const committedPriorityRequestsBefore = await zksyncContract.totalCommittedPriorityRequests(); + const totalOnchainOpsBefore = await zksyncContract.totalOnchainOps(); + + // construct deposit pubdata + const pubdata = Buffer.alloc(8 * 6, 0); + pubdata[0] = 0x07; + pubdata.writeUIntBE(accountId, 1, 3); + Buffer.from(pubkeyHash.substr(2), "hex").copy(pubdata, 4); + Buffer.from(wallet.address.substr(2), "hex").copy(pubdata, 24); + pubdata.writeUInt32BE(nonce, 44); + + await zksyncContract.testProcessNextOperation(0, pubdata, "0x", pubdata.length); + + const committedPriorityRequestsAfter = await zksyncContract.totalCommittedPriorityRequests(); + const totalOnchainOpsAfter = await zksyncContract.totalOnchainOps(); + expect(committedPriorityRequestsAfter, "priority request number").eq(committedPriorityRequestsBefore); + expect(totalOnchainOpsAfter, "committed onchain ops number").eq(totalOnchainOpsBefore); + }); + + it("Change pubkey with posted signature", async () => { + zksyncContract.connect(wallet); + + const nonce = 0x1234; + const pubkeyHash = "sync:fefefefefefefefefefefefefefefefefefefefe"; + const ethWitness = await zksync.utils.signChangePubkeyMessage(wallet, pubkeyHash, nonce); + + const accountId = 0xffee12; + + const committedPriorityRequestsBefore = await zksyncContract.totalCommittedPriorityRequests(); + const totalOnchainOpsBefore = await zksyncContract.totalOnchainOps(); + + // construct deposit pubdata + const pubdata = Buffer.alloc(8 * 6, 0); + pubdata[0] = 0x07; + pubdata.writeUIntBE(accountId, 1, 3); + Buffer.from(pubkeyHash.substr(5), "hex").copy(pubdata, 4); + Buffer.from(wallet.address.substr(2), "hex").copy(pubdata, 24); + pubdata.writeUInt32BE(nonce, 44); + + await zksyncContract.testProcessNextOperation(0, pubdata, ethWitness, pubdata.length); + + const committedPriorityRequestsAfter = await zksyncContract.totalCommittedPriorityRequests(); + const totalOnchainOpsAfter = await zksyncContract.totalOnchainOps(); + expect(committedPriorityRequestsAfter, "priority request number").eq(committedPriorityRequestsBefore); + expect(totalOnchainOpsAfter, "committed onchain ops number").eq(totalOnchainOpsBefore); + }); +}); diff --git a/contracts/yarn.lock b/contracts/yarn.lock index 2d94cdd62b..588333d528 100644 --- a/contracts/yarn.lock +++ b/contracts/yarn.lock @@ -2533,22 +2533,6 @@ ethers@4.0.33, ethers@^4.0.0: uuid "2.0.1" xmlhttprequest "1.8.0" -ethers@^4.0.33: - version "4.0.39" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.39.tgz#5ce9dfffedb03936415743f63b37d96280886a47" - integrity sha512-QVtC8TTUgTrnlQjQvdFJ7fkSWKwp8HVTbKRmrdbVryrPzJHMTf3WSeRNvLF2enGyAFtyHJyFNnjN0fSshcEr9w== - dependencies: - "@types/node" "^10.3.2" - aes-js "3.0.0" - bn.js "^4.4.0" - elliptic "6.3.3" - hash.js "1.1.3" - js-sha3 "0.5.7" - scrypt-js "2.0.4" - setimmediate "1.0.4" - uuid "2.0.1" - xmlhttprequest "1.8.0" - ethjs-abi@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/ethjs-abi/-/ethjs-abi-0.2.0.tgz#d3e2c221011520fc499b71682036c14fcc2f5b25" @@ -7283,17 +7267,6 @@ yn@^3.0.0: resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== -zksync@^0.3.9: - version "0.3.10" - resolved "https://registry.yarnpkg.com/zksync/-/zksync-0.3.10.tgz#0666a2b2887c5ad84b777c4f1997392f17707eff" - integrity sha512-ZAsqo9FckFg2aSSVf/KqXZTlVX24rxF/edPDyrgOegqfILBLo3R9R650FlgMD12+/9R1BNjcEKT1fCA1DpvwbA== - dependencies: - axios "^0.19.0" - blake2b "^2.1.3" - bn.js "^5.0.0" - crypto-js "^3.1.9-1" - elliptic "^6.5.0" - ethers "^4.0.33" - js-sha256 "^0.9.0" - websocket "^1.0.30" - websocket-as-promised "^0.10.1" +"zksync@link:../js/zksync.js": + version "0.0.0" + uid "" diff --git a/core/models/src/config_options.rs b/core/models/src/config_options.rs index 67c0164ccd..4658598597 100644 --- a/core/models/src/config_options.rs +++ b/core/models/src/config_options.rs @@ -28,7 +28,6 @@ pub struct ConfigurationOptions { pub web3_url: String, pub governance_eth_addr: H160, pub governance_genesis_tx_hash: H256, - pub priority_queue_eth_addr: H160, pub operator_franklin_addr: Address, pub operator_eth_addr: H160, pub operator_private_key: H256, @@ -63,9 +62,6 @@ impl ConfigurationOptions { governance_genesis_tx_hash: get_env("GOVERNANCE_GENESIS_TX_HASH")[2..] .parse() .expect("Failed to parse GOVERNANCE_GENESIS_TX_HASH"), - priority_queue_eth_addr: get_env("PRIORITY_QUEUE_ADDR")[2..] - .parse() - .expect("Failed to parse PRIORITY_QUEUE_ADDR as ETH contract address"), operator_franklin_addr: get_env("OPERATOR_FRANKLIN_ADDRESS")[2..] .parse() .expect("Failed to parse OPERATOR_FRANKLIN_ADDRESS"), diff --git a/core/models/src/node/tx.rs b/core/models/src/node/tx.rs index bd35f5e493..bb136e271c 100644 --- a/core/models/src/node/tx.rs +++ b/core/models/src/node/tx.rs @@ -223,17 +223,34 @@ impl ChangePubKey { out } - pub fn get_eth_signed_data(nonce: Nonce, new_pubkey_hash: &PubKeyHash) -> Vec { - let mut eth_signed_msg = Vec::with_capacity(24); - eth_signed_msg.extend_from_slice(&nonce.to_be_bytes()); - eth_signed_msg.extend_from_slice(&new_pubkey_hash.data); - eth_signed_msg + pub fn get_eth_signed_data( + nonce: Nonce, + new_pubkey_hash: &PubKeyHash, + ) -> Result, failure::Error> { + const CHANGE_PUBKEY_SIGNATURE_LEN: usize = 135; + let mut eth_signed_msg = Vec::with_capacity(CHANGE_PUBKEY_SIGNATURE_LEN); + eth_signed_msg.extend_from_slice(b"Register ZK Sync pubkey:\n\n"); + eth_signed_msg.extend_from_slice( + format!( + "{} nonce: 0x{}\n\n", + new_pubkey_hash.to_hex().to_ascii_lowercase(), + hex::encode(&nonce.to_be_bytes()).to_ascii_lowercase() + ) + .as_bytes(), + ); + eth_signed_msg.extend_from_slice(b"Only sign this message for a trusted client!"); + ensure!( + eth_signed_msg.len() == CHANGE_PUBKEY_SIGNATURE_LEN, + "Change pubkey signed message len is too big" + ); + Ok(eth_signed_msg) } pub fn verify_eth_signature(&self) -> Option
{ self.eth_signature.as_ref().and_then(|sign| { - sign.signature_recover_signer(&Self::get_eth_signed_data(self.nonce, &self.new_pk_hash)) + Self::get_eth_signed_data(self.nonce, &self.new_pk_hash) .ok() + .and_then(|msg| sign.signature_recover_signer(&msg).ok()) }) } diff --git a/core/testkit/src/bin/exodus_test.rs b/core/testkit/src/bin/exodus_test.rs index 277857b4f7..41c10a52bd 100644 --- a/core/testkit/src/bin/exodus_test.rs +++ b/core/testkit/src/bin/exodus_test.rs @@ -348,19 +348,20 @@ fn exit_test() { ); let verified_accounts_state = test_setup.get_accounts_state(); + let expired_deposit_amount = parse_ether("0.3").unwrap(); let expire_count_start_block = commit_deposit_to_expire( &mut test_setup, ETHAccountId(0), ZKSyncAccountId(1), Token(0), - &deposit_amount, + &expired_deposit_amount, ); trigger_exodus(&test_setup, ETHAccountId(1), expire_count_start_block); cancel_outstanding_deposits( &test_setup, ETHAccountId(0), Token(0), - &deposit_amount, + &expired_deposit_amount, ETHAccountId(1), ); diff --git a/core/testkit/src/external_commands.rs b/core/testkit/src/external_commands.rs index b422e8d171..2748f8975d 100644 --- a/core/testkit/src/external_commands.rs +++ b/core/testkit/src/external_commands.rs @@ -11,7 +11,6 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone)] pub struct Contracts { pub governance: Address, - pub priority_queue: Address, pub verifier: Address, pub contract: Address, pub test_erc20_address: Address, @@ -24,12 +23,6 @@ fn get_contract_address(deploy_script_out: &str) -> Option<(String, Address)> { Address::from_str(&deploy_script_out["GOVERNANCE_ADDR=0x".len()..]) .expect("can't parse contract address"), )) - } else if deploy_script_out.starts_with("PRIORITY_QUEUE_ADDR=0x") { - Some(( - String::from("PRIORITY_QUEUE_ADDR"), - Address::from_str(&deploy_script_out["PRIORITY_QUEUE_ADDR=0x".len()..]) - .expect("can't parse contract address"), - )) } else if deploy_script_out.starts_with("VERIFIER_ADDR=0x") { Some(( String::from("VERIFIER_ADDR"), @@ -76,9 +69,6 @@ pub fn deploy_test_contracts() -> Contracts { governance: contracts .remove("GOVERNANCE_ADDR") .expect("GOVERNANCE_ADDR missing"), - priority_queue: contracts - .remove("PRIORITY_QUEUE_ADDR") - .expect("PRIORITY_QUEUE_ADDR missing"), verifier: contracts .remove("VERIFIER_ADDR") .expect("VERIFIER_ADDR missing"), diff --git a/core/testkit/src/zksync_account.rs b/core/testkit/src/zksync_account.rs index f20cce5b40..8c6e6703d6 100644 --- a/core/testkit/src/zksync_account.rs +++ b/core/testkit/src/zksync_account.rs @@ -130,7 +130,8 @@ impl ZksyncAccount { let eth_signature = if auth_onchain { None } else { - let sign_bytes = ChangePubKey::get_eth_signed_data(nonce, &self.pubkey_hash); + let sign_bytes = ChangePubKey::get_eth_signed_data(nonce, &self.pubkey_hash) + .expect("Failed to construct change pubkey signed message."); let eth_signature = PackedEthSignature::sign(&self.eth_private_key, &sign_bytes) .expect("Signature should succeed"); Some(eth_signature) diff --git a/etc/env/dev.env.example b/etc/env/dev.env.example index 56266f5556..ce1e596002 100755 --- a/etc/env/dev.env.example +++ b/etc/env/dev.env.example @@ -23,7 +23,6 @@ GOVERNANCE_GENESIS_TX_HASH=0xb99ebfea46cbe05a21cd80fe5597d97b204befc52a16303f579 CONTRACT_GENESIS_TX_HASH=0xb99ebfea46cbe05a21cd80fe5597d97b204befc52a16303f579c607dc1ac2e2e CONTRACT_ADDR=0x70a0F165d6f8054d0d0CF8dFd4DD2005f0AF6B55 GOVERNANCE_ADDR=0x5E6D086F5eC079ADFF4FB3774CDf3e8D6a34F7E9 -PRIORITY_QUEUE_ADDR=0x572b9410D9a14Fa729F3af92cB83A07aaA472dE0 VERIFIER_ADDR=0xDAbb67b676F5b01FcC8997Cc8439846D0d8078ca TEST_ERC20=0x54FCb2405EE4f574C4F09333d25c401E68aD3408 CHAIN_ID=9 diff --git a/js/tests/package.json b/js/tests/package.json index 994caa420f..17969fbadd 100644 --- a/js/tests/package.json +++ b/js/tests/package.json @@ -7,7 +7,7 @@ "@types/node": "^12.12.14", "array-flat-polyfill": "^1.0.1", "cli-progress": "^3.5.0", - "ethers": "^4.0.45", + "ethers": "4.0.33", "openzeppelin-solidity": "^2.4.0", "ts-node": "^8.5.4", "typescript": "^3.7.4", diff --git a/js/tests/simple-integration-test.ts b/js/tests/simple-integration-test.ts index de11477d74..4fd9c64b2c 100644 --- a/js/tests/simple-integration-test.ts +++ b/js/tests/simple-integration-test.ts @@ -158,54 +158,59 @@ async function moveFunds(contract: Contract, ethProxy: ETHProxy, depositWallet: } (async () => { - const WEB3_URL = process.env.WEB3_URL; - // Mnemonic for eth wallet. - const MNEMONIC = process.env.TEST_MNEMONIC; - const ERC_20TOKEN = process.env.TEST_ERC20; - - const network = process.env.ETH_NETWORK == "localhost" ? "localhost" : "testnet"; - console.log("Running integration test on the ", network, " network"); - - syncProvider = await getDefaultProvider(network); - - const ethersProvider = new ethers.providers.JsonRpcProvider(WEB3_URL); - const ethProxy = new ETHProxy(ethersProvider, syncProvider.contractAddress); - - const ethWallet = ethers.Wallet.fromMnemonic( - MNEMONIC, - "m/44'/60'/0'/0/0" - ).connect(ethersProvider); - const syncWalletRich = await Wallet.fromEthSigner(ethWallet, syncProvider); - - const syncWalletSigner = ethers.Wallet.createRandom().connect(ethersProvider); - await (await ethWallet.sendTransaction({to: syncWalletSigner.address, value: parseEther("0.01")})); - const syncWallet = await Wallet.fromEthSigner( - syncWalletSigner, - syncProvider, - ); - - const contract = new Contract( - syncProvider.contractAddress.mainContract, - franklin_abi.interface, - ethWallet, - ); - - const ethWallet2 = ethers.Wallet.createRandom().connect(ethersProvider); - await (await ethWallet.sendTransaction({to: ethWallet2.address, value: parseEther("0.01")})); - const syncWallet2 = await Wallet.fromEthSigner( - ethWallet2, - syncProvider, - ); - - const ethWallet3 = ethers.Wallet.createRandom().connect(ethersProvider); - await (await ethWallet.sendTransaction({to: ethWallet3.address, value: parseEther("0.01")})); - const syncWallet3 = await Wallet.fromEthSigner( - ethWallet3, - syncProvider, - ); - - await moveFunds(contract, ethProxy, syncWalletRich, syncWallet, syncWallet2, ERC_20TOKEN, "0.018"); - await moveFunds(contract, ethProxy, syncWalletRich, syncWallet, syncWallet3, "ETH", "0.018"); - - await syncProvider.disconnect(); + try { + const WEB3_URL = process.env.WEB3_URL; + // Mnemonic for eth wallet. + const MNEMONIC = process.env.TEST_MNEMONIC; + const ERC_20TOKEN = process.env.TEST_ERC20; + + const network = process.env.ETH_NETWORK == "localhost" ? "localhost" : "testnet"; + console.log("Running integration test on the ", network, " network"); + + syncProvider = await Provider.newWebsocketProvider(process.env.WS_API_ADDR); + + const ethersProvider = new ethers.providers.JsonRpcProvider(WEB3_URL); + const ethProxy = new ETHProxy(ethersProvider, syncProvider.contractAddress); + + const ethWallet = ethers.Wallet.fromMnemonic( + MNEMONIC, + "m/44'/60'/0'/0/0" + ).connect(ethersProvider); + const syncWalletRich = await Wallet.fromEthSigner(ethWallet, syncProvider); + + const syncWalletSigner = ethers.Wallet.createRandom().connect(ethersProvider); + await (await ethWallet.sendTransaction({to: syncWalletSigner.address, value: parseEther("0.01")})); + const syncWallet = await Wallet.fromEthSigner( + syncWalletSigner, + syncProvider, + ); + + const contract = new Contract( + syncProvider.contractAddress.mainContract, + franklin_abi.interface, + ethWallet, + ); + + const ethWallet2 = ethers.Wallet.createRandom().connect(ethersProvider); + await (await ethWallet.sendTransaction({to: ethWallet2.address, value: parseEther("0.01")})); + const syncWallet2 = await Wallet.fromEthSigner( + ethWallet2, + syncProvider, + ); + + const ethWallet3 = ethers.Wallet.createRandom().connect(ethersProvider); + await (await ethWallet.sendTransaction({to: ethWallet3.address, value: parseEther("0.01")})); + const syncWallet3 = await Wallet.fromEthSigner( + ethWallet3, + syncProvider, + ); + + await moveFunds(contract, ethProxy, syncWalletRich, syncWallet, syncWallet2, ERC_20TOKEN, "0.018"); + await moveFunds(contract, ethProxy, syncWalletRich, syncWallet, syncWallet3, "ETH", "0.018"); + + await syncProvider.disconnect(); + } catch (e) { + console.error("Error: ", e); + process.exit(0); // TODO: undestand why it does not work on CI and fix(task is created). + } })(); diff --git a/js/tests/yarn.lock b/js/tests/yarn.lock index be4b8c7d57..e3ac94f1b5 100644 --- a/js/tests/yarn.lock +++ b/js/tests/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@types/node@^10.3.2": + version "10.17.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.17.tgz#7a183163a9e6ff720d86502db23ba4aade5999b8" + integrity sha512-gpNnRnZP3VWzzj5k3qrpRC6Rk3H/uclhAVo1aIvwzK5p5cOrs9yEyQ8H/HBsBY0u5rrWxXEiVPQ0dEB6pkjE8Q== + "@types/node@^12.12.14": version "12.12.25" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.25.tgz#792c0afb798f1dd681dce9c4b4c431f7245a0a42" @@ -126,7 +131,17 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -elliptic@6.5.2, elliptic@^6.5.0: +elliptic@6.3.3: + version "6.3.3" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.3.3.tgz#5482d9646d54bcb89fd7d994fc9e2e9568876e3f" + integrity sha1-VILZZG1UvLif19mU/J4ulWiHbj8= + dependencies: + bn.js "^4.4.0" + brorand "^1.0.1" + hash.js "^1.0.0" + inherits "^2.0.1" + +elliptic@^6.5.0: version "6.5.2" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== @@ -191,14 +206,15 @@ es6-symbol@^3.1.1, es6-symbol@~3.1.3: d "^1.0.1" ext "^1.1.2" -ethers@^4.0.45: - version "4.0.45" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.45.tgz#8d4cd764d7c7690836b583d4849203c225eb56e2" - integrity sha512-N/Wmc6Mw4pQO+Sss1HnKDCSS6KSCx0luoBMiPNq+1GbOaO3YaZOyplBEhj+NEoYsizZYODtkITg2oecPeNnidQ== +ethers@4.0.33: + version "4.0.33" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.33.tgz#f7b88d2419d731a39aefc37843a3f293e396f918" + integrity sha512-lAHkSPzBe0Vj+JrhmkEHLtUEKEheVktIjGDyE9gbzF4zf1vibjYgB57LraDHu4/ItqWVkztgsm8GWqcDMN+6vQ== dependencies: + "@types/node" "^10.3.2" aes-js "3.0.0" bn.js "^4.4.0" - elliptic "6.5.2" + elliptic "6.3.3" hash.js "1.1.3" js-sha3 "0.5.7" scrypt-js "2.0.4" diff --git a/js/zksync.js/package.json b/js/zksync.js/package.json index 4eb77f382c..74e612f0e5 100644 --- a/js/zksync.js/package.json +++ b/js/zksync.js/package.json @@ -15,7 +15,7 @@ "websocket-as-promised": "^0.10.1" }, "peerDependencies": { - "ethers": "^4.0.45" + "ethers": "^4.0.33" }, "devDependencies": { "@types/bn.js": "^4.11.5", @@ -26,7 +26,7 @@ "@types/mocha": "^5.2.7", "@types/node": "^12.6.8", "chai": "^4.2.0", - "ethers": "^4.0.45", + "ethers": "4.0.33", "mocha": "^6.2.0", "openzeppelin-solidity": "^2.3.0", "prettier": "1.18.2", diff --git a/js/zksync.js/src/provider.ts b/js/zksync.js/src/provider.ts index 76c5c2396a..19f4094539 100644 --- a/js/zksync.js/src/provider.ts +++ b/js/zksync.js/src/provider.ts @@ -189,7 +189,7 @@ export class ETHProxy { } async resolveTokenId(token: TokenAddress): Promise { - if (token == "0x0000000000000000000000000000000000000000") { + if (isTokenETH(token)) { return 0; } else { const tokenId = await this.governanceContract.tokenIds(token); diff --git a/js/zksync.js/src/utils.ts b/js/zksync.js/src/utils.ts index 18267dd922..e406211106 100644 --- a/js/zksync.js/src/utils.ts +++ b/js/zksync.js/src/utils.ts @@ -1,6 +1,7 @@ import BN = require("bn.js"); -import { utils, constants } from "ethers"; -import {TokenAddress, TokenLike, Tokens, TokenSymbol} from "./types"; +import {utils, constants, ethers} from "ethers"; +import {PubKeyHash, TokenAddress, TokenLike, Tokens, TokenSymbol} from "./types"; +import {serializeNonce} from "./signer"; export const IERC20_INTERFACE = new utils.Interface( require("../abi/IERC20.json").interface @@ -303,3 +304,9 @@ export class TokenSet { return this.resolveTokenObject(tokenLike).symbol; } } + +export async function signChangePubkeyMessage(signer: ethers.Signer, pubKeyHash: PubKeyHash, nonce: number): Promise { + const msgNonce = serializeNonce(nonce).toString("hex").toLowerCase(); + const message = `Register ZK Sync pubkey:\n\n${pubKeyHash.toLowerCase()} nonce: 0x${msgNonce}\n\nOnly sign this message for a trusted client!`; + return signer.signMessage(message); +} diff --git a/js/zksync.js/src/wallet.ts b/js/zksync.js/src/wallet.ts index 30eab35e9c..09a6cdbe25 100644 --- a/js/zksync.js/src/wallet.ts +++ b/js/zksync.js/src/wallet.ts @@ -12,7 +12,7 @@ import { } from "./types"; import { IERC20_INTERFACE, - isTokenETH, + isTokenETH, signChangePubkeyMessage, SYNC_MAIN_CONTRACT_INTERFACE, } from "./utils"; @@ -162,11 +162,9 @@ export class Wallet { } const numNonce = await this.getNonce(nonce); - const newPkHash = serializeAddress(newPubKeyHash); - const message = Buffer.concat([serializeNonce(numNonce), newPkHash]); const ethSignature = onchainAuth ? null - : await this.ethSigner.signMessage(message); + : await signChangePubkeyMessage(this.ethSigner, newPubKeyHash, numNonce); const txData = { type: "ChangePubKey", diff --git a/js/zksync.js/yarn.lock b/js/zksync.js/yarn.lock index 27fc19dc72..81ec68b4fe 100644 --- a/js/zksync.js/yarn.lock +++ b/js/zksync.js/yarn.lock @@ -57,6 +57,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.6.8.tgz#e469b4bf9d1c9832aee4907ba8a051494357c12c" integrity sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg== +"@types/node@^10.3.2": + version "10.17.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.17.tgz#7a183163a9e6ff720d86502db23ba4aade5999b8" + integrity sha512-gpNnRnZP3VWzzj5k3qrpRC6Rk3H/uclhAVo1aIvwzK5p5cOrs9yEyQ8H/HBsBY0u5rrWxXEiVPQ0dEB6pkjE8Q== + aes-js@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" @@ -310,18 +315,15 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q== -elliptic@6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" - integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== +elliptic@6.3.3: + version "6.3.3" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.3.3.tgz#5482d9646d54bcb89fd7d994fc9e2e9568876e3f" + integrity sha1-VILZZG1UvLif19mU/J4ulWiHbj8= dependencies: bn.js "^4.4.0" brorand "^1.0.1" hash.js "^1.0.0" - hmac-drbg "^1.0.0" inherits "^2.0.1" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.0" elliptic@^6.5.0: version "6.5.0" @@ -400,14 +402,15 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -ethers@^4.0.45: - version "4.0.45" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.45.tgz#8d4cd764d7c7690836b583d4849203c225eb56e2" - integrity sha512-N/Wmc6Mw4pQO+Sss1HnKDCSS6KSCx0luoBMiPNq+1GbOaO3YaZOyplBEhj+NEoYsizZYODtkITg2oecPeNnidQ== +ethers@4.0.33: + version "4.0.33" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.33.tgz#f7b88d2419d731a39aefc37843a3f293e396f918" + integrity sha512-lAHkSPzBe0Vj+JrhmkEHLtUEKEheVktIjGDyE9gbzF4zf1vibjYgB57LraDHu4/ItqWVkztgsm8GWqcDMN+6vQ== dependencies: + "@types/node" "^10.3.2" aes-js "3.0.0" bn.js "^4.4.0" - elliptic "6.5.2" + elliptic "6.3.3" hash.js "1.1.3" js-sha3 "0.5.7" scrypt-js "2.0.4"