Skip to content

Commit

Permalink
Added PaymentForwarder contract.
Browse files Browse the repository at this point in the history
  • Loading branch information
miohtama committed May 9, 2017
1 parent 490e2c8 commit d438f64
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 10 deletions.
68 changes: 68 additions & 0 deletions contracts/PaymentForwarder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
pragma solidity ^0.4.8;

import "./Haltable.sol";

/**
* Forward Ethereum payments to another wallet and track them with an event.
*
* Allows to identify customers who made Ethereum payment for a central token issuance.
* Furthermore allow making a payment on behalf of another address.
*
* Allow pausing to signal the end of the crowdsale.
*/
contract PaymentForwarder is Haltable {

/** Who will get all ETH in the end */
address public teamMultisig;

/** Total incoming money */
uint public totalTransferred;

/** How many distinct customers we have that have made a payment */
uint public customerCount;

/** Total incoming money per centrally tracked customer id */
mapping(uint128 => uint) public paymentsByCustomer;

/** Total incoming money per benefactor address */
mapping(address => uint) public paymentsByBenefactor;

/** A customer has made a payment. Benefactor is the address where the tokens will be ultimately issued.*/
event PaymentForwarded(address source, uint amount, uint128 customerId, address benefactor);

function PaymentForwarder(address _owner, address _teamMultisig) {
teamMultisig = _teamMultisig;
owner = _owner;
}

/**
* Pay on a behalf of an address.
*
* @param customerId Identifier in the central database, UUID v4
*
*/
function pay(uint128 customerId, address benefactor) public stopInEmergency payable {

uint weiAmount = msg.value;

PaymentForwarded(msg.sender, weiAmount, customerId, benefactor);

// We trust Ethereum amounts cannot overflow uint256
totalTransferred += weiAmount;

if(paymentsByCustomer[customerId] == 0) {
customerCount++;
}

paymentsByCustomer[customerId] += weiAmount;

// We track benefactor addresses for extra safety;
// In the case of central ETH issuance tracking has problems we can
// construct ETH contributions solely based on blockchain data
paymentsByBenefactor[benefactor] += weiAmount;

// May run out of gas
if(!teamMultisig.send(weiAmount)) throw;
}

}
2 changes: 1 addition & 1 deletion ico/cmd/rawinvestments.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def main(chain, address, csv_file):
# Sanity check
print("Block number is", web3.eth.blockNumber)

Crowdsale = c.provider.get_contract_factory('MintedTokenCappedCrowdsale')
Crowdsale = c.provider.get_base_contract_factory('MintedTokenCappedCrowdsale')
crowdsale = Crowdsale(address=address)

print("Total amount raised is", from_wei(crowdsale.call().weiRaised(), "ether"), "ether")
Expand Down
17 changes: 8 additions & 9 deletions ico/cmd/rebuildcrowdsale.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@
@click.option('--start-from', nargs=1, help='First row to import (zero based)', required=False, default=0)
@click.option('--multiplier', nargs=1, help='Token amount multiplier, to fix decimal place, as 10^exponent', required=False, default=1)
def main(chain, address, contract_address, csv_file, limit, start_from, multiplier):
"""Rebuild a relaunched CrowdsaleToken contract.
"""Rebuild data on relaunched CrowdsaleToken contract.
This allows you rerun investment data to fix potential errors in the contract.
Example:
rebuild-crowdsale --address=0x001FC7d7E506866aEAB82C11dA515E9DD6D02c25 --chain=kovan --contract-address=0xf09e4a27a02afd29590a989cb2dda9af8eebc77f --start-from=0 --limit=600 --multiplier=12 --csv-file=inputdata.csv
"""

project = Project()
Expand All @@ -40,7 +44,7 @@ def main(chain, address, contract_address, csv_file, limit, start_from, multipli

# Goes through geth account unlock process if needed
if is_account_locked(web3, address):
request_account_unlock(c, address, None)
request_account_unlock(c, address, timeout=3600*6)

transaction = {"from": address}

Expand All @@ -63,7 +67,7 @@ def main(chain, address, contract_address, csv_file, limit, start_from, multipli

start_time = time.time()
start_balance = from_wei(web3.eth.getBalance(address), "ether")
for i in range(start_from, start_from+limit):
for i in range(start_from, min(start_from+limit, len(rows))):
data = rows[i]
addr = data["Address"]
wei = to_wei(data["Invested ETH"], "ether")
Expand All @@ -74,17 +78,12 @@ def main(chain, address, contract_address, csv_file, limit, start_from, multipli
tokens *= multiplier
end_balance = from_wei(web3.eth.getBalance(address), "ether")
spent = start_balance - end_balance
print("Row", i, "giving", tokens, "to", addr, "from tx", orig_txid, "#", orig_tx_index, "ETH spent", spent, "time passed", time.time() - start_time)
print("Row", i, "giving", tokens, "to", addr, "from tx", orig_txid, "ETH spent", spent, "time passed", time.time() - start_time)

if relaunched_crowdsale.call().getRestoredTransactionStatus(orig_txid):
print("Already restored, skipping")
continue

raised = relaunched_crowdsale.call().weiRaised()
sold = relaunched_crowdsale.call().tokensSold()
if relaunched_crowdsale.call().isBreakingCap(tokens, wei, raised, sold):
sys.exit("Oops broke the cap.")

txid = relaunched_crowdsale.transact(transaction).setInvestorDataAndIssueNewToken(addr, wei, tokens, orig_txid)
check_succesful_tx(web3, txid)

Expand Down
97 changes: 97 additions & 0 deletions ico/tests/contracts/test_forwarder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""Payment forwarder."""
import pytest

