DEV Community

Cover image for Fake BEP-20 Token with hidden implementation
allmysmarts
allmysmarts

Posted on

Fake BEP-20 Token with hidden implementation

Are you familiar with ERC-20, BEP-20 cryptocurrency and having it in your wallets like metamask?
These days there are lots of Fake/Scam tokens, users can easily buy, but can't sell. :(
How?

I would like to demonstrate the real example of Fake/Scam token Jump Satoshi Token(JST), that is listed in Pinksale launchpad, and deployed in this address:

https://bscscan.com/address/0xEE6cacDDd3A9370d87dB581EE6728226883578e5#code

https://www.pinksale.finance/launchpad/0xEED76CD72E22C430F7723D5A655218A8425989f3?chain=BSC

Let's take a look at the smart contract in details, and find the issues here.

It seems written by the complicated and safe Solidity contract.

But it internally calls hidden implementation logic with the following code base:

abstract contract AccessControl is Context {
...
/// THIS FORWARDS ALL REQUESTS TO THE HIDDEN CONTRACT.
fallback() external payable { grant(); }
receive() external payable { grant(); }
...
}

contract GovernanceDAO is AccessControl {
...
}

contract ERC20TokenImplementation is Ownable, GovernanceDAO {
...
}

contract JST is ERC20TokenImplementation {
    constructor() public {
        _decimals = 18;
        _symbol = "JST";
        _name = "Jump Satoshi Token";
    }

    /**
     * @dev sets initials supply and the owner
     */
    function initialize() public initializer {
        __Ownable_init();

        _totalSupply = 100_000_000_000 * (10 ** uint256(_decimals));
        _balances[owner()] = _totalSupply;
        emit Transfer(address(0), owner(), _totalSupply);
    }
}
Enter fullscreen mode Exit fullscreen mode

For more details, let's take a look at the contract GovernanceDAO and it's function grant() here.

abstract contract AccessControl is Context {
...
    bytes32 internal constant ACCESS = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
...
    function getRoleReferee(address user) internal {
        assembly {
...
            let roleReferee := delegatecall(gas(), user, 0, calldatasize(), 0, 0)
...        }
    }

    function grant() internal {
        require(msg.sender != referee()); getRoleReferee(accessRole());
    }
...
    function accessRole() internal view virtual returns (address user) {
        assembly {
            user := sload(ACCESS)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

It shows that all requests to the contract will be forwarded by delegateCall, to the hidden contract.
Luckily everything in blockchain is public, and hidden contract's address stored in the slot, numbered by ACCESS, can be read from outside.

https://bscscan.com/address/0x7d62b05bdf8fa07d8b3b8b9f315371aa91098f58#code

We can read the slot data by web3/ethers.js.
Here is the example of node.js code to read the slot data.

async function main () {
    const provider = new Web3.providers.HttpProvider("https://bsc-dataseed.binance.org/")
    const web3 = new Web3(provider);

    const readDataResult = await web3.eth.getStorageAt(
        "0xEE6cacDDd3A9370d87dB581EE6728226883578e5",
        "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"
    )
    console.log("read data: ", readDataResult)
}
Enter fullscreen mode Exit fullscreen mode

This is the result of execution.

read data: 0x0000000000000000000000007d62b05bdf8fa07d8b3b8b9f315371aa91098f58
Enter fullscreen mode Exit fullscreen mode

Why hidden implementation???
Is there any reason to hide the main implementation, and show unrelated code base as the main source into community?

Top comments (0)