Transaction Hash:
Block:
13685215 at Nov-25-2021 07:04:45 PM +UTC
Transaction Fee:
0.006660688955877984 ETH
$14.52
Gas Used:
54,627 Gas / 121.930344992 Gwei
Emitted Events:
| 285 |
Vyper_contract.Transfer( _from=Forwarder, _to=[Receiver] 0x1522900b6dafac587d499a862861c0869be6e428, _value=23080870000000000000000 )
|
| 286 |
Forwarder.TokensFlushed( tokenContractAddress=Vyper_contract, value=23080870000000000000000 )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x00BDb569...b9Ae374eD | (Bitstamp 1) |
49.948988809406941281 Eth
Nonce: 1319035
|
49.942328120451063297 Eth
Nonce: 1319036
| 0.006660688955877984 | |
| 0xD533a949...bA034cd52 | |||||
|
0xEA674fdD...16B898ec8
Miner
| (Ethermine) | 2,258.140015098598366255 Eth | 2,258.140097039098366255 Eth | 0.0000819405 |
Execution Trace
Bitstamp 2.2da03409( )
WalletSimple.flushForwarderTokens( forwarderAddress=0x91db9e27E750C43a96926B2E04D795c24f13F67B, tokenContractAddress=0xD533a949740bb3306d119CC777fa900bA034cd52 )Forwarder.flushTokens( tokenContractAddress=0xD533a949740bb3306d119CC777fa900bA034cd52 )-
Vyper_contract.balanceOf( arg0=0x91db9e27E750C43a96926B2E04D795c24f13F67B ) => ( 23080870000000000000000 )
-
Vyper_contract.transfer( _to=0x1522900B6daFac587d499a862861C0869Be6E428, _value=23080870000000000000000 ) => ( True )
-
File 1 of 3: Forwarder
File 2 of 3: Vyper_contract
File 3 of 3: WalletSimple
pragma solidity ^0.4.14;
/**
* Contract that exposes the needed erc20 token functions
*/
contract ERC20Interface {
// Send _value amount of tokens to address _to
function transfer(address _to, uint256 _value) returns (bool success);
// Get the account balance of another account with address _owner
function balanceOf(address _owner) constant returns (uint256 balance);
}
/**
* Contract that will forward any incoming Ether to its creator
*/
contract Forwarder {
// Address to which any funds sent to this contract will be forwarded
address public parentAddress;
event ForwarderDeposited(address from, uint value, bytes data);
event TokensFlushed(
address tokenContractAddress, // The contract address of the token
uint value // Amount of token sent
);
/**
* Create the contract, and set the destination address to that of the creator
*/
function Forwarder() {
parentAddress = msg.sender;
}
/**
* Modifier that will execute internal code block only if the sender is a parent of the forwarder contract
*/
modifier onlyParent {
if (msg.sender != parentAddress) {
throw;
}
_;
}
/**
* Default function; Gets called when Ether is deposited, and forwards it to the destination address
*/
function() payable {
if (!parentAddress.call.value(msg.value)(msg.data))
throw;
// Fire off the deposited event if we can forward it
ForwarderDeposited(msg.sender, msg.value, msg.data);
}
/**
* Execute a token transfer of the full balance from the forwarder token to the main wallet contract
* @param tokenContractAddress the address of the erc20 token contract
*/
function flushTokens(address tokenContractAddress) onlyParent {
ERC20Interface instance = ERC20Interface(tokenContractAddress);
var forwarderAddress = address(this);
var forwarderBalance = instance.balanceOf(forwarderAddress);
if (forwarderBalance == 0) {
return;
}
if (!instance.transfer(parentAddress, forwarderBalance)) {
throw;
}
TokensFlushed(tokenContractAddress, forwarderBalance);
}
/**
* It is possible that funds were sent to this address before the contract was deployed.
* We can flush those funds to the destination address.
*/
function flush() {
if (!parentAddress.call.value(this.balance)())
throw;
}
}
/**
* Basic multi-signer wallet designed for use in a co-signing environment where 2 signatures are required to move funds.
* Typically used in a 2-of-3 signing configuration. Uses ecrecover to allow for 2 signatures in a single transaction.
*/
contract WalletSimple {
// Events
event Deposited(address from, uint value, bytes data);
event SafeModeActivated(address msgSender);
event Transacted(
address msgSender, // Address of the sender of the message initiating the transaction
address otherSigner, // Address of the signer (second signature) used to initiate the transaction
bytes32 operation, // Operation hash (sha3 of toAddress, value, data, expireTime, sequenceId)
address toAddress, // The address the transaction was sent to
uint value, // Amount of Wei sent to the address
bytes data // Data sent when invoking the transaction
);
event TokenTransacted(
address msgSender, // Address of the sender of the message initiating the transaction
address otherSigner, // Address of the signer (second signature) used to initiate the transaction
bytes32 operation, // Operation hash (sha3 of toAddress, value, tokenContractAddress, expireTime, sequenceId)
address toAddress, // The address the transaction was sent to
uint value, // Amount of token sent
address tokenContractAddress // The contract address of the token
);
// Public fields
address[] public signers; // The addresses that can co-sign transactions on the wallet
bool public safeMode = false; // When active, wallet may only send to signer addresses
// Internal fields
uint constant SEQUENCE_ID_WINDOW_SIZE = 10;
uint[10] recentSequenceIds;
/**
* Modifier that will execute internal code block only if the sender is an authorized signer on this wallet
*/
modifier onlysigner {
if (!isSigner(msg.sender)) {
throw;
}
_;
}
/**
* Set up a simple multi-sig wallet by specifying the signers allowed to be used on this wallet.
* 2 signers will be required to send a transaction from this wallet.
* Note: The sender is NOT automatically added to the list of signers.
* Signers CANNOT be changed once they are set
*
* @param allowedSigners An array of signers on the wallet
*/
function WalletSimple(address[] allowedSigners) {
if (allowedSigners.length != 3) {
// Invalid number of signers
throw;
}
signers = allowedSigners;
}
/**
* Gets called when a transaction is received without calling a method
*/
function() payable {
if (msg.value > 0) {
// Fire deposited event if we are receiving funds
Deposited(msg.sender, msg.value, msg.data);
}
}
/**
* Create a new contract (and also address) that forwards funds to this contract
* returns address of newly created forwarder address
*/
function createForwarder() onlysigner returns (address) {
return new Forwarder();
}
/**
* Execute a multi-signature transaction from this wallet using 2 signers: one from msg.sender and the other from ecrecover.
* The signature is a signed form (using eth.sign) of tightly packed toAddress, value, data, expireTime and sequenceId
* Sequence IDs are numbers starting from 1. They are used to prevent replay attacks and may not be repeated.
*
* @param toAddress the destination address to send an outgoing transaction
* @param value the amount in Wei to be sent
* @param data the data to send to the toAddress when invoking the transaction
* @param expireTime the number of seconds since 1970 for which this transaction is valid
* @param sequenceId the unique sequence id obtainable from getNextSequenceId
* @param signature the result of eth.sign on the operationHash sha3(toAddress, value, data, expireTime, sequenceId)
*/
function sendMultiSig(address toAddress, uint value, bytes data, uint expireTime, uint sequenceId, bytes signature) onlysigner {
// Verify the other signer
var operationHash = sha3("ETHER", toAddress, value, data, expireTime, sequenceId);
var otherSigner = verifyMultiSig(toAddress, operationHash, signature, expireTime, sequenceId);
// Success, send the transaction
if (!(toAddress.call.value(value)(data))) {
// Failed executing transaction
throw;
}
Transacted(msg.sender, otherSigner, operationHash, toAddress, value, data);
}
/**
* Execute a multi-signature token transfer from this wallet using 2 signers: one from msg.sender and the other from ecrecover.
* The signature is a signed form (using eth.sign) of tightly packed toAddress, value, tokenContractAddress, expireTime and sequenceId
* Sequence IDs are numbers starting from 1. They are used to prevent replay attacks and may not be repeated.
*
* @param toAddress the destination address to send an outgoing transaction
* @param value the amount in tokens to be sent
* @param tokenContractAddress the address of the erc20 token contract
* @param expireTime the number of seconds since 1970 for which this transaction is valid
* @param sequenceId the unique sequence id obtainable from getNextSequenceId
* @param signature the result of eth.sign on the operationHash sha3(toAddress, value, tokenContractAddress, expireTime, sequenceId)
*/
function sendMultiSigToken(address toAddress, uint value, address tokenContractAddress, uint expireTime, uint sequenceId, bytes signature) onlysigner {
// Verify the other signer
var operationHash = sha3("ERC20", toAddress, value, tokenContractAddress, expireTime, sequenceId);
var otherSigner = verifyMultiSig(toAddress, operationHash, signature, expireTime, sequenceId);
ERC20Interface instance = ERC20Interface(tokenContractAddress);
if (!instance.transfer(toAddress, value)) {
throw;
}
TokenTransacted(msg.sender, otherSigner, operationHash, toAddress, value, tokenContractAddress);
}
/**
* Execute a token flush from one of the forwarder addresses. This transfer needs only a single signature and can be done by any signer
*
* @param forwarderAddress the address of the forwarder address to flush the tokens from
* @param tokenContractAddress the address of the erc20 token contract
*/
function flushForwarderTokens(address forwarderAddress, address tokenContractAddress) onlysigner {
Forwarder forwarder = Forwarder(forwarderAddress);
forwarder.flushTokens(tokenContractAddress);
}
/**
* Do common multisig verification for both eth sends and erc20token transfers
*
* @param toAddress the destination address to send an outgoing transaction
* @param operationHash the sha3 of the toAddress, value, data/tokenContractAddress and expireTime
* @param signature the tightly packed signature of r, s, and v as an array of 65 bytes (returned by eth.sign)
* @param expireTime the number of seconds since 1970 for which this transaction is valid
* @param sequenceId the unique sequence id obtainable from getNextSequenceId
* returns address of the address to send tokens or eth to
*/
function verifyMultiSig(address toAddress, bytes32 operationHash, bytes signature, uint expireTime, uint sequenceId) private returns (address) {
var otherSigner = recoverAddressFromSignature(operationHash, signature);
// Verify if we are in safe mode. In safe mode, the wallet can only send to signers
if (safeMode && !isSigner(toAddress)) {
// We are in safe mode and the toAddress is not a signer. Disallow!
throw;
}
// Verify that the transaction has not expired
if (expireTime < block.timestamp) {
// Transaction expired
throw;
}
// Try to insert the sequence ID. Will throw if the sequence id was invalid
tryInsertSequenceId(sequenceId);
if (!isSigner(otherSigner)) {
// Other signer not on this wallet or operation does not match arguments
throw;
}
if (otherSigner == msg.sender) {
// Cannot approve own transaction
throw;
}
return otherSigner;
}
/**
* Irrevocably puts contract into safe mode. When in this mode, transactions may only be sent to signing addresses.
*/
function activateSafeMode() onlysigner {
safeMode = true;
SafeModeActivated(msg.sender);
}
/**
* Determine if an address is a signer on this wallet
* @param signer address to check
* returns boolean indicating whether address is signer or not
*/
function isSigner(address signer) returns (bool) {
// Iterate through all signers on the wallet and
for (uint i = 0; i < signers.length; i++) {
if (signers[i] == signer) {
return true;
}
}
return false;
}
/**
* Gets the second signer's address using ecrecover
* @param operationHash the sha3 of the toAddress, value, data/tokenContractAddress and expireTime
* @param signature the tightly packed signature of r, s, and v as an array of 65 bytes (returned by eth.sign)
* returns address recovered from the signature
*/
function recoverAddressFromSignature(bytes32 operationHash, bytes signature) private returns (address) {
if (signature.length != 65) {
throw;
}
// We need to unpack the signature, which is given as an array of 65 bytes (from eth.sign)
bytes32 r;
bytes32 s;
uint8 v;
assembly {
r := mload(add(signature, 32))
s := mload(add(signature, 64))
v := and(mload(add(signature, 65)), 255)
}
if (v < 27) {
v += 27; // Ethereum versions are 27 or 28 as opposed to 0 or 1 which is submitted by some signing libs
}
return ecrecover(operationHash, v, r, s);
}
/**
* Verify that the sequence id has not been used before and inserts it. Throws if the sequence ID was not accepted.
* We collect a window of up to 10 recent sequence ids, and allow any sequence id that is not in the window and
* greater than the minimum element in the window.
* @param sequenceId to insert into array of stored ids
*/
function tryInsertSequenceId(uint sequenceId) onlysigner private {
// Keep a pointer to the lowest value element in the window
uint lowestValueIndex = 0;
for (uint i = 0; i < SEQUENCE_ID_WINDOW_SIZE; i++) {
if (recentSequenceIds[i] == sequenceId) {
// This sequence ID has been used before. Disallow!
throw;
}
if (recentSequenceIds[i] < recentSequenceIds[lowestValueIndex]) {
lowestValueIndex = i;
}
}
if (sequenceId < recentSequenceIds[lowestValueIndex]) {
// The sequence ID being used is lower than the lowest value in the window
// so we cannot accept it as it may have been used before
throw;
}
if (sequenceId > (recentSequenceIds[lowestValueIndex] + 10000)) {
// Block sequence IDs which are much higher than the lowest value
// This prevents people blocking the contract by using very large sequence IDs quickly
throw;
}
recentSequenceIds[lowestValueIndex] = sequenceId;
}
/**
* Gets the next available sequence ID for signing when using executeAndConfirm
* returns the sequenceId one higher than the highest currently stored
*/
function getNextSequenceId() returns (uint) {
uint highestSequenceId = 0;
for (uint i = 0; i < SEQUENCE_ID_WINDOW_SIZE; i++) {
if (recentSequenceIds[i] > highestSequenceId) {
highestSequenceId = recentSequenceIds[i];
}
}
return highestSequenceId + 1;
}
}File 2 of 3: Vyper_contract
# @version 0.2.4
"""
@title Curve DAO Token
@author Curve Finance
@license MIT
@notice ERC20 with piecewise-linear mining supply.
@dev Based on the ERC-20 token standard as defined at
https://eips.ethereum.org/EIPS/eip-20
"""
from vyper.interfaces import ERC20
implements: ERC20
event Transfer:
_from: indexed(address)
_to: indexed(address)
_value: uint256
event Approval:
_owner: indexed(address)
_spender: indexed(address)
_value: uint256
event UpdateMiningParameters:
time: uint256
rate: uint256
supply: uint256
event SetMinter:
minter: address
event SetAdmin:
admin: address
name: public(String[64])
symbol: public(String[32])
decimals: public(uint256)
balanceOf: public(HashMap[address, uint256])
allowances: HashMap[address, HashMap[address, uint256]]
total_supply: uint256
minter: public(address)
admin: public(address)
# General constants
YEAR: constant(uint256) = 86400 * 365
# Allocation:
# =========
# * shareholders - 30%
# * emplyees - 3%
# * DAO-controlled reserve - 5%
# * Early users - 5%
# == 43% ==
# left for inflation: 57%
# Supply parameters
INITIAL_SUPPLY: constant(uint256) = 1_303_030_303
INITIAL_RATE: constant(uint256) = 274_815_283 * 10 ** 18 / YEAR # leading to 43% premine
RATE_REDUCTION_TIME: constant(uint256) = YEAR
RATE_REDUCTION_COEFFICIENT: constant(uint256) = 1189207115002721024 # 2 ** (1/4) * 1e18
RATE_DENOMINATOR: constant(uint256) = 10 ** 18
INFLATION_DELAY: constant(uint256) = 86400
# Supply variables
mining_epoch: public(int128)
start_epoch_time: public(uint256)
rate: public(uint256)
start_epoch_supply: uint256
@external
def __init__(_name: String[64], _symbol: String[32], _decimals: uint256):
"""
@notice Contract constructor
@param _name Token full name
@param _symbol Token symbol
@param _decimals Number of decimals for token
"""
init_supply: uint256 = INITIAL_SUPPLY * 10 ** _decimals
self.name = _name
self.symbol = _symbol
self.decimals = _decimals
self.balanceOf[msg.sender] = init_supply
self.total_supply = init_supply
self.admin = msg.sender
log Transfer(ZERO_ADDRESS, msg.sender, init_supply)
self.start_epoch_time = block.timestamp + INFLATION_DELAY - RATE_REDUCTION_TIME
self.mining_epoch = -1
self.rate = 0
self.start_epoch_supply = init_supply
@internal
def _update_mining_parameters():
"""
@dev Update mining rate and supply at the start of the epoch
Any modifying mining call must also call this
"""
_rate: uint256 = self.rate
_start_epoch_supply: uint256 = self.start_epoch_supply
self.start_epoch_time += RATE_REDUCTION_TIME
self.mining_epoch += 1
if _rate == 0:
_rate = INITIAL_RATE
else:
_start_epoch_supply += _rate * RATE_REDUCTION_TIME
self.start_epoch_supply = _start_epoch_supply
_rate = _rate * RATE_DENOMINATOR / RATE_REDUCTION_COEFFICIENT
self.rate = _rate
log UpdateMiningParameters(block.timestamp, _rate, _start_epoch_supply)
@external
def update_mining_parameters():
"""
@notice Update mining rate and supply at the start of the epoch
@dev Callable by any address, but only once per epoch
Total supply becomes slightly larger if this function is called late
"""
assert block.timestamp >= self.start_epoch_time + RATE_REDUCTION_TIME # dev: too soon!
self._update_mining_parameters()
@external
def start_epoch_time_write() -> uint256:
"""
@notice Get timestamp of the current mining epoch start
while simultaneously updating mining parameters
@return Timestamp of the epoch
"""
_start_epoch_time: uint256 = self.start_epoch_time
if block.timestamp >= _start_epoch_time + RATE_REDUCTION_TIME:
self._update_mining_parameters()
return self.start_epoch_time
else:
return _start_epoch_time
@external
def future_epoch_time_write() -> uint256:
"""
@notice Get timestamp of the next mining epoch start
while simultaneously updating mining parameters
@return Timestamp of the next epoch
"""
_start_epoch_time: uint256 = self.start_epoch_time
if block.timestamp >= _start_epoch_time + RATE_REDUCTION_TIME:
self._update_mining_parameters()
return self.start_epoch_time + RATE_REDUCTION_TIME
else:
return _start_epoch_time + RATE_REDUCTION_TIME
@internal
@view
def _available_supply() -> uint256:
return self.start_epoch_supply + (block.timestamp - self.start_epoch_time) * self.rate
@external
@view
def available_supply() -> uint256:
"""
@notice Current number of tokens in existence (claimed or unclaimed)
"""
return self._available_supply()
@external
@view
def mintable_in_timeframe(start: uint256, end: uint256) -> uint256:
"""
@notice How much supply is mintable from start timestamp till end timestamp
@param start Start of the time interval (timestamp)
@param end End of the time interval (timestamp)
@return Tokens mintable from `start` till `end`
"""
assert start <= end # dev: start > end
to_mint: uint256 = 0
current_epoch_time: uint256 = self.start_epoch_time
current_rate: uint256 = self.rate
# Special case if end is in future (not yet minted) epoch
if end > current_epoch_time + RATE_REDUCTION_TIME:
current_epoch_time += RATE_REDUCTION_TIME
current_rate = current_rate * RATE_DENOMINATOR / RATE_REDUCTION_COEFFICIENT
assert end <= current_epoch_time + RATE_REDUCTION_TIME # dev: too far in future
for i in range(999): # Curve will not work in 1000 years. Darn!
if end >= current_epoch_time:
current_end: uint256 = end
if current_end > current_epoch_time + RATE_REDUCTION_TIME:
current_end = current_epoch_time + RATE_REDUCTION_TIME
current_start: uint256 = start
if current_start >= current_epoch_time + RATE_REDUCTION_TIME:
break # We should never get here but what if...
elif current_start < current_epoch_time:
current_start = current_epoch_time
to_mint += current_rate * (current_end - current_start)
if start >= current_epoch_time:
break
current_epoch_time -= RATE_REDUCTION_TIME
current_rate = current_rate * RATE_REDUCTION_COEFFICIENT / RATE_DENOMINATOR # double-division with rounding made rate a bit less => good
assert current_rate <= INITIAL_RATE # This should never happen
return to_mint
@external
def set_minter(_minter: address):
"""
@notice Set the minter address
@dev Only callable once, when minter has not yet been set
@param _minter Address of the minter
"""
assert msg.sender == self.admin # dev: admin only
assert self.minter == ZERO_ADDRESS # dev: can set the minter only once, at creation
self.minter = _minter
log SetMinter(_minter)
@external
def set_admin(_admin: address):
"""
@notice Set the new admin.
@dev After all is set up, admin only can change the token name
@param _admin New admin address
"""
assert msg.sender == self.admin # dev: admin only
self.admin = _admin
log SetAdmin(_admin)
@external
@view
def totalSupply() -> uint256:
"""
@notice Total number of tokens in existence.
"""
return self.total_supply
@external
@view
def allowance(_owner : address, _spender : address) -> uint256:
"""
@notice Check the amount of tokens that an owner allowed to a spender
@param _owner The address which owns the funds
@param _spender The address which will spend the funds
@return uint256 specifying the amount of tokens still available for the spender
"""
return self.allowances[_owner][_spender]
@external
def transfer(_to : address, _value : uint256) -> bool:
"""
@notice Transfer `_value` tokens from `msg.sender` to `_to`
@dev Vyper does not allow underflows, so the subtraction in
this function will revert on an insufficient balance
@param _to The address to transfer to
@param _value The amount to be transferred
@return bool success
"""
assert _to != ZERO_ADDRESS # dev: transfers to 0x0 are not allowed
self.balanceOf[msg.sender] -= _value
self.balanceOf[_to] += _value
log Transfer(msg.sender, _to, _value)
return True
@external
def transferFrom(_from : address, _to : address, _value : uint256) -> bool:
"""
@notice Transfer `_value` tokens from `_from` to `_to`
@param _from address The address which you want to send tokens from
@param _to address The address which you want to transfer to
@param _value uint256 the amount of tokens to be transferred
@return bool success
"""
assert _to != ZERO_ADDRESS # dev: transfers to 0x0 are not allowed
# NOTE: vyper does not allow underflows
# so the following subtraction would revert on insufficient balance
self.balanceOf[_from] -= _value
self.balanceOf[_to] += _value
self.allowances[_from][msg.sender] -= _value
log Transfer(_from, _to, _value)
return True
@external
def approve(_spender : address, _value : uint256) -> bool:
"""
@notice Approve `_spender` to transfer `_value` tokens on behalf of `msg.sender`
@dev Approval may only be from zero -> nonzero or from nonzero -> zero in order
to mitigate the potential race condition described here:
https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
@param _spender The address which will spend the funds
@param _value The amount of tokens to be spent
@return bool success
"""
assert _value == 0 or self.allowances[msg.sender][_spender] == 0
self.allowances[msg.sender][_spender] = _value
log Approval(msg.sender, _spender, _value)
return True
@external
def mint(_to: address, _value: uint256) -> bool:
"""
@notice Mint `_value` tokens and assign them to `_to`
@dev Emits a Transfer event originating from 0x00
@param _to The account that will receive the created tokens
@param _value The amount that will be created
@return bool success
"""
assert msg.sender == self.minter # dev: minter only
assert _to != ZERO_ADDRESS # dev: zero address
if block.timestamp >= self.start_epoch_time + RATE_REDUCTION_TIME:
self._update_mining_parameters()
_total_supply: uint256 = self.total_supply + _value
assert _total_supply <= self._available_supply() # dev: exceeds allowable mint amount
self.total_supply = _total_supply
self.balanceOf[_to] += _value
log Transfer(ZERO_ADDRESS, _to, _value)
return True
@external
def burn(_value: uint256) -> bool:
"""
@notice Burn `_value` tokens belonging to `msg.sender`
@dev Emits a Transfer event with a destination of 0x00
@param _value The amount that will be burned
@return bool success
"""
self.balanceOf[msg.sender] -= _value
self.total_supply -= _value
log Transfer(msg.sender, ZERO_ADDRESS, _value)
return True
@external
def set_name(_name: String[64], _symbol: String[32]):
"""
@notice Change the token name and symbol to `_name` and `_symbol`
@dev Only callable by the admin account
@param _name New token name
@param _symbol New token symbol
"""
assert msg.sender == self.admin, "Only admin is allowed to change name"
self.name = _name
self.symbol = _symbolFile 3 of 3: WalletSimple
pragma solidity ^0.4.14;
/**
* Contract that exposes the needed erc20 token functions
*/
contract ERC20Interface {
// Send _value amount of tokens to address _to
function transfer(address _to, uint256 _value) returns (bool success);
// Get the account balance of another account with address _owner
function balanceOf(address _owner) constant returns (uint256 balance);
}
/**
* Contract that will forward any incoming Ether to its creator
*/
contract Forwarder {
// Address to which any funds sent to this contract will be forwarded
address public parentAddress;
event ForwarderDeposited(address from, uint value, bytes data);
event TokensFlushed(
address tokenContractAddress, // The contract address of the token
uint value // Amount of token sent
);
/**
* Create the contract, and set the destination address to that of the creator
*/
function Forwarder() {
parentAddress = msg.sender;
}
/**
* Modifier that will execute internal code block only if the sender is a parent of the forwarder contract
*/
modifier onlyParent {
if (msg.sender != parentAddress) {
throw;
}
_;
}
/**
* Default function; Gets called when Ether is deposited, and forwards it to the destination address
*/
function() payable {
if (!parentAddress.call.value(msg.value)(msg.data))
throw;
// Fire off the deposited event if we can forward it
ForwarderDeposited(msg.sender, msg.value, msg.data);
}
/**
* Execute a token transfer of the full balance from the forwarder token to the main wallet contract
* @param tokenContractAddress the address of the erc20 token contract
*/
function flushTokens(address tokenContractAddress) onlyParent {
ERC20Interface instance = ERC20Interface(tokenContractAddress);
var forwarderAddress = address(this);
var forwarderBalance = instance.balanceOf(forwarderAddress);
if (forwarderBalance == 0) {
return;
}
if (!instance.transfer(parentAddress, forwarderBalance)) {
throw;
}
TokensFlushed(tokenContractAddress, forwarderBalance);
}
/**
* It is possible that funds were sent to this address before the contract was deployed.
* We can flush those funds to the destination address.
*/
function flush() {
if (!parentAddress.call.value(this.balance)())
throw;
}
}
/**
* Basic multi-signer wallet designed for use in a co-signing environment where 2 signatures are required to move funds.
* Typically used in a 2-of-3 signing configuration. Uses ecrecover to allow for 2 signatures in a single transaction.
*/
contract WalletSimple {
// Events
event Deposited(address from, uint value, bytes data);
event SafeModeActivated(address msgSender);
event Transacted(
address msgSender, // Address of the sender of the message initiating the transaction
address otherSigner, // Address of the signer (second signature) used to initiate the transaction
bytes32 operation, // Operation hash (sha3 of toAddress, value, data, expireTime, sequenceId)
address toAddress, // The address the transaction was sent to
uint value, // Amount of Wei sent to the address
bytes data // Data sent when invoking the transaction
);
event TokenTransacted(
address msgSender, // Address of the sender of the message initiating the transaction
address otherSigner, // Address of the signer (second signature) used to initiate the transaction
bytes32 operation, // Operation hash (sha3 of toAddress, value, tokenContractAddress, expireTime, sequenceId)
address toAddress, // The address the transaction was sent to
uint value, // Amount of token sent
address tokenContractAddress // The contract address of the token
);
// Public fields
address[] public signers; // The addresses that can co-sign transactions on the wallet
bool public safeMode = false; // When active, wallet may only send to signer addresses
// Internal fields
uint constant SEQUENCE_ID_WINDOW_SIZE = 10;
uint[10] recentSequenceIds;
/**
* Modifier that will execute internal code block only if the sender is an authorized signer on this wallet
*/
modifier onlysigner {
if (!isSigner(msg.sender)) {
throw;
}
_;
}
/**
* Set up a simple multi-sig wallet by specifying the signers allowed to be used on this wallet.
* 2 signers will be required to send a transaction from this wallet.
* Note: The sender is NOT automatically added to the list of signers.
* Signers CANNOT be changed once they are set
*
* @param allowedSigners An array of signers on the wallet
*/
function WalletSimple(address[] allowedSigners) {
if (allowedSigners.length != 3) {
// Invalid number of signers
throw;
}
signers = allowedSigners;
}
/**
* Gets called when a transaction is received without calling a method
*/
function() payable {
if (msg.value > 0) {
// Fire deposited event if we are receiving funds
Deposited(msg.sender, msg.value, msg.data);
}
}
/**
* Create a new contract (and also address) that forwards funds to this contract
* returns address of newly created forwarder address
*/
function createForwarder() onlysigner returns (address) {
return new Forwarder();
}
/**
* Execute a multi-signature transaction from this wallet using 2 signers: one from msg.sender and the other from ecrecover.
* The signature is a signed form (using eth.sign) of tightly packed toAddress, value, data, expireTime and sequenceId
* Sequence IDs are numbers starting from 1. They are used to prevent replay attacks and may not be repeated.
*
* @param toAddress the destination address to send an outgoing transaction
* @param value the amount in Wei to be sent
* @param data the data to send to the toAddress when invoking the transaction
* @param expireTime the number of seconds since 1970 for which this transaction is valid
* @param sequenceId the unique sequence id obtainable from getNextSequenceId
* @param signature the result of eth.sign on the operationHash sha3(toAddress, value, data, expireTime, sequenceId)
*/
function sendMultiSig(address toAddress, uint value, bytes data, uint expireTime, uint sequenceId, bytes signature) onlysigner {
// Verify the other signer
var operationHash = sha3("ETHER", toAddress, value, data, expireTime, sequenceId);
var otherSigner = verifyMultiSig(toAddress, operationHash, signature, expireTime, sequenceId);
// Success, send the transaction
if (!(toAddress.call.value(value)(data))) {
// Failed executing transaction
throw;
}
Transacted(msg.sender, otherSigner, operationHash, toAddress, value, data);
}
/**
* Execute a multi-signature token transfer from this wallet using 2 signers: one from msg.sender and the other from ecrecover.
* The signature is a signed form (using eth.sign) of tightly packed toAddress, value, tokenContractAddress, expireTime and sequenceId
* Sequence IDs are numbers starting from 1. They are used to prevent replay attacks and may not be repeated.
*
* @param toAddress the destination address to send an outgoing transaction
* @param value the amount in tokens to be sent
* @param tokenContractAddress the address of the erc20 token contract
* @param expireTime the number of seconds since 1970 for which this transaction is valid
* @param sequenceId the unique sequence id obtainable from getNextSequenceId
* @param signature the result of eth.sign on the operationHash sha3(toAddress, value, tokenContractAddress, expireTime, sequenceId)
*/
function sendMultiSigToken(address toAddress, uint value, address tokenContractAddress, uint expireTime, uint sequenceId, bytes signature) onlysigner {
// Verify the other signer
var operationHash = sha3("ERC20", toAddress, value, tokenContractAddress, expireTime, sequenceId);
var otherSigner = verifyMultiSig(toAddress, operationHash, signature, expireTime, sequenceId);
ERC20Interface instance = ERC20Interface(tokenContractAddress);
if (!instance.transfer(toAddress, value)) {
throw;
}
TokenTransacted(msg.sender, otherSigner, operationHash, toAddress, value, tokenContractAddress);
}
/**
* Execute a token flush from one of the forwarder addresses. This transfer needs only a single signature and can be done by any signer
*
* @param forwarderAddress the address of the forwarder address to flush the tokens from
* @param tokenContractAddress the address of the erc20 token contract
*/
function flushForwarderTokens(address forwarderAddress, address tokenContractAddress) onlysigner {
Forwarder forwarder = Forwarder(forwarderAddress);
forwarder.flushTokens(tokenContractAddress);
}
/**
* Do common multisig verification for both eth sends and erc20token transfers
*
* @param toAddress the destination address to send an outgoing transaction
* @param operationHash the sha3 of the toAddress, value, data/tokenContractAddress and expireTime
* @param signature the tightly packed signature of r, s, and v as an array of 65 bytes (returned by eth.sign)
* @param expireTime the number of seconds since 1970 for which this transaction is valid
* @param sequenceId the unique sequence id obtainable from getNextSequenceId
* returns address of the address to send tokens or eth to
*/
function verifyMultiSig(address toAddress, bytes32 operationHash, bytes signature, uint expireTime, uint sequenceId) private returns (address) {
var otherSigner = recoverAddressFromSignature(operationHash, signature);
// Verify if we are in safe mode. In safe mode, the wallet can only send to signers
if (safeMode && !isSigner(toAddress)) {
// We are in safe mode and the toAddress is not a signer. Disallow!
throw;
}
// Verify that the transaction has not expired
if (expireTime < block.timestamp) {
// Transaction expired
throw;
}
// Try to insert the sequence ID. Will throw if the sequence id was invalid
tryInsertSequenceId(sequenceId);
if (!isSigner(otherSigner)) {
// Other signer not on this wallet or operation does not match arguments
throw;
}
if (otherSigner == msg.sender) {
// Cannot approve own transaction
throw;
}
return otherSigner;
}
/**
* Irrevocably puts contract into safe mode. When in this mode, transactions may only be sent to signing addresses.
*/
function activateSafeMode() onlysigner {
safeMode = true;
SafeModeActivated(msg.sender);
}
/**
* Determine if an address is a signer on this wallet
* @param signer address to check
* returns boolean indicating whether address is signer or not
*/
function isSigner(address signer) returns (bool) {
// Iterate through all signers on the wallet and
for (uint i = 0; i < signers.length; i++) {
if (signers[i] == signer) {
return true;
}
}
return false;
}
/**
* Gets the second signer's address using ecrecover
* @param operationHash the sha3 of the toAddress, value, data/tokenContractAddress and expireTime
* @param signature the tightly packed signature of r, s, and v as an array of 65 bytes (returned by eth.sign)
* returns address recovered from the signature
*/
function recoverAddressFromSignature(bytes32 operationHash, bytes signature) private returns (address) {
if (signature.length != 65) {
throw;
}
// We need to unpack the signature, which is given as an array of 65 bytes (from eth.sign)
bytes32 r;
bytes32 s;
uint8 v;
assembly {
r := mload(add(signature, 32))
s := mload(add(signature, 64))
v := and(mload(add(signature, 65)), 255)
}
if (v < 27) {
v += 27; // Ethereum versions are 27 or 28 as opposed to 0 or 1 which is submitted by some signing libs
}
return ecrecover(operationHash, v, r, s);
}
/**
* Verify that the sequence id has not been used before and inserts it. Throws if the sequence ID was not accepted.
* We collect a window of up to 10 recent sequence ids, and allow any sequence id that is not in the window and
* greater than the minimum element in the window.
* @param sequenceId to insert into array of stored ids
*/
function tryInsertSequenceId(uint sequenceId) onlysigner private {
// Keep a pointer to the lowest value element in the window
uint lowestValueIndex = 0;
for (uint i = 0; i < SEQUENCE_ID_WINDOW_SIZE; i++) {
if (recentSequenceIds[i] == sequenceId) {
// This sequence ID has been used before. Disallow!
throw;
}
if (recentSequenceIds[i] < recentSequenceIds[lowestValueIndex]) {
lowestValueIndex = i;
}
}
if (sequenceId < recentSequenceIds[lowestValueIndex]) {
// The sequence ID being used is lower than the lowest value in the window
// so we cannot accept it as it may have been used before
throw;
}
if (sequenceId > (recentSequenceIds[lowestValueIndex] + 10000)) {
// Block sequence IDs which are much higher than the lowest value
// This prevents people blocking the contract by using very large sequence IDs quickly
throw;
}
recentSequenceIds[lowestValueIndex] = sequenceId;
}
/**
* Gets the next available sequence ID for signing when using executeAndConfirm
* returns the sequenceId one higher than the highest currently stored
*/
function getNextSequenceId() returns (uint) {
uint highestSequenceId = 0;
for (uint i = 0; i < SEQUENCE_ID_WINDOW_SIZE; i++) {
if (recentSequenceIds[i] > highestSequenceId) {
highestSequenceId = recentSequenceIds[i];
}
}
return highestSequenceId + 1;
}
}