import uuid
from eth_utils import to_wei
from ethereum.tester import TransactionFailed


@pytest.fixture
def payment_forwarder(chain, team_multisig):
args = [team_multisig, team_multisig]

tx = {
"from": team_multisig
}

contract, hash = chain.provider.deploy_contract('PaymentForwarder', deploy_args=args, deploy_transaction=tx)
return contract


def test_pay_once(web3, payment_forwarder, team_multisig, customer):
"""Pay for myself."""

value = to_wei("1.0", "ether")
customer_id = int(uuid.uuid4().hex, 16) # Customer ids are 128-bit UUID v4

team_multisig_begin = web3.eth.getBalance(team_multisig)
payment_forwarder.transact({"value": value, "from": customer}).pay(customer_id, customer)
team_multisig_end = web3.eth.getBalance(team_multisig)

assert team_multisig_end - team_multisig_begin > 0
assert payment_forwarder.call().totalTransferred() == value
assert payment_forwarder.call().paymentsByCustomer(customer_id) == value
assert payment_forwarder.call().customerCount() == 1

# Check we properly generate an event
events = payment_forwarder.pastEvents("PaymentForwarded").get()
assert len(events) == 1
e = events[-1]
assert e["args"]["source"] == customer
assert e["args"]["amount"] == value
assert e["args"]["customerId"] == customer_id
assert e["args"]["benefactor"] == customer


def test_pay_twice(web3, payment_forwarder, team_multisig, customer, customer_2):
"""Pay for myself twice."""

value = to_wei("1.0", "ether")
customer_id = int(uuid.uuid4().hex, 16) # Customer ids are 128-bit UUID v4

team_multisig_begin = web3.eth.getBalance(team_multisig)
# We pay from two distinct addresses on behalf of the same customer
payment_forwarder.transact({"value": value, "from": customer}).pay(customer_id, customer)
payment_forwarder.transact({"value": value, "from": customer_2}).pay(customer_id, customer)
team_multisig_end = web3.eth.getBalance(team_multisig)

assert team_multisig_end - team_multisig_begin > 0
assert payment_forwarder.call().totalTransferred() == 2*value
assert payment_forwarder.call().paymentsByCustomer(customer_id) == 2*value
assert payment_forwarder.call().paymentsByBenefactor(customer) == 2*value
assert payment_forwarder.call().customerCount() == 1

# Check we properly generate an event
events = payment_forwarder.pastEvents("PaymentForwarded").get()
assert len(events) == 2
e = events[-1]
assert e["args"]["source"] == customer_2
assert e["args"]["amount"] == value
assert e["args"]["customerId"] == customer_id
assert e["args"]["benefactor"] == customer


def test_halt(web3, payment_forwarder, team_multisig, customer):
"""We can stop crowdsale."""

value = to_wei("1.0", "ether")
customer_id = int(uuid.uuid4().hex, 16) # Customer ids are 128-bit UUID v4

team_multisig_begin = web3.eth.getBalance(team_multisig)
payment_forwarder.transact({"from": team_multisig}).halt()
with pytest.raises(TransactionFailed):
payment_forwarder.transact({"value": value, "from": customer}).pay(customer_id, customer)


def test_unhalt(web3, payment_forwarder, team_multisig, customer):
"""We can resume crowdsale."""

value = to_wei("1.0", "ether")
customer_id = int(uuid.uuid4().hex, 16) # Customer ids are 128-bit UUID v4

payment_forwarder.transact({"from": team_multisig}).halt()
payment_forwarder.transact({"from": team_multisig}).unhalt()

assert payment_forwarder.call().customerCount() == 0
payment_forwarder.transact({"value": value, "from": customer}).pay(customer_id, customer)
assert payment_forwarder.call().customerCount() == 1

0 comments on commit d438f64

Please sign in to comment.