Skip to content

Instantly share code, notes, and snippets.

@radfish
Last active September 9, 2020 08:58
Show Gist options
  • Save radfish/60dc2464ab469389c70e657720cd684d to your computer and use it in GitHub Desktop.
Save radfish/60dc2464ab469389c70e657720cd684d to your computer and use it in GitHub Desktop.
Dissambled + reverse engineered code for Ethereum contract for TimeMiner coin
This is some notes after inspecting the code of the TimeMiner
contract on Ethereum, as reconstructed from disassembly and
from a fragment of code posted officially. The original contract
code was not open sourced by the authors as of this writing.
Coin: https://www.timeminer.site/
Officially posted screenshot with partial code of the contract:
https://i.imgur.com/iFdbLaH.png
Contract disassebly:
https://etherscan.io/bytecode-decompiler?a=0xA54C67bd320Da4F9725a6f585b7635a0c09B122e
1. The contract maintains token count for each holding address
in '.balance' field. This field for a given address is updated
upon transacting from or to that address, but the update changes
the amount only every hour (accrual interval of "time" interest).
2. Withdrawal of tokens is protected by an '.allowance' field,
which sets the limit of how many tokens can be moved from the
owner address to a given receiver address. So, nobody except
the owner can authorize a transaction to move tokens to another
address. Seems solid.
3. Admin wallet can control the "time" interest amount at will
by changing info.supplydivision or info.supplymultiply values,
which seems possible to do at will through
'setPrizeFromNewAddress' method.
An attack by the admin would be to issue three transactions
back-to-back, near a hour boundary (when totalSupply is updated):
1. set supplymultiply to some very large value (like 1000000),
2. move tokens between some random address to another address
(this will update totalSupply to a huge value, and accrue
huge interest to the two addresses between which this move
takes place.
3. set supplymultiply back to 1 (so that others don't get
the inflated interest, nor notice anything).
Thus, the admin can create arbitrary amounts of tokens at will.
4. There is a mode in the contract that moves tokens without
interest acrual: see 'stableCoinSystem'. This mode can be
activated by the admin wallet at will. Seems mostly harmless,
at most it is a switch that disables accrual and can reenable
it again, without any loss to how much "interest" accumulated
during the time that accrual was disabled. The switch is basically
delaying settlement of "interest".
5. In the dissassembled code, paranthesis appear wrong:
(10^6 * block.timestamp - info.coinCreationTime / 3600 ...
should be
(10^6 * (block.timestamp - info.coinCreationTime) / 3600 ...
I am going to assume this is a bug in the disassembler, not the
contract, since there are also issues with order of argumennts
passed to 'log' statements, however I can't be sure; and since
the effect of such an error would make the contract behavior
completely wild, which is not the case, as far as I know.
The code fragment in the official screenshot happens to not
include the code that performs this calculation, so can't be sure
what code exactly is in the contract.
6. There is a whitelist feature that allows marking some token
holder addresses as 'whitelisted' however it is not used
anywhere in the code. Not clear what this is about.
#
# Panoramix v4 Oct 2019
# Decompiled source of 0xA54C67bd320Da4F9725a6f585b7635a0c09B122e
#
# Let's make the world open source
#
const name = ''
const decimals = 6
const symbol = ''
const TOKEN_PRECISION = 10^6
def storage:
info.totalSupply is uint256 at storage 0
balanceOf is mapping of struct at storage 1
info.admin is addr at storage 2
info.supplydivision is uint256 at storage 3 # info.supplydivision
info.supplymultiply is uint256 at storage 4 # info.supplymultiply
info.stableCoinSystem is uint8 at storage 5 # info.stableCoinSystem
info.coinWorkingTime is uint256 at storage 6
info.coinCreationTime is uint256 at storage 7
preSaleInfo.isPreSaleActive is uint8 at storage 8 offset 160
preSaleInfo.admin is addr at storage 8
preSaleInfo.preSaleDivide is uint256 at storage 9
def isWhitelisted(address _address): # not payable
require calldata.size - 4 >= 32
return bool(balanceOf[addr(_address)].whitelisted)
def balanceOf(address _owner): # not payable
require calldata.size - 4 >= 32
return balanceOf[addr(_owner)].balance
def balanceOfTokenCirculation(addr _user): # not payable
require calldata.size - 4 >= 32
return balanceOf[addr(_user)].appliedTokenCirculation
def allowance(address _owner, address _spender): # not payable
require calldata.size - 4 >= 64
return balanceOf[addr(_owner)][2][addr(_spender)].allowance
#
# Regular functions
#
def _fallback() payable: # default function
revert
def infoStableSystem(): # not payable
return bool(info.stableCoinSystem), info.supplydivision, info.supplymultiply
def changePreSalePriceIfToHigh(uint256 _preSaleDivide): # not payable
require calldata.size - 4 >= 32
require caller == info.admin
preSaleInfo.preSaleDivide = _preSaleDivide
def totalSupply(): # not payable
require info.supplydivision
return ((10^6 * block.timestamp - info.coinCreationTime / 3600 / info.supplydivision * info.supplymultiply) + initial_supply)
def setStableCoinSystem(bool _stableCoinSystem): # not payable
require calldata.size - 4 >= 32
require caller == info.admin
info.stableCoinSystem = uint8(_stableCoinSystem)
def setPrizeFromNewAddress(uint256 _supplydivisiong, uint256 _supplymultiply): # not payable
require calldata.size - 4 >= 64
require caller == info.admin
info.supplydivision = _supplydivision
info.supplymultiply = _supplymultiply
def whitelist(addr _user, bool _status): # not payable
require calldata.size - 4 >= 64
require caller == info.admin
balanceOf[addr(_user)].whitelisted = uint8(_status)
log 0x5a25e09a: _status, _user
def approve(address _spender, uint256 _value): # not payable
require calldata.size - 4 >= 64
balanceOf[caller][2][addr(_spender)].allowance = _value
log Approval(
address owner=_value,
address spender=caller,
uint256 value=_spender)
return 1
def tokensToClaim(addr _param1): # not payable
require calldata.size - 4 >= 32
require info.supplydivision
require balanceOf[addr(_param1)].appliedTokenCirculation
return ((initial_supply * 10^12 *
balanceOf[addr(_param1)].balance / balanceOf[addr(_param1)].appliedTokenCirculation)+
(10^6 * block.timestamp - info.coinCreationTime / 3600 /
info.supplydivision * info.supplymultiply
* 10^12 *
* balanceOf[addr(_param1)].balance / balanceOf[addr(_param1)].appliedTokenCirculation) / 10^12)
def allInfoFor(addr _user): # not payable
require calldata.size - 4 >= 32
require info.supplydivision
require info.supplydivision
require balanceOf[addr(_user)].appliedTokenCirculation
return (totalSupply(),
balanceofTokenCirculation(_user),
balanceOf(_user),
tokensToClaim(_user))
def transfer(address _to, uint256 _tokens): # not payable
require calldata.size - 4 >= 64
_transfer(caller, _to, _tokens)
return 1
def preSaleFinished(): # not payable
require caller == info.admin
uint8(preSaleInfo.isPreSaleActive) = 0
_transfer(addr(this.address), info.admin, balanceOf[this.address].balance)
def preSale(uint256 _tokens) payable:
require calldata.size - 4 >= 32
require uint8(preSaleInfo.isPreSaleActive)
require preSaleInfo.preSaleDivide
require call.value > 5 * 10^18 * _tokens / preSaleInfo.preSaleDivide
_transfer(addr(this.address), caller, _tokens * TOKEN_PRECISION)
call addr(preSaleInfo.admin) with:
value call.value wei
gas 2300 * is_zero(value) wei
if not ext_call.success:
revert with ext_call.return_data[0 len return_data.size]
def transferFrom(address _from, address _to, uint256 _tokens): # not payable
require calldata.size - 4 >= 96
require _tokens <= balanceOf[addr(_from)][2][caller].allowance
balanceOf[addr(_from)][2][caller].allowance -= _tokens
_transfer(_from, _to, _tokens)
return 1
def _transfer(address _from, address _to, uint256 _tokens):
require balanceOf[addr(_from)].balance >= _tokens
require balanceOf[addr(_from)].balance >= 1
if not info.stableCoinSystem:
balanceOf[addr(_from)].balance -= _tokens
balanceOf[_to].balance += _tokens
log Transfer(
address from=_tokens,
address to=_from,
uint256 value=_to)
else:
if not balanceOf[addr(_to)].balance:
balanceOf[addr(_to)].appliedTokenCirculation = info.totalSupply
if info.coinWorkingTime + 3600 < block.timestamp:
info.coinWorkingTime = block.timestamp
require info.supplydivision
info.totalSupply = (10^6 * block.timestamp - info.coinCreationTime / 3600 / info.supplydivision * info.supplymultiply) + initial_supply
require balanceOf[addr(_from)].appliedTokenCirculation
balanceOf[addr(_from)].balance = 10^12 * balanceOf[addr(_from)].balance / balanceOf[addr(_from)].appliedTokenCirculation * info.totalSupply / 10^12
balanceOf[addr(_from)].appliedTokenCirculation = info.totalSupply
require balanceOf[_to].appliedTokenCirculation
balanceOf[addr(_to)].balance = 10^12 * balanceOf[_to].balance / balanceOf[_to].appliedTokenCirculation * info.totalSupply / 10^12
balanceOf[addr(_to)].appliedTokenCirculation = info.totalSupply
require balanceOf[addr(_from)].appliedTokenCirculation
balanceOf[addr(_from)].balance -= 10^12 * _tokens / balanceOf[addr(_from)].appliedTokenCirculation * info.totalSupply / 10^12
balanceOf[addr(_to)].balance += 10^12 * _tokens / balanceOf[addr(_from)].appliedTokenCirculation * info.totalSupply / 10^12
log Transfer(
address from=(10^12 * _tokens / balanceOf[addr(_from)].appliedTokenCirculation * info.totalSupply / 10^12),
address to=_from,
uint256 value=_to)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment