1

The contracts I used:

EtherDelta(DEX)

Freedomcoin(Token)

Here is my logic:

I use userA who deployed the two contracts to transfer 100Ether to userB, then userB invoke depositToken, however, as there is no transferFrom in the token's contract, it will go to fallback function:

  function() {
    throw;
  }

clearly it will throw, and that's exactly what happened in remix.

However, when I tested this in bytecode using web3py, the transaction was successful, status was 1, and when I checked balanceOf in EtherDelta, it returned the true value. I don't know what's wrong. Is this something about web3py itself?

Here is my testing code, I used ganache-cli as testnet.

import json
from tqdm import trange
from web3 import Web3, HTTPProvider, IPCProvider, WebsocketProvider

account1 = "0x827A309CCf3a92F259E2081240Da9A503495F101"
account2 = "0x52EDd47566812fb01BdAA58d58630fe95C757CdD"

w3 = Web3(HTTPProvider('http://127.0.0.1:8545',request_kwargs={'timeout':600}))

DexBinCode =""
#I'm omitting this as it's too long, but I'm sure that the deployment is successful.

dexDeploymentReceipt = w3.eth.sendTransaction({'from':account1,'value':0,'data':DexBinCode,'gas':100000000})
dexDeployHash = Web3.toHex(dexDeploymentReceipt)
truedexDeploymentReceipt = w3.eth.getTransactionReceipt(dexDeployHash)
dex_address = truedexDeploymentReceipt['contractAddress']

TokenBinCode = ""
# Same as the DEX contract

# Deploy token contract
try:
    tokenDeploymentReceipt = w3.eth.sendTransaction({'from':account1,'value':0,'data':TokenBinCode,'gas':100000000})
    tokenDeployHash = Web3.toHex(tokenDeploymentReceipt)
    truetokenDeploymentReceipt = w3.eth.getTransactionReceipt(tokenDeployHash)
    token_address = truetokenDeploymentReceipt['contractAddress'] 

except:
    print('error1')


# Transfer 100 Ether from account1 to account2
try:
    setupBinCode = "0xa9059cbb000000000000000000000000"+account2.replace('0x','')+"0000000000000000000000000000000000000000000000000000000000000064"
    setupTransferReceipt = w3.eth.sendTransaction({'from':account1,'to':token_address,'value':0,'data':setupBinCode,'gas':100000000})
    setupTransferHash = Web3.toHex(setupTransferReceipt)
    truesetupTransferReceipt = w3.eth.getTransactionReceipt(setupTransferHash)
except:
    print('error2')

# depositToken with account2
try:
    depositBinCode = "0x338b5dea000000000000000000000000"+token_address.replace('0x','')+"00000000000000000000000000000000000000000000000000000000000001f4"
    balanceBinCode = "0xf7888aec000000000000000000000000"+token_address.replace('0x','')+"000000000000000000000000"+account2.replace('0x','')
    depositReceipt = w3.eth.sendTransaction({'from':account2,'to':dex_address,'value':0,'data':depositBinCode,'gas':1000000000})
    depositHash = Web3.toHex(depositReceipt)
    trueDepositReceipt = w3.eth.getTransactionReceipt(depositHash)
    print(trueDepositReceipt)
    balance = w3.eth.call({'from':account2,'to':dex_address,'data':balanceBinCode})
    balance = Web3.toHex(balance)
    print(balance)
    if balance !='0x00000000000000000000000000000000000000000000000000000000000001f4':
        print('error6')
except:
    print('error7')       

The result of trueDepositReceipt:

AttributeDict({'transactionHash': HexBytes('0xeb4e17dc0025e2edb8c2108944b3d521b71c0d527acd97f1c2fbfddb331bd76f'), 'transactionIndex': 0, 'blockHash': HexBytes('0xc71c82f0d38fa2c8ac6dfce57068dd93b1420d23e4b6a99ecbe5d269d56298b6'), 'blockNumber': 80, 'from': '0x52EDd47566812fb01BdAA58d58630fe95C757CdD', 'to': '0x552044ff5ca9F9094754C1ed53fD84Ac182eCC9B', 'gasUsed': 50181, 'cumulativeGasUsed': 50181, 'contractAddress': None, 'logs': [AttributeDict({'logIndex': 0, 'transactionIndex': 0, 'transactionHash': HexBytes('0xeb4e17dc0025e2edb8c2108944b3d521b71c0d527acd97f1c2fbfddb331bd76f'), 'blockHash': HexBytes('0xc71c82f0d38fa2c8ac6dfce57068dd93b1420d23e4b6a99ecbe5d269d56298b6'), 'blockNumber': 80, 'address': '0x552044ff5ca9F9094754C1ed53fD84Ac182eCC9B', 'data': '0x00000000000000000000000080eff541e03059f920931ab7d93c942e2da3052700000000000000000000000052edd47566812fb01bdaa58d58630fe95c757cdd00000000000000000000000000000000000000000000000000000000000001f400000000000000000000000000000000000000000000000000000000000001f4', 'topics': [HexBytes('0xdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7')], 'type': 'mined'})], 'status': 1, 'logsBloom': HexBytes('0x00002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000002000000000000000000000000000000000')})

Please don't ask why I'm using bytecode... That's the request for my experiment. I can make sure that there are nothing wrong with the function signatures.

1 Answer 1

1

Assuming you are using the solidity version required by each contract the problem is that solc 0.4.9 used by the exchange has a bug that ignores the lack of value returned by transferFrom.

The exchange calls transferFrom from the token and it will revert if it returns false.

if (!Token(token).transferFrom(msg.sender, this, amount)) throw;

But the token doesn't implement transferFrom so the token's fallback is called instead.

function () public payable {
    create(msg.sender);
}

Call to create succeed and returns nothing. But the exchange is expecting it to returns a bool.

function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {}

The compiler's bug is that it uses the same memory buffer for both input and output parameters. So since the token returns nothing the input is evaluated as it was the output and for the exchange transferFrom returns true.

Compiling with a more recent solc version like 0.4.26 causes the call to revert because it uses the new opcodes RETURNDATASIZE to check that transferFrom has returned a bool.

2
  • Thank you a lot!!!. In Remix I compile the contracts with 0.4.26, while in web3.py, I copied the creation bytecode from etherscan.io. So I guess the solc version is what caused the problem. Plus I seems to have mistakenly thought that when transferFrom is missing, the exchange's fallback will be called. Nov 14, 2020 at 4:58
  • Can you please review this answer. The results are quite staggering IMO. Could this be a potential issue in the ecosystem, or did I get something wrong? Thanks. Nov 15, 2020 at 13:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.