Skip to content

Commit

Permalink
Latest updates upload
Browse files Browse the repository at this point in the history
  • Loading branch information
nikitaeverywhere committed Apr 23, 2018
1 parent 312fb81 commit a7bb0fe
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 41 deletions.
2 changes: 1 addition & 1 deletion contracts/storage/StorageInterface.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma solidity ^0.4.21;
pragma solidity ^0.4.23;

interface StorageInterface {

Expand Down
4 changes: 2 additions & 2 deletions contracts/teams/TeamContracts.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma solidity ^0.4.21;
pragma solidity ^0.4.23;

import "../storage/TeamsStorageController.sol";
import "../storage/StorageInterface.sol";
Expand All @@ -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;
Expand Down
31 changes: 14 additions & 17 deletions contracts/token/DTT.sol
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {
Expand All @@ -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(
Expand All @@ -38,19 +38,17 @@ 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",
"address Withdraw Recipient Address",
"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",
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion contracts/token/ERC20TokenInterface.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma solidity ^0.4.21;
pragma solidity ^0.4.23;

contract ERC20TokenInterface {

Expand Down
24 changes: 13 additions & 11 deletions test/teams/TeamContracts.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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
Expand Down
127 changes: 118 additions & 9 deletions test/token/DTT.js
Original file line number Diff line number Diff line change
@@ -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%
Expand Down Expand Up @@ -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 (~$${
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
Expand Down

0 comments on commit a7bb0fe

Please sign in to comment.