From a7bb0fe15e93dbe7d8a8bf89778f44f2c025afff Mon Sep 17 00:00:00 2001 From: ZitRo Date: Mon, 23 Apr 2018 12:22:11 +0300 Subject: [PATCH] Latest updates upload --- contracts/storage/StorageInterface.sol | 2 +- contracts/teams/TeamContracts.sol | 4 +- contracts/token/DTT.sol | 31 +++--- contracts/token/ERC20TokenInterface.sol | 2 +- test/teams/TeamContracts.js | 24 +++-- test/token/DTT.js | 127 ++++++++++++++++++++++-- 6 files changed, 149 insertions(+), 41 deletions(-) diff --git a/contracts/storage/StorageInterface.sol b/contracts/storage/StorageInterface.sol index bf67e4f..c3a9e4d 100644 --- a/contracts/storage/StorageInterface.sol +++ b/contracts/storage/StorageInterface.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.21; +pragma solidity ^0.4.23; interface StorageInterface { diff --git a/contracts/teams/TeamContracts.sol b/contracts/teams/TeamContracts.sol index 043b1d1..9924a6c 100644 --- a/contracts/teams/TeamContracts.sol +++ b/contracts/teams/TeamContracts.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.21; +pragma solidity ^0.4.23; import "../storage/TeamsStorageController.sol"; import "../storage/StorageInterface.sol"; @@ -23,7 +23,7 @@ contract TeamContracts is TeamsStorageController { /** * Constructor. This is yet the only way to set owner address, token address and storage address. */ - function TeamContracts (address dt, address token, address dbAddress) public { + constructor (address dt, address token, address dbAddress) public { dreamTeamAddress = dt; erc20TokenAddress = token; db = dbAddress; diff --git a/contracts/token/DTT.sol b/contracts/token/DTT.sol index 0bc4319..1d1cfd3 100644 --- a/contracts/token/DTT.sol +++ b/contracts/token/DTT.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.21; +pragma solidity ^0.4.23; interface tokenRecipient { function receiveApproval (address from, uint256 value, address token, bytes extraData) external; @@ -10,7 +10,7 @@ interface tokenRecipient { * 2. Additional utility function approveAndCall. [OK] * 3. Function to rescue "lost forever" tokens, which were accidentally sent to the contract address. [OK] * 4. Additional transfer and approve functions which allow to distinct the transaction signer and executor, - * which enables accounts with no Ether on their balances to make token transfers and use DreamTeam services. [TEST] + * which enables accounts with no Ether on their balances to make token transfers and use DreamTeam services. [ALPHA] * 5. Token sale distribution rules. [OK] */ contract DTT { @@ -28,7 +28,7 @@ contract DTT { event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); - bytes public ethSignedMessagePrefix = "\x19Ethereum Signed Message:\n32"; + bytes public ethSignedMessagePrefix = "\x19Ethereum Signed Message:\n"; enum sigStandard { typed, personal, stringHex } enum sigDestination { transfer, approve, approveAndCall } bytes32 public sigDestinationTransfer = keccak256( @@ -38,9 +38,8 @@ contract DTT { "uint256 Amount to Transfer (last six digits are decimals)", "uint256 Fee in Tokens Paid to Executor (last six digits are decimals)", "uint256 Signature Expiration Timestamp (unix timestamp)", - "uint256 Signature ID", - "uint8 Signature Standard" - ); // `transferViaSignature`: keccak256(address(this), from, to, value, fee, deadline, sigId, sigStandard) + "uint256 Signature ID" + ); // `transferViaSignature`: keccak256(address(this), from, to, value, fee, deadline, sigId) bytes32 public sigDestinationApprove = keccak256( "address Token Contract Address", "address Withdraw Approval Address", @@ -48,9 +47,8 @@ contract DTT { "uint256 Amount to Transfer (last six digits are decimals)", "uint256 Fee in Tokens Paid to Executor (last six digits are decimals)", "uint256 Signature Expiration Timestamp (unix timestamp)", - "uint256 Signature ID", - "uint8 Signature Standard" - ); // `approveViaSignature`: keccak256(address(this), from, spender, value, fee, deadline, sigId, sigStandard) + "uint256 Signature ID" + ); // `approveViaSignature`: keccak256(address(this), from, spender, value, fee, deadline, sigId) bytes32 public sigDestinationApproveAndCall = keccak256( // `approveAndCallViaSignature` "address Token Contract Address", "address Withdraw Approval Address", @@ -59,11 +57,10 @@ contract DTT { "bytes Data to Transfer", "uint256 Fee in Tokens Paid to Executor (last six digits are decimals)", "uint256 Signature Expiration Timestamp (unix timestamp)", - "uint256 Signature ID", - "uint8 Signature Standard" - ); // `approveAndCallViaSignature`: keccak256(address(this), from, spender, value, extraData, fee, deadline, sigId, sigStandard) + "uint256 Signature ID" + ); // `approveAndCallViaSignature`: keccak256(address(this), from, spender, value, extraData, fee, deadline, sigId) - function DTT (string tokenName, string tokenSymbol) public { // todo: remove initial supply + constructor (string tokenName, string tokenSymbol) public { name = tokenName; symbol = tokenSymbol; rescueAccount = tokenDistributor = msg.sender; @@ -141,20 +138,20 @@ contract DTT { ) ); } else if (std == sigStandard.personal) { // Ethereum signed message signature - require(from == ecrecover(keccak256(ethSignedMessagePrefix, data), v, r, s)); + require(from == ecrecover(keccak256(ethSignedMessagePrefix, "32", data), v, r, s)); } else { // == 2; Signed string hash signature (the most expensive but universal) - require(from == ecrecover(keccak256(ethSignedMessagePrefix, hexToString(data)), v, r, s)); + require(from == ecrecover(keccak256(ethSignedMessagePrefix, "64", hexToString(data)), v, r, s)); } usedSigIds[from][sigId] = true; } - function hexToString (bytes32 sig) internal pure returns (string) { // TODO: convert to two uint256 and test gas + function hexToString (bytes32 sig) internal pure returns (bytes) { // /to-try/ convert to two uint256 and test gas bytes memory str = new bytes(64); for (uint8 i = 0; i < 32; ++i) { str[2 * i] = byte((uint8(sig[i]) / 16 < 10 ? 48 : 87) + uint8(sig[i]) / 16); str[2 * i + 1] = byte((uint8(sig[i]) % 16 < 10 ? 48 : 87) + (uint8(sig[i]) % 16)); } - return string(str); + return str; } /** diff --git a/contracts/token/ERC20TokenInterface.sol b/contracts/token/ERC20TokenInterface.sol index fe17379..3a7d0cf 100644 --- a/contracts/token/ERC20TokenInterface.sol +++ b/contracts/token/ERC20TokenInterface.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.4.21; +pragma solidity ^0.4.23; contract ERC20TokenInterface { diff --git a/test/teams/TeamContracts.js b/test/teams/TeamContracts.js index 63172db..cf2bad3 100644 --- a/test/teams/TeamContracts.js +++ b/test/teams/TeamContracts.js @@ -81,10 +81,6 @@ contract("TeamContracts", (accounts) => { member: [] }]; - console.log( - `Estimations used for this test: ETH/USD=${ ethToUsdRate }, gasPrice=${ gasPrice / Math.pow(10, 9) } GWei` - ); - /** * Returns team information in human-friendly format. You can re-use this function whereever you like. * @param {number} teamId @@ -160,9 +156,13 @@ contract("TeamContracts", (accounts) => { }} before(async function () { + console.log( + `Estimations used for this test: ETH/USD=${ ethToUsdRate }, gasPrice=${ gasPrice / Math.pow(10, 9) } GWei` + ); storage = await Storage.deployed(); token = await Token.deployed(); teamContracts = await TeamContracts.deployed(); + console.log(`Deployed team contracts: ${ teamContracts.address }`); }); describe("Token checkup", () => { @@ -271,13 +271,15 @@ contract("TeamContracts", (accounts) => { }); } - try { // Phase 1: allow smart contract to withdrawal teamInitialTokenReward tokens from DreamTeam account - const tx = await token.approve(teamContracts.address, amount, { - from: fromAccount - }); - console.log(` ⓘ token.approve: ${ getUsedGas(tx) }`); - } catch (e) { - assert.fail("Cannot approve in ERC20 token; " + e); + if (fromAccount !== accounts[DreamTeam]) { // PRE-APPROVAL must be performed for DreamTeam account + try { // Phase 1: allow smart contract to withdrawal teamInitialTokenReward tokens from DreamTeam account + const tx = await token.approve(teamContracts.address, amount, { + from: fromAccount + }); + console.log(` ⓘ token.approve: ${ getUsedGas(tx) }`); + } catch (e) { + assert.fail("Cannot approve in ERC20 token; " + e); + } } try { // Phase 2: tell the team ID and amount to transfer to that team diff --git a/test/token/DTT.js b/test/token/DTT.js index acbe97b..ad7a10d 100644 --- a/test/token/DTT.js +++ b/test/token/DTT.js @@ -1,10 +1,12 @@ const DTT = artifacts.require("DTT"); const Web3 = require("web3"); // Latest web3 version web3m = new Web3(web3.currentProvider); +const sigUtils = require("eth-sig-util"); +const Buffer = require("safe-buffer").Buffer; /** * Generate random token sale distribution table. - * @param {number} tokenDecimals + * @param {number} tokenDecimals */ function getTokenSaleDistributionTable (tokenDecimals = 18) { const approximateTokens = 40000000 /* USD */ / 300 /* ETH/USD */ * 1800 /* Tokens per 1 ETH */; // +- 100% @@ -59,7 +61,7 @@ const forwardTime = async function (minutesToForward) { return new Promise((reso }) } const gasPrice = 2 * Math.pow(10, 9); // 2 GWei -const ethToUsdRate = 400; +const ethToUsdRate = 700; const blockGasLimit = 4600000 - 600000; // Give 600000 spare gas per block const expectedTotalSupply = 250000000; const getUsedGas = (tx) => `${ tx.receipt.gasUsed } gas (~$${ @@ -300,7 +302,7 @@ contract("DTT", (accounts) => { describe("Token approve and transferFrom", () => { - it("Token transfer case 1", async function () { + it("Token transfer case 1, single transfer", async function () { const value = 10 * 10 ** decimals; const from = dreamTeamAccount; @@ -343,7 +345,7 @@ contract("DTT", (accounts) => { }); - it("Token transfer case 2", async function () { + it("Token transfer case 2, multiple transfers", async function () { const value = 10 * 10 ** decimals; const from = dreamTeamAccount; @@ -385,7 +387,7 @@ contract("DTT", (accounts) => { let value, from, to, delegate, fee, deadline, signature, usedSigId; - it("Allows pre-signed transfer", async function () { + it("Allows pre-signed transfer using personal sign standard", async function () { value = 10 * 10 ** decimals; from = account1; @@ -408,12 +410,119 @@ contract("DTT", (accounts) => { }); - it("Does not allow to re-use signatures", async function () { + it("Does not allow to re-use signature", async function () { try { - await token.transferViaSignature(from, to, value, fee, deadline, usedSigId, signature, { - from: delegate - }); + await token.transferViaSignature( + from, to, value, fee, deadline, usedSigId, signature, SIG_STANDARD_PERSONAL, { from: delegate } + ); + } catch (e) { + return assert.ok(true); + } + assert.fail(`Allows signature to be re-used (replay attack)`); + + }); + + it("Allows pre-signed transfer using personal hex string sign standard", async function () { + + value = 4 * 10 ** decimals; + from = account1; + to = strangerAccount; + delegate = dreamTeamAccount; + fee = 2 * 10 ** decimals - 1; + deadline = (await web3m.eth.getBlock(`latest`)).timestamp + 60 * 60 * 24 * 7; // +7 days + const dataToSign = web3m.utils.soliditySha3(token.address, from, to, value, fee, deadline, usedSigId = sigId++); + const balanceFrom = +(await token.balanceOf.call(from)); + const balanceTo = +(await token.balanceOf.call(to)); + const balanceDelegate = +(await token.balanceOf.call(delegate)); + signature = await web3m.eth.sign(dataToSign.slice(2), account1); + const tx = await token.transferViaSignature( + from, to, value, fee, deadline, usedSigId, signature, SIG_STANDARD_HEX_STRING, { from: delegate } + ); + infoLog(`TX (transferViaSignature) gas usage: ${ getUsedGas(tx) }`); + assert.equal(+(await token.balanceOf(from)), balanceFrom - value - fee, "Must subtract balance"); + assert.equal(+(await token.balanceOf(to)), balanceTo + value, "Must add balance to recipient"); + assert.equal(+(await token.balanceOf(delegate)), balanceDelegate + fee, "Must pay fee to delegate"); + + }); + + it("Does not allow to re-use signature", async function () { + + try { + await token.transferViaSignature( + from, to, value, fee, deadline, usedSigId, signature, SIG_STANDARD_HEX_STRING, { from: delegate } + ); + } catch (e) { + return assert.ok(true); + } + assert.fail(`Allows signature to be re-used (replay attack)`); + + }); + + it("Allows pre-signed transfer using sign typed data standard", async function () { + + const newAccount = web3m.eth.accounts.create(); + value = 4 * 10 ** decimals; + from = newAccount.address; + to = strangerAccount; + delegate = dreamTeamAccount; + fee = 2 * 10 ** decimals - 1; + deadline = (await web3m.eth.getBlock(`latest`)).timestamp + 60 * 60 * 24 * 7; // +7 days + usedSigId = sigId++; + const dataToSign = [{ + type: 'address', + name: 'Token Contract Address', + value: token.address + }, { + type: 'address', + name: 'Sender\'s Address', + value: from + }, { + type: 'address', + name: 'Recipient\'s Address', + value: to + }, { + type: 'uint256', + name: 'Amount to Transfer (last six digits are decimals)', + value: value + }, { + type: 'uint256', + name: 'Fee in Tokens Paid to Executor (last six digits are decimals)', + value: fee + }, { + type: 'uint256', + name: 'Signature Expiration Timestamp (unix timestamp)', + value: deadline + }, { + type: 'uint256', + name: 'Signature ID', + value: usedSigId + }]; + await token.transfer(from, value + fee, { from: dreamTeamAccount }); + const balanceFrom = +(await token.balanceOf.call(from)); + assert.equal(balanceFrom, value + fee, "Account balance must be refilled"); + const balanceTo = +(await token.balanceOf.call(to)); + const balanceDelegate = +(await token.balanceOf.call(delegate)); + signature = sigUtils.signTypedData( + Buffer.from(newAccount.privateKey.slice(2), 'hex'), + { data: dataToSign } + ); + const tx = await token.transferViaSignature( + from, to, value, fee, deadline, usedSigId, signature, SIG_STANDARD_TYPED, { from: delegate } + ); + infoLog(`TX (transferViaSignature) gas usage: ${ getUsedGas(tx) }`); + assert.equal(+(await token.balanceOf(from)), balanceFrom - value - fee, "Must subtract balance"); + assert.equal(+(await token.balanceOf(to)), balanceTo + value, "Must add balance to recipient"); + assert.equal(+(await token.balanceOf(delegate)), balanceDelegate + fee, "Must pay fee to delegate"); + + }); + + it("Does not allow to re-use signature", async function () { + + try { + await token.transferViaSignature( + from, to, value, fee, deadline, usedSigId, signature, SIG_STANDARD_TYPED, { from: delegate } + ); } catch (e) { return assert.ok(true); }