-
Notifications
You must be signed in to change notification settings - Fork 559
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
174 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |