commit
901af71b0c
|
@ -37,7 +37,7 @@ Other options are
|
|||
|
||||
`--passfile`: the path to a file storing the password for the JSON encoded private key. Always used with --account.
|
||||
|
||||
`--maxTxValue`: the maxTxValue for payment transaction. This can be changed later but providing a meaningful value on creation can be convenient. Defaults to 100000000.
|
||||
`--tokenMaxTxValue`: the tokenMaxTxValue for payment transaction. This can be changed later but providing a meaningful value on creation can be convenient. Defaults to 100000000.
|
||||
|
||||
`--minBlockDistance`: how many blocks must elapse between two consecutive payments. This solves the possible attack of having the Keycard sign several transactions at once. The higher the value, the more time must pass between transactions. This can be changed later too. Defaults to 5.
|
||||
|
||||
|
|
|
@ -4,9 +4,8 @@ import Web3 from 'web3';
|
|||
import parseArgs from 'minimist';
|
||||
import fs from 'fs';
|
||||
|
||||
const factoryABI = [{"constant":false,"inputs":[{"name":"keycard","type":"address"},{"components":[{"name":"maxTxValue","type":"uint256"},{"name":"minBlockDistance","type":"uint256"}],"name":"settings","type":"tuple"},{"name":"keycardIsOwner","type":"bool"},{"name":"optToken","type":"address"},{"name":"optTokenMaxTxAmount","type":"uint256"}],"name":"create","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x03fbbd28"},{"constant":false,"inputs":[{"name":"_wallet","type":"address"},{"name":"_keycard","type":"address"}],"name":"unregisterFromOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x25a6e9a1"},{"constant":false,"inputs":[{"name":"_oldOwner","type":"address"},{"name":"_newOwner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x299a7bcc"},{"constant":false,"inputs":[{"name":"_oldKeycard","type":"address"},{"name":"_newKeycard","type":"address"}],"name":"setKeycard","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x4349c8bc"},{"constant":false,"inputs":[{"name":"_owner","type":"address"},{"name":"_keycard","type":"address"}],"name":"unregister","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0x4a45b60b"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"countWalletsForOwner","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function","signature":"0x624de70e"},{"constant":false,"inputs":[{"name":"_owner","type":"address"},{"name":"_keycard","type":"address"}],"name":"register","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function","signature":"0xaa677354"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"keycardsWallets","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xcf7661b9"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"uint256"}],"name":"ownersWallets","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function","signature":"0xde59dda1"},{"anonymous":false,"inputs":[{"indexed":false,"name":"wallet","type":"address"}],"name":"NewWallet","type":"event","signature":"0xd627a1aeb13261b560c345aaf7d003d55a27193b9284c0b941f53cd62a045f16"}];
|
||||
|
||||
const argv = parseArgs(process.argv.slice(2), {string: ["registry", "keycard", "token"], default: {"endpoint": "ws://127.0.0.1:8546", "maxTxValue": 10000000000000, "minBlockDistance": 5, "token": "0x0000000000000000000000000000000000000000", "tokenMaxTxValue": 10000000000000}});
|
||||
const factoryABI = [{"constant":false,"inputs":[{"name":"_wallet","type":"address"},{"name":"_keycard","type":"address"}],"name":"unregisterFromOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_oldOwner","type":"address"},{"name":"_newOwner","type":"address"}],"name":"setOwner","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_oldKeycard","type":"address"},{"name":"_newKeycard","type":"address"}],"name":"setKeycard","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"},{"name":"_keycard","type":"address"}],"name":"unregister","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"countWalletsForOwner","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_keycard","type":"address"},{"name":"_keycardIsOwner","type":"bool"},{"name":"_minBlockDistance","type":"uint256"},{"name":"_txMaxAmount","type":"uint256"}],"name":"create","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_owner","type":"address"},{"name":"_keycard","type":"address"}],"name":"register","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"keycardsWallets","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"uint256"}],"name":"ownersWallets","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"currency","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_currency","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"wallet","type":"address"}],"name":"NewWallet","type":"event"}];
|
||||
const argv = parseArgs(process.argv.slice(2), {boolean: ["deploy-registry"], string: ["registry", "keycard", "token"], default: {"endpoint": "ws://127.0.0.1:8546", "minBlockDistance": 5, "tokenMaxTxValue": 10000000000000}});
|
||||
|
||||
const web3 = new Web3(argv["endpoint"]);
|
||||
const KeycardWalletFactory = new web3.eth.Contract(factoryABI, argv["registry"]);
|
||||
|
@ -23,22 +22,27 @@ function loadAccount(account, passfile) {
|
|||
return web3.eth.accounts.decrypt(json, pass);
|
||||
}
|
||||
|
||||
async function createWallet(sender, keycard, maxTxValue, minBlockDistance, token, tokenMaxTxValue) {
|
||||
let methodCall = KeycardWalletFactory.methods.create(keycard.toLowerCase(), {maxTxValue: maxTxValue, minBlockDistance: minBlockDistance}, true, token, tokenMaxTxValue);
|
||||
async function sendMethod(methodCall, sender, to) {
|
||||
let receipt;
|
||||
|
||||
if (typeof(sender) == "string") {
|
||||
let gasAmount = await methodCall.estimateGas({from: sender});
|
||||
receipt = await methodCall.send({from: sender, gas: gasAmount});
|
||||
} else {
|
||||
let gasAmount = await methodCall.estimateGas({from: sender.address});
|
||||
let data = methodCall.encodeABI();
|
||||
let signedTx = await sender.signTransaction({to: to, data: data, gas: gasAmount});
|
||||
receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
|
||||
}
|
||||
|
||||
return receipt;
|
||||
}
|
||||
|
||||
async function createWallet(sender, keycard, minBlockDistance, tokenMaxTxValue) {
|
||||
let methodCall = KeycardWalletFactory.methods.create(keycard.toLowerCase(), true, minBlockDistance, tokenMaxTxValue);
|
||||
|
||||
try {
|
||||
let receipt;
|
||||
|
||||
if (typeof(sender) == "string") {
|
||||
let gasAmount = await methodCall.estimateGas({from: sender});
|
||||
receipt = await methodCall.send({from: sender, gas: gasAmount});
|
||||
} else {
|
||||
let gasAmount = await methodCall.estimateGas({from: sender.address});
|
||||
let data = methodCall.encodeABI();
|
||||
let signedTx = await sender.signTransaction({to: KeycardWalletFactory.options.address, data: data, gas: gasAmount});
|
||||
receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
|
||||
}
|
||||
|
||||
let receipt = await sendMethod(methodCall, sender, KeycardWalletFactory.options.address);
|
||||
const event = receipt.events.NewWallet;
|
||||
return event.returnValues.wallet;
|
||||
} catch(err) {
|
||||
|
@ -47,26 +51,15 @@ async function createWallet(sender, keycard, maxTxValue, minBlockDistance, token
|
|||
}
|
||||
}
|
||||
|
||||
async function deployRegistry(sender, code, token) {
|
||||
let methodCall = KeycardWalletFactory.deploy({data: "0x" + code, arguments: [token]});
|
||||
let receipt = await sendMethod(methodCall, sender, null);
|
||||
return receipt.contractAddress;
|
||||
}
|
||||
|
||||
async function run() {
|
||||
KeycardWalletFactory.transactionConfirmationBlocks = 3;
|
||||
|
||||
let keycards;
|
||||
|
||||
if (argv["file"]) {
|
||||
let file = fs.readFileSync(argv["file"], 'utf8');
|
||||
keycards = file.split("\n").map((addr) => addr.trim());
|
||||
} else if (argv["keycard"]) {
|
||||
keycards = [argv["keycard"]]
|
||||
} else {
|
||||
console.error("either the --file or the --keycard option must be specified");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!argv["registry"]) {
|
||||
console.error("the ---registry option must be specified");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let sender;
|
||||
|
||||
if (argv["account"]) {
|
||||
|
@ -84,19 +77,53 @@ async function run() {
|
|||
sender = argv["sender"] || await getDefaultSender();
|
||||
}
|
||||
|
||||
let walletAddresses = await Promise.all(keycards.map((keycard) => createWallet(sender, keycard, argv["maxTxValue"], argv["minBlockDistance"], argv["token"], argv["tokenMaxTxValue"])));
|
||||
let zippedAddresses = keycards.map((keycard, i) => [keycard, walletAddresses[i]]);
|
||||
if (argv["deploy-registry"]) {
|
||||
if (!argv["code"]) {
|
||||
console.error("the --code option must be specified");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let fid;
|
||||
if (argv["out"]) {
|
||||
fid = fs.openSync(argv["out"], "w", o0644);
|
||||
if (!argv["token"]) {
|
||||
console.error("the --token option must be specified");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let code = fs.readFileSync(argv["code"], 'utf8').trim();
|
||||
let registry = await deployRegistry(sender, code, argv["token"]);
|
||||
console.log("Registry deployed at: " + registry);
|
||||
} else {
|
||||
fid = STDOUT;
|
||||
let keycards;
|
||||
|
||||
if (argv["file"]) {
|
||||
let file = fs.readFileSync(argv["file"], 'utf8');
|
||||
keycards = file.split("\n").map((addr) => addr.trim());
|
||||
} else if (argv["keycard"]) {
|
||||
keycards = [argv["keycard"]]
|
||||
} else {
|
||||
console.error("either the --file or the --keycard option must be specified");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!argv["registry"]) {
|
||||
console.error("the --registry option must be specified");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let walletAddresses = await Promise.all(keycards.map((keycard) => createWallet(sender, keycard, argv["minBlockDistance"], argv["tokenMaxTxValue"])));
|
||||
let zippedAddresses = keycards.map((keycard, i) => [keycard, walletAddresses[i]]);
|
||||
|
||||
let fid;
|
||||
if (argv["out"]) {
|
||||
fid = fs.openSync(argv["out"], "w", o0644);
|
||||
} else {
|
||||
fid = STDOUT;
|
||||
}
|
||||
|
||||
zippedAddresses.forEach((tuple) => fs.writeSync(fid, `${tuple[0]},${tuple[1]}\n`));
|
||||
|
||||
fs.close(fid);
|
||||
}
|
||||
|
||||
zippedAddresses.forEach((tuple) => fs.writeSync(fid, `${tuple[0]},${tuple[1]}\n`));
|
||||
|
||||
fs.close(fid);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,9 +5,7 @@ import "./KeycardRegistry.sol";
|
|||
import "./IERC20.sol";
|
||||
|
||||
contract KeycardWallet {
|
||||
event TopUp(address from, uint256 value);
|
||||
event NewPaymentRequest(uint256 blockNumber, address to, address currency, uint256 amount);
|
||||
event NewWithdrawal(address to, uint256 value);
|
||||
event NewPayment(uint256 blockNumber, address to, address currency, uint256 amount);
|
||||
|
||||
//TODO: replace with chainid opcode
|
||||
// uint256 constant chainId = 1;
|
||||
|
@ -33,41 +31,24 @@ contract KeycardWallet {
|
|||
address public register;
|
||||
address public owner;
|
||||
address public keycard;
|
||||
Settings public settings;
|
||||
mapping(address => uint) public tokenMaxTxAmount;
|
||||
mapping(address => uint) public pendingWithdrawals;
|
||||
uint256 public totalPendingWithdrawals;
|
||||
uint256 public lastUsedBlockNum;
|
||||
|
||||
struct Settings {
|
||||
uint256 maxTxValue;
|
||||
uint256 minBlockDistance;
|
||||
}
|
||||
uint256 public minBlockDistance;
|
||||
|
||||
modifier onlyOwner() {
|
||||
require(msg.sender == owner, "owner required");
|
||||
_;
|
||||
}
|
||||
|
||||
// anyone can add funds to the wallet
|
||||
function () external payable {
|
||||
emit TopUp(msg.sender, msg.value);
|
||||
}
|
||||
|
||||
constructor(address _owner, address _keycard, Settings memory _settings, address _register,
|
||||
address _optToken, uint256 _optTokenMaxTxAmount) public {
|
||||
constructor(address _owner, address _keycard, address _register, uint256 _minBlockDistance, address _token, uint256 _tokenMaxTxAmount) public {
|
||||
owner = _owner == address(0) ? msg.sender : _owner;
|
||||
keycard = _keycard;
|
||||
register = address(0);
|
||||
|
||||
settings = _settings;
|
||||
minBlockDistance = _minBlockDistance;
|
||||
_setRegister(_register);
|
||||
totalPendingWithdrawals = 0;
|
||||
lastUsedBlockNum = block.number;
|
||||
|
||||
if (_optToken != address(0)) {
|
||||
setTokenMaxTXAmount(_optToken, _optTokenMaxTxAmount);
|
||||
}
|
||||
tokenMaxTxAmount[_token] = _tokenMaxTxAmount;
|
||||
}
|
||||
|
||||
function _setRegister(address _register) internal {
|
||||
|
@ -110,8 +91,8 @@ contract KeycardWallet {
|
|||
keycard = _keycard;
|
||||
}
|
||||
|
||||
function setSettings(Settings memory _settings) public onlyOwner {
|
||||
settings = _settings;
|
||||
function setMinBlockDistance(uint256 _minBlockDistance) public onlyOwner {
|
||||
minBlockDistance = _minBlockDistance;
|
||||
}
|
||||
|
||||
function setTokenMaxTXAmount(address _token, uint256 _maxTxAmount) public onlyOwner {
|
||||
|
@ -171,54 +152,22 @@ contract KeycardWallet {
|
|||
require(_payment.blockNumber >= (block.number - maxTxDelayInBlocks), "transaction too old");
|
||||
|
||||
// check that the block number is not too near to the last one in which a tx has been processed
|
||||
require(_payment.blockNumber >= (lastUsedBlockNum + settings.minBlockDistance), "cooldown period not expired yet");
|
||||
require(_payment.blockNumber >= (lastUsedBlockNum + minBlockDistance), "cooldown period not expired yet");
|
||||
|
||||
// check that the blockHash is valid
|
||||
require(_payment.blockHash == blockhash(_payment.blockNumber), "invalid block hash");
|
||||
|
||||
if (_payment.currency == address(0)) {
|
||||
// ETH transfer
|
||||
// check that _payment.amount is not greater than the maxTxValue for this currency
|
||||
require(_payment.amount <= tokenMaxTxAmount[_payment.currency], "amount not allowed");
|
||||
|
||||
// check that _payment.amount is not greater than settings.maxTxValue
|
||||
require(_payment.amount <= settings.maxTxValue, "amount not allowed");
|
||||
// check that balance is enough for this payment
|
||||
require(IERC20(_payment.currency).balanceOf(address(this)) >= _payment.amount, "balance is not enough");
|
||||
|
||||
int256 availableBalance = int256(address(this).balance - totalPendingWithdrawals - _payment.amount);
|
||||
// check that balance is enough for this payment
|
||||
require(availableBalance >= 0, "balance is not enough");
|
||||
|
||||
// add pendingWithdrawal
|
||||
totalPendingWithdrawals += _payment.amount;
|
||||
pendingWithdrawals[_payment.to] += _payment.amount;
|
||||
} else {
|
||||
//ERC20
|
||||
|
||||
// check that _payment.amount is not greater than settings.maxTxValue
|
||||
require(_payment.amount <= tokenMaxTxAmount[_payment.currency], "amount not allowed");
|
||||
|
||||
// check that balance is enough for this payment
|
||||
require(IERC20(_payment.currency).balanceOf(address(this)) >= _payment.amount, "balance is not enough");
|
||||
|
||||
// transfer token
|
||||
require(IERC20(_payment.currency).transfer(_payment.to, _payment.amount), "transfer failed");
|
||||
}
|
||||
// transfer token
|
||||
require(IERC20(_payment.currency).transfer(_payment.to, _payment.amount), "transfer failed");
|
||||
|
||||
// set new baseline block for checks
|
||||
lastUsedBlockNum = block.number;
|
||||
emit NewPaymentRequest(_payment.blockNumber, _payment.to, _payment.currency, _payment.amount);
|
||||
}
|
||||
|
||||
function availableBalance() public returns (int256) {
|
||||
return int256(address(this).balance - totalPendingWithdrawals);
|
||||
}
|
||||
|
||||
function withdraw() public {
|
||||
uint256 amount = pendingWithdrawals[msg.sender];
|
||||
require(amount > 0, "no pending withdrawal");
|
||||
|
||||
pendingWithdrawals[msg.sender] = 0;
|
||||
totalPendingWithdrawals -= amount;
|
||||
|
||||
msg.sender.transfer(amount);
|
||||
emit NewWithdrawal(msg.sender, amount);
|
||||
emit NewPayment(_payment.blockNumber, _payment.to, _payment.currency, _payment.amount);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,20 +7,22 @@ import "./KeycardRegistry.sol";
|
|||
contract KeycardWalletFactory is KeycardRegistry {
|
||||
mapping(address => address[]) public ownersWallets;
|
||||
mapping(address => address) public keycardsWallets;
|
||||
address public currency;
|
||||
|
||||
event NewWallet(
|
||||
KeycardWallet wallet
|
||||
);
|
||||
event NewWallet(KeycardWallet wallet);
|
||||
|
||||
function create(address keycard, KeycardWallet.Settings memory settings, bool keycardIsOwner,
|
||||
address optToken, uint256 optTokenMaxTxAmount) public {
|
||||
address owner = keycardIsOwner ? keycard : msg.sender;
|
||||
constructor(address _currency) public {
|
||||
currency = _currency;
|
||||
}
|
||||
|
||||
require(keycardsWallets[keycard] == address(0), "the keycard is already associated to a wallet");
|
||||
function create(address _keycard, bool _keycardIsOwner, uint256 _minBlockDistance, uint256 _txMaxAmount) public {
|
||||
address owner = _keycardIsOwner ? _keycard : msg.sender;
|
||||
|
||||
KeycardWallet wallet = new KeycardWallet(owner, keycard, settings, address(this), optToken, optTokenMaxTxAmount);
|
||||
require(keycardsWallets[_keycard] == address(0), "the keycard is already associated to a wallet");
|
||||
|
||||
KeycardWallet wallet = new KeycardWallet(owner, _keycard, address(this), _minBlockDistance, currency, _txMaxAmount);
|
||||
ownersWallets[owner].push(address(wallet));
|
||||
keycardsWallets[keycard] = address(wallet);
|
||||
keycardsWallets[_keycard] = address(wallet);
|
||||
emit NewWallet(wallet);
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ let owner, owner2;
|
|||
|
||||
config({
|
||||
contracts: {
|
||||
KeycardWalletFactory: {}
|
||||
KeycardWalletFactory: {args:["0x00000000000000000000000000000000000000ff"]}
|
||||
}
|
||||
}, (err, _accounts) => {
|
||||
owner = _accounts[0];
|
||||
|
@ -19,7 +19,7 @@ contract('KeycardWalletFactory', () => {
|
|||
it ('create', async () => {
|
||||
const keycard = "0x0000000000000000000000000000000000000001";
|
||||
|
||||
const create = KeycardWalletFactory.methods.create(keycard, {maxTxValue: 999, minBlockDistance: 1}, false, "0x0000000000000000000000000000000000000000", 0);
|
||||
const create = KeycardWalletFactory.methods.create(keycard, false, 1, 0);
|
||||
const receipt = await create.send({
|
||||
from: owner
|
||||
});
|
||||
|
@ -36,7 +36,7 @@ contract('KeycardWalletFactory', () => {
|
|||
const keycard = "0x0000000000000000000000000000000000000002";
|
||||
assert.equal(await KeycardWalletFactory.methods.countWalletsForOwner(keycard).call(), 0);
|
||||
|
||||
const create = KeycardWalletFactory.methods.create(keycard, {maxTxValue: 999, minBlockDistance: 1}, true, "0x0000000000000000000000000000000000000000", 0);
|
||||
const create = KeycardWalletFactory.methods.create(keycard, true, 1, 0);
|
||||
const receipt = await create.send({
|
||||
from: owner
|
||||
});
|
||||
|
@ -54,7 +54,7 @@ contract('KeycardWalletFactory', () => {
|
|||
const keycard = "0x0000000000000000000000000000000000000002";
|
||||
|
||||
try {
|
||||
const create = KeycardWalletFactory.methods.create(keycard, {maxTxValue: 999, minBlockDistance: 1}, false, "0x0000000000000000000000000000000000000000", 0);
|
||||
const create = KeycardWalletFactory.methods.create(keycard, false, 1, 0);
|
||||
const receipt = await create.send({
|
||||
from: owner2
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const KeycardWallet = require('Embark/contracts/KeycardWallet');
|
||||
const KeycardWalletFactory = require('Embark/contracts/KeycardWallet');
|
||||
const KeycardWalletFactory = require('Embark/contracts/KeycardWalletFactory');
|
||||
const MockERC20 = require('Embark/contracts/MockERC20');
|
||||
const { getErrorReason } = require('./utils');
|
||||
|
||||
|
@ -35,8 +35,8 @@ async function signPaymentRequest(signer, message) {
|
|||
let domainData = {
|
||||
name: "KeycardWallet",
|
||||
version: "1",
|
||||
chainId: 1,
|
||||
verifyingContract: KeycardWalletFactory.address
|
||||
chainId: 3,
|
||||
verifyingContract: KeycardWalletFactory.options.address
|
||||
};
|
||||
|
||||
let data = {
|
||||
|
@ -52,15 +52,10 @@ async function signPaymentRequest(signer, message) {
|
|||
return promisifyJsonRPC(cb => web3.currentProvider.sendAsync({method: "eth_signTypedData", params: [signer, data], from: signer}, cb));
|
||||
}
|
||||
|
||||
let owner,
|
||||
merchant;
|
||||
let owner, merchant, keycard;
|
||||
|
||||
config({
|
||||
contracts: {
|
||||
KeycardWallet: {
|
||||
args: [zeroAddress, zeroAddress, {maxTxValue: 0, minBlockDistance: 0}, zeroAddress, zeroAddress, 0]
|
||||
},
|
||||
KeycardWalletFactory: {},
|
||||
MockERC20: {}
|
||||
}
|
||||
}, (err, _accounts) => {
|
||||
|
@ -71,36 +66,30 @@ config({
|
|||
});
|
||||
|
||||
contract('KeycardWallet', () => {
|
||||
before(async () => {
|
||||
let tmp = await KeycardWalletFactory.deploy({arguments: [MockERC20.address]}).send({from: owner});
|
||||
KeycardWalletFactory.options.address = tmp.options.address;
|
||||
|
||||
const create = KeycardWalletFactory.methods.create(zeroAddress, false, 0, 0);
|
||||
const receipt = await create.send({from: owner});
|
||||
|
||||
const event = receipt.events.NewWallet;
|
||||
KeycardWallet.options.address = event.returnValues.wallet;
|
||||
});
|
||||
|
||||
it('registers', async () => {
|
||||
const setRegister = KeycardWallet.methods.setRegister(KeycardWalletFactory.address);
|
||||
const setRegister = KeycardWallet.methods.setRegister(KeycardWalletFactory.options.address);
|
||||
await setRegister.send({
|
||||
from: owner
|
||||
});
|
||||
});
|
||||
|
||||
it('add balance', async () => {
|
||||
const contractBalanceBefore = await web3.eth.getBalance(KeycardWallet.address);
|
||||
assert.equal(contractBalanceBefore, 0);
|
||||
|
||||
const value = 100;
|
||||
const nonce = await web3.eth.getTransactionCount(owner);
|
||||
|
||||
const tx = {
|
||||
from: owner,
|
||||
to: KeycardWallet.address,
|
||||
nonce: nonce,
|
||||
value: value,
|
||||
};
|
||||
|
||||
const res = await web3.eth.sendTransaction(tx);
|
||||
const contractBalanceAfter = await web3.eth.getBalance(KeycardWallet.address);
|
||||
assert.equal(contractBalanceAfter, value);
|
||||
const currentRegister = await KeycardWallet.methods.register().call();
|
||||
assert.equal(currentRegister, KeycardWalletFactory.options.address);
|
||||
});
|
||||
|
||||
it('requestPayment without setting a keycard address', async () => {
|
||||
const block = await web3.eth.getBlock("latest");
|
||||
const requestPayment = KeycardWallet.methods.requestPayment({blockNumber: block.number, blockHash: block.hash, currency: zeroAddress, amount: 0, to: merchant}, "0x00");
|
||||
const requestPayment = KeycardWallet.methods.requestPayment({blockNumber: block.number, blockHash: block.hash, currency: MockERC20.address, amount: 0, to: merchant}, "0x00");
|
||||
try {
|
||||
const estimatedGas = await requestPayment.estimateGas();
|
||||
await requestPayment.send({
|
||||
|
@ -143,9 +132,9 @@ contract('KeycardWallet', () => {
|
|||
const to = merchant;
|
||||
const value = 10;
|
||||
|
||||
const message = {blockNumber: block.number, blockHash: block.hash, currency: zeroAddress, amount: value, to: to};
|
||||
const message = {blockNumber: block.number, blockHash: block.hash, currency: MockERC20.address, amount: value, to: to};
|
||||
// message is signed by the merchant
|
||||
const sig = await signPaymentRequest(merchant, message)
|
||||
const sig = await signPaymentRequest(merchant, message);
|
||||
const requestPayment = KeycardWallet.methods.requestPayment(message, sig);
|
||||
|
||||
try {
|
||||
|
@ -158,9 +147,6 @@ contract('KeycardWallet', () => {
|
|||
} catch (err) {
|
||||
assert.equal(getErrorReason(err), "signer is not the keycard");
|
||||
}
|
||||
|
||||
const pendingWithdrawal = await KeycardWallet.methods.pendingWithdrawals(merchant).call();
|
||||
assert.equal(pendingWithdrawal, 0);
|
||||
});
|
||||
|
||||
it('requestPayment with block in the future', async () => {
|
||||
|
@ -169,8 +155,8 @@ contract('KeycardWallet', () => {
|
|||
const to = merchant;
|
||||
const value = 10;
|
||||
|
||||
const message = {blockNumber: block.number + 1, blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000", currency: zeroAddress, amount: value, to: to};
|
||||
const sig = await signPaymentRequest(keycard, message)
|
||||
const message = {blockNumber: block.number + 1, blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000", currency: MockERC20.address, amount: value, to: to};
|
||||
const sig = await signPaymentRequest(keycard, message);
|
||||
const requestPayment = KeycardWallet.methods.requestPayment(message, sig);
|
||||
|
||||
try {
|
||||
|
@ -183,9 +169,6 @@ contract('KeycardWallet', () => {
|
|||
} catch (err) {
|
||||
assert.equal(getErrorReason(err), "transaction cannot be in the future");
|
||||
}
|
||||
|
||||
const pendingWithdrawal = await KeycardWallet.methods.pendingWithdrawals(merchant).call();
|
||||
assert.equal(pendingWithdrawal, 0);
|
||||
});
|
||||
|
||||
it('requestPayment with wrong block hash', async () => {
|
||||
|
@ -194,7 +177,7 @@ contract('KeycardWallet', () => {
|
|||
const to = merchant;
|
||||
const value = 10;
|
||||
|
||||
const message = {blockNumber: block.number, blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000", currency: zeroAddress, amount: value, to: to};
|
||||
const message = {blockNumber: block.number, blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000", currency: MockERC20.address, amount: value, to: to};
|
||||
const sig = await signPaymentRequest(keycard, message)
|
||||
const requestPayment = KeycardWallet.methods.requestPayment(message, sig);
|
||||
|
||||
|
@ -208,9 +191,6 @@ contract('KeycardWallet', () => {
|
|||
} catch (err) {
|
||||
assert.equal(getErrorReason(err), "invalid block hash");
|
||||
}
|
||||
|
||||
const pendingWithdrawal = await KeycardWallet.methods.pendingWithdrawals(merchant).call();
|
||||
assert.equal(pendingWithdrawal, 0);
|
||||
});
|
||||
|
||||
it('requestPayment with params different from signed params', async () => {
|
||||
|
@ -218,9 +198,9 @@ contract('KeycardWallet', () => {
|
|||
const to = merchant;
|
||||
const value = 10;
|
||||
|
||||
const badMessage = {blockNumber: block.number, blockHash: block.hash, currency: zeroAddress, amount: value + 1, to: to};
|
||||
const badMessage = {blockNumber: block.number, blockHash: block.hash, currency: MockERC20.address, amount: value + 1, to: to};
|
||||
|
||||
const message = {blockNumber: block.number, blockHash: block.hash, currency: zeroAddress, amount: value, to: to};
|
||||
const message = {blockNumber: block.number, blockHash: block.hash, currency: MockERC20.address, amount: value, to: to};
|
||||
const sig = await signPaymentRequest(keycard, message)
|
||||
const requestPayment = KeycardWallet.methods.requestPayment(badMessage, sig);
|
||||
|
||||
|
@ -234,37 +214,31 @@ contract('KeycardWallet', () => {
|
|||
} catch (err) {
|
||||
assert.equal(getErrorReason(err), "signer is not the keycard");
|
||||
}
|
||||
|
||||
|
||||
const pendingWithdrawal = await KeycardWallet.methods.pendingWithdrawals(merchant).call();
|
||||
assert.equal(pendingWithdrawal, 0);
|
||||
});
|
||||
|
||||
it('setSettings needs to be called by the owner', async () => {
|
||||
const setSettings = KeycardWallet.methods.setSettings({maxTxValue: 999, minBlockDistance: 1});
|
||||
it('setMinBlockDistance needs to be called by the owner', async () => {
|
||||
const setMinBlockDistance = KeycardWallet.methods.setMinBlockDistance(1);
|
||||
try {
|
||||
const receipt = await setSettings.send({
|
||||
const receipt = await setMinBlockDistance.send({
|
||||
from: merchant
|
||||
});
|
||||
assert.fail("setSettings should have failed");
|
||||
assert.fail("setMinBlockDistance should have failed");
|
||||
} catch (err) {
|
||||
assert.equal(getErrorReason(err), "owner required");
|
||||
}
|
||||
});
|
||||
|
||||
it('setSettings called by the owner', async () => {
|
||||
const settingsBefore = await KeycardWallet.methods.settings().call();
|
||||
assert.equal(settingsBefore.maxTxValue, 0);
|
||||
assert.equal(settingsBefore.minBlockDistance, 0);
|
||||
it('setMinBlockDistance called by the owner', async () => {
|
||||
const minBlockDistanceBefore = await KeycardWallet.methods.minBlockDistance().call();
|
||||
assert.equal(minBlockDistanceBefore, 0);
|
||||
|
||||
const setSettings = KeycardWallet.methods.setSettings({maxTxValue: 999, minBlockDistance: 1});
|
||||
await setSettings.send({
|
||||
const setMinBlockDistance = KeycardWallet.methods.setMinBlockDistance(1);
|
||||
await setMinBlockDistance.send({
|
||||
from: owner
|
||||
});
|
||||
|
||||
const currentSettings = await KeycardWallet.methods.settings().call();
|
||||
assert.equal(currentSettings.maxTxValue, 999);
|
||||
assert.equal(currentSettings.minBlockDistance, 1);
|
||||
const currentMinBlockDistance = await KeycardWallet.methods.minBlockDistance().call();
|
||||
assert.equal(currentMinBlockDistance, 1);
|
||||
});
|
||||
|
||||
it('requestPayment with token not in the whitelist', async () => {
|
||||
|
@ -272,7 +246,7 @@ contract('KeycardWallet', () => {
|
|||
const to = merchant;
|
||||
const value = 1;
|
||||
|
||||
const message = {blockNumber: block.number, blockHash: block.hash, currency: MockERC20.address, amount: value, to: to};
|
||||
const message = {blockNumber: block.number, blockHash: block.hash, currency: zeroAddress, amount: value, to: to};
|
||||
const sig = await signPaymentRequest(keycard, message)
|
||||
const requestPayment = KeycardWallet.methods.requestPayment(message, sig);
|
||||
|
||||
|
@ -300,27 +274,6 @@ contract('KeycardWallet', () => {
|
|||
assert.equal(tokenMaxTxAmount, maxTxValue);
|
||||
});
|
||||
|
||||
it('requestPayment with value greater than maxTxValue', async () => {
|
||||
const block = await web3.eth.getBlock("latest");
|
||||
const to = merchant;
|
||||
const value = 1000;
|
||||
|
||||
const message = {blockNumber: block.number, blockHash: block.hash, currency: zeroAddress, amount: value, to: to};
|
||||
const sig = await signPaymentRequest(keycard, message)
|
||||
const requestPayment = KeycardWallet.methods.requestPayment(message, sig);
|
||||
|
||||
try {
|
||||
const estimatedGas = await requestPayment.estimateGas();
|
||||
const receipt = await requestPayment.send({
|
||||
from: merchant,
|
||||
gas: estimatedGas
|
||||
});
|
||||
assert.fail("requestPayment should have failed");
|
||||
} catch (err) {
|
||||
assert.equal(getErrorReason(err), "amount not allowed");
|
||||
}
|
||||
});
|
||||
|
||||
it('requestPayment with value greater than maxTxValue for ERC20 token', async () => {
|
||||
const block = await web3.eth.getBlock("latest");
|
||||
const to = merchant;
|
||||
|
@ -342,27 +295,6 @@ contract('KeycardWallet', () => {
|
|||
}
|
||||
});
|
||||
|
||||
it('requestPayment with value greater than balance', async () => {
|
||||
const block = await web3.eth.getBlock("latest");
|
||||
const to = merchant;
|
||||
const value = 101;
|
||||
|
||||
const message = {blockNumber: block.number, blockHash: block.hash, currency: zeroAddress, amount: value, to: to};
|
||||
const sig = await signPaymentRequest(keycard, message)
|
||||
const requestPayment = KeycardWallet.methods.requestPayment(message, sig);
|
||||
|
||||
try {
|
||||
const estimatedGas = await requestPayment.estimateGas();
|
||||
const receipt = await requestPayment.send({
|
||||
from: merchant,
|
||||
gas: estimatedGas
|
||||
});
|
||||
assert.fail("requestPayment should have failed");
|
||||
} catch (err) {
|
||||
assert.equal(getErrorReason(err), "balance is not enough");
|
||||
}
|
||||
});
|
||||
|
||||
it('requestPayment with value greater than balance for ERC20', async () => {
|
||||
const block = await web3.eth.getBlock("latest");
|
||||
const to = merchant;
|
||||
|
@ -384,38 +316,7 @@ contract('KeycardWallet', () => {
|
|||
}
|
||||
});
|
||||
|
||||
it('requestPayment', async () => {
|
||||
const block = await web3.eth.getBlock("latest");
|
||||
const to = merchant;
|
||||
const value = 10;
|
||||
|
||||
const message = {blockNumber: block.number, blockHash: block.hash, currency: zeroAddress, amount: value, to: to};
|
||||
const sig = await signPaymentRequest(keycard, message)
|
||||
const requestPayment = KeycardWallet.methods.requestPayment(message, sig);
|
||||
|
||||
const estimatedGas = await requestPayment.estimateGas();
|
||||
const receipt = await requestPayment.send({
|
||||
from: merchant,
|
||||
gas: estimatedGas
|
||||
});
|
||||
|
||||
const event = receipt.events.NewPaymentRequest;
|
||||
assert.equal(event.returnValues.blockNumber, block.number);
|
||||
assert.equal(event.returnValues.to, to);
|
||||
assert.equal(event.returnValues.amount, value);
|
||||
|
||||
const pendingWithdrawal = await KeycardWallet.methods.pendingWithdrawals(merchant).call();
|
||||
assert.equal(pendingWithdrawal, value);
|
||||
|
||||
const totalPendingWithdrawal = await KeycardWallet.methods.totalPendingWithdrawals().call();
|
||||
assert.equal(totalPendingWithdrawal, value);
|
||||
});
|
||||
|
||||
it('requestPayment with ERC20', async () => {
|
||||
// skip a block for test
|
||||
const setSettings = KeycardWallet.methods.setSettings({maxTxValue: 999, minBlockDistance: 1});
|
||||
await setSettings.send({from: owner});
|
||||
|
||||
const block = await web3.eth.getBlock("latest");
|
||||
const to = merchant;
|
||||
const value = 10;
|
||||
|
@ -430,16 +331,11 @@ contract('KeycardWallet', () => {
|
|||
gas: estimatedGas
|
||||
});
|
||||
|
||||
const event = receipt.events.NewPaymentRequest;
|
||||
const event = receipt.events.NewPayment;
|
||||
assert.equal(event.returnValues.blockNumber, block.number);
|
||||
assert.equal(event.returnValues.to, to);
|
||||
assert.equal(event.returnValues.amount, value);
|
||||
|
||||
const pendingWithdrawal = await KeycardWallet.methods.pendingWithdrawals(merchant).call();
|
||||
assert.equal(pendingWithdrawal, value);
|
||||
|
||||
const totalPendingWithdrawal = await KeycardWallet.methods.totalPendingWithdrawals().call();
|
||||
assert.equal(totalPendingWithdrawal, value);
|
||||
assert.equal(event.returnValues.currency, MockERC20.address);
|
||||
});
|
||||
|
||||
it('requestPayment without waiting for cooldown', async () => {
|
||||
|
@ -447,7 +343,7 @@ contract('KeycardWallet', () => {
|
|||
const to = merchant;
|
||||
const value = 1;
|
||||
|
||||
const message = {blockNumber: block.number, blockHash: block.hash, currency: zeroAddress, amount: value, to: to};
|
||||
const message = {blockNumber: block.number, blockHash: block.hash, currency: MockERC20.address, amount: value, to: to};
|
||||
const sig = await signPaymentRequest(keycard, message)
|
||||
const requestPayment = KeycardWallet.methods.requestPayment(message, sig);
|
||||
|
||||
|
@ -464,36 +360,8 @@ contract('KeycardWallet', () => {
|
|||
}
|
||||
|
||||
// skip a block for next test
|
||||
const setSettings = KeycardWallet.methods.setSettings({maxTxValue: 999, minBlockDistance: 1});
|
||||
await setSettings.send({from: owner});
|
||||
});
|
||||
|
||||
it('requestPayment with value greater than available balance', async () => {
|
||||
const block = await web3.eth.getBlock("latest");
|
||||
const to = merchant;
|
||||
const value = 100;
|
||||
|
||||
const totalBalance = await web3.eth.getBalance(KeycardWallet.address);
|
||||
assert.equal(totalBalance, value);
|
||||
|
||||
const totalPendingWithdrawal = await KeycardWallet.methods.totalPendingWithdrawals().call();
|
||||
assert.equal(totalPendingWithdrawal, 10);
|
||||
|
||||
const message = {blockNumber: block.number, blockHash: block.hash, currency: zeroAddress, amount: value, to: to};
|
||||
const sig = await signPaymentRequest(keycard, message)
|
||||
const requestPayment = KeycardWallet.methods.requestPayment(message, sig);
|
||||
|
||||
try {
|
||||
const estimatedGas = await requestPayment.estimateGas();
|
||||
const receipt = await requestPayment.send({
|
||||
from: merchant,
|
||||
gas: estimatedGas
|
||||
});
|
||||
|
||||
assert.fail("requestPayment should have failed");
|
||||
} catch (err) {
|
||||
assert.equal(getErrorReason(err), "balance is not enough");
|
||||
}
|
||||
const setMinBlockDistance = KeycardWallet.methods.setMinBlockDistance(1);
|
||||
await setMinBlockDistance.send({from: owner});
|
||||
});
|
||||
|
||||
it('requestPayment with old block', async () => {
|
||||
|
@ -502,7 +370,7 @@ contract('KeycardWallet', () => {
|
|||
const to = merchant;
|
||||
const value = 1;
|
||||
|
||||
const message = {blockNumber: block.number, blockHash: block.hash, currency: zeroAddress, amount: value, to: to};
|
||||
const message = {blockNumber: block.number, blockHash: block.hash, currency: MockERC20.address, amount: value, to: to};
|
||||
const sig = await signPaymentRequest(keycard, message)
|
||||
const requestPayment = KeycardWallet.methods.requestPayment(message, sig);
|
||||
|
||||
|
@ -519,58 +387,7 @@ contract('KeycardWallet', () => {
|
|||
}
|
||||
|
||||
// skip a block for next test
|
||||
const setSettings = KeycardWallet.methods.setSettings({maxTxValue: 999, minBlockDistance: 1});
|
||||
await setSettings.send({from: owner});
|
||||
const setMinBlockDistance = KeycardWallet.methods.setMinBlockDistance(1);
|
||||
await setMinBlockDistance.send({from: owner});
|
||||
});
|
||||
|
||||
it('withdraw from address without pendingWithdrawal', async () => {
|
||||
const pendingWithdrawalBefore = await KeycardWallet.methods.pendingWithdrawals(thief).call();
|
||||
assert.equal(pendingWithdrawalBefore, 0);
|
||||
|
||||
const withdraw = KeycardWallet.methods.withdraw();
|
||||
try {
|
||||
const receipt = await withdraw.send({ from: thief });
|
||||
assert.fail("withdraw should have failed");
|
||||
} catch (err) {
|
||||
assert.equal(getErrorReason(err), "no pending withdrawal");
|
||||
}
|
||||
});
|
||||
|
||||
it('withdraw', async () => {
|
||||
const withdrawalValue = 10;
|
||||
const merchantBalanceBefore = await web3.eth.getBalance(merchant);
|
||||
const pendingWithdrawalBefore = await KeycardWallet.methods.pendingWithdrawals(merchant).call();
|
||||
assert.equal(pendingWithdrawalBefore, withdrawalValue);
|
||||
|
||||
const withdraw = KeycardWallet.methods.withdraw();
|
||||
const receipt = await withdraw.send({ from: merchant });
|
||||
const event = receipt.events.NewWithdrawal;
|
||||
assert.equal(event.returnValues.to, merchant);
|
||||
assert.equal(event.returnValues.value, withdrawalValue);
|
||||
|
||||
const gasPrice = await web3.eth.getGasPrice();
|
||||
const fullTxPrice = (new web3.utils.BN(gasPrice)).mul(new web3.utils.BN(receipt.gasUsed));
|
||||
|
||||
const pendingWithdrawalAfter = await KeycardWallet.methods.pendingWithdrawals(merchant).call();
|
||||
assert.equal(pendingWithdrawalAfter, 0);
|
||||
|
||||
const totalPendingWithdrawalAfter = await KeycardWallet.methods.totalPendingWithdrawals().call();
|
||||
assert.equal(totalPendingWithdrawalAfter, 0);
|
||||
|
||||
const expectedMerchantBalance = (new web3.utils.BN(merchantBalanceBefore)).sub(fullTxPrice).add(new web3.utils.BN(withdrawalValue));
|
||||
const merchantBalanceAfter = await web3.eth.getBalance(merchant);
|
||||
assert.deepStrictEqual(new web3.utils.BN(merchantBalanceAfter), expectedMerchantBalance);
|
||||
});
|
||||
|
||||
it('withdraw error on 0 pendingWithdrawal', async () => {
|
||||
const pendingWithdrawal = await KeycardWallet.methods.pendingWithdrawals(merchant).call();
|
||||
assert.equal(pendingWithdrawal, 0);
|
||||
|
||||
try {
|
||||
await KeycardWallet.methods.withdraw().send({ from: merchant });
|
||||
assert.fail("withdraw should have failed");
|
||||
} catch(err) {
|
||||
assert.equal(getErrorReason(err), "no pending withdrawal");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue