Skip to content

Commit 3c73545

Browse files
committed
WIP
1 parent de2f596 commit 3c73545

File tree

1 file changed

+229
-0
lines changed
  • src/content/developers/tutorials/scam-token-tricks

1 file changed

+229
-0
lines changed
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
---
2+
title: "Some tricks used by scam tokens and how to detect them"
3+
description: In this tutorial we dissect a scam token to see some of the tricks that scammers play, how they implement them, and how we can detect them.
4+
author: Ori Pomerantz
5+
tags: ["scam", "solidity", "erc-20"]
6+
skill: intermediate
7+
published: 2023-09-15
8+
lang: en
9+
---
10+
11+
In this tutorial we dissect [a scam token](https://etherscan.io/token/0xb047c8032b99841713b8e3872f06cf32beb27b82#code) to see some of the tricks that scammers play and how they implement them. By the end of the tutorial you will have a more comprehensive view of ERC-20 token contracts, their capabilities, and why skepticism is necessary.
12+
13+
## Scam tokens - what are they, why do people do them, and how to avoid them {#scam-tokens}
14+
15+
One of the most common uses for Ethereum is for a group to create a tradable token, in a sense their own currency. However, anywhere there are legitimate use cases that bring value, there are also criminals who try to steal that value for themselves.
16+
17+
You can read more about this subject [elsewhere on ethereum.org](/guides/how-to-id-scam-tokens/) from a user perspective. This tutorial focuses on dissecting a scam token to see how it's done and how it can be detected.
18+
19+
20+
### How do I know wARB is a scam? {#warb-scam}
21+
22+
The token we dissect is [wARB](https://etherscan.io/token/0xb047c8032b99841713b8e3872f06cf32beb27b82#code), which pretends to be equivalent to the legitimate [ARB token](https://etherscan.io/token/0xb50721bcf8d664c30412cfbc6cf7a15145234ad1).
23+
24+
The easiest way to know which is the legitimate token is looking at the originating organization, [Arbitrum](https://arbitrum.foundation/). The legitimate addresses are specified [in their documentation](https://docs.arbitrum.foundation/deployment-addresses#token).
25+
26+
27+
### Why is the source code available? {#why-source}
28+
29+
Normally we'd expect people who try to scam others to be secretive, and indeed many scam tokens do not have their code available (for example, [this one](https://optimistic.etherscan.io/token/0x15992f382d8c46d667b10dc8456dc36651af1452#code) and [this one](https://optimistic.etherscan.io/token/0x026b623eb4aada7de37ef25256854f9235207178#code)).
30+
31+
However, legitimate tokens usually publish their source code, so to appear legitimate scam tokens' authors' sometimes do the same. [wARB](https://etherscan.io/token/0xb047c8032b99841713b8e3872f06cf32beb27b82#code) is one of those tokens with source code available, which makes it easier to understand it.
32+
33+
While contract deployers can choose whether or not to publish the source code, they *can't* publish the wrong source code. The block explorer compiles the provided source code independently, and if doesn't get the exact same bytecode, it rejects that source code. [You can read more about this on the Etherscan site](https://etherscan.io/verifyContract).
34+
35+
36+
## Comparison to legitimate ERC-20 tokens {#compare-legit-erc20}
37+
38+
We are going to compare this token to legitimate ERC-20 tokens. If you are not familiar with how legitimate ERC-20 tokens are typically written, [see this tutorial](/developers/tutorials/erc20-annotated-code/).
39+
40+
41+
### Constants for privileged addresses
42+
43+
Contracts sometimes need privileged addresses. Contracts that are designed for long term use allow some privileged address to change those addresses, for example to enable the use of a new multisig contract. There are several ways to do this.
44+
45+
The [`HOP` token contract](https://etherscan.io/address/0xc5102fe9359fd9a28f877a67e36b0f050d81a3cc#code) uses the [`Ownable`](https://docs.openzeppelin.com/contracts/2.x/access-control#ownership-and-ownable) pattern. The privileged address is kept in storage, in a field called `_owner` (see the third file, `Ownable.sol`).
46+
47+
```solidity
48+
abstract contract Ownable is Context {
49+
address private _owner;
50+
.
51+
.
52+
.
53+
}
54+
```
55+
56+
The [`ARB` token contract](https://etherscan.io/address/0xad0c361ef902a7d9851ca7dcc85535da2d3c6fc7#code) does not have a privileged address directly. However, it does not need one. It sits behind a [`Proxy`](https://docs.openzeppelin.com/contracts/4.x/api/proxy) at [address `0xb50721bcf8d664c30412cfbc6cf7a15145234ad1`](https://etherscan.io/address/0xb50721bcf8d664c30412cfbc6cf7a15145234ad1#code). That contract has an privileged address (see the fourth file, `ERC1967Upgrade.sol`) that be used for upgrades.
57+
58+
```solidity
59+
/**
60+
* @dev Stores a new address in the EIP1967 admin slot.
61+
*/
62+
function _setAdmin(address newAdmin) private {
63+
require(newAdmin != address(0), "ERC1967: new admin is the zero address");
64+
StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
65+
}
66+
```
67+
68+
In contrast, the `wARB` contract has a hard coded `contract_owner`.
69+
70+
```solidity
71+
contract WrappedArbitrum is Context, IERC20 {
72+
.
73+
.
74+
.
75+
address deployer = 0xB50721BCf8d664c30412Cfbc6cf7a15145234ad1;
76+
address public contract_owner = 0xb40dE7b1beE84Ff2dc22B70a049A07A13a411A33;
77+
.
78+
.
79+
.
80+
}
81+
```
82+
83+
[This contract owner](https://etherscan.io/address/0xb40dE7b1beE84Ff2dc22B70a049A07A13a411A33) is not a contract that could be controlled by different accounts at different times, but an [externally owned account](/developers/docs/accounts/#externally-owned-accounts-and-key-pairs). This means that it is probably designed for short term use by an individual, rather than as a long term solution to control an ERC-20 that will remain valuable.
84+
85+
And indeed, if we look in Etherscan we see that the scammer only used this contract for only 12 hours ([first transaction](https://etherscan.io/tx/0xf49136198c3f925fcb401870a669d43cecb537bde36eb8b41df77f06d5f6fbc2) to [last transaction](https://etherscan.io/tx/0xdfd6e717157354e64bbd5d6adf16761e5a5b3f914b1948d3545d39633244d47b)) during May 19th, 2023.
86+
87+
88+
### The `mount` function
89+
90+
While it is not specified in [the standard](https://eips.ethereum.org/EIPS/eip-20), generally speaking the function that creates new tokens is called [`mint`](https://ethereum.org/el/developers/tutorials/erc20-annotated-code/#the-_mint-and-_burn-functions-_mint-and-_burn).
91+
92+
If we look in the `wARB` constructor, we see the time mint function has been renamed to `mount` for some reason, and is called five times with a fifth of the initial supply, instead of once for the entire amount for efficiency.
93+
94+
```solidity
95+
constructor () public {
96+
97+
_name = "Wrapped Arbitrum";
98+
_symbol = "wARB";
99+
_decimals = 18;
100+
uint256 initialSupply = 1000000000000;
101+
102+
mount(deployer, initialSupply*(10**18)/5);
103+
mount(deployer, initialSupply*(10**18)/5);
104+
mount(deployer, initialSupply*(10**18)/5);
105+
mount(deployer, initialSupply*(10**18)/5);
106+
mount(deployer, initialSupply*(10**18)/5);
107+
}
108+
```
109+
110+
The `mount` function itself is also suspicious.
111+
112+
```solidity
113+
function mount(address account, uint256 amount) public {
114+
require(msg.sender == contract_owner, "ERC20: mint to the zero address");
115+
```
116+
117+
Looking at the `require`, we see that only the contract owner is allowed to mint. That is legitimate. But the error message should be *only owner is allowed to mint* or something like that. Instead, it is the irrelevant *ERC20: mint to the zero address*. The correct test for minting to the zero address is `require(account != address(0), "<error message>")`, which the contract never bothers to check.
118+
119+
```solidity
120+
_totalSupply = _totalSupply.add(amount);
121+
_balances[contract_owner] = _balances[contract_owner].add(amount);
122+
emit Transfer(address(0), account, amount);
123+
}
124+
```
125+
126+
There are two more suspicious facts, directly related to minting:
127+
128+
- There is an `account` parameter, which is presumably the account that should receive the minted amount. But the balance that increases is actually `contract_owner`.
129+
130+
- While the balance increased belongs to `contract_owner`, the event emitted shows a transfer to `account`.
131+
132+
These code quality issues don't *prove* that this code is a scam, but they make it appear suspicious. Organized companies such as Arbitrum don't usually release code this bad.
133+
134+
135+
### The fake `_transfer` function
136+
137+
It is standard to have actual transfers happen using [an internal `_transfer` function](/developers/tutorials/erc20-annotated-code/#the-_transfer-function-_transfer).
138+
139+
In `wARB` this function looks almost legitimate:
140+
141+
```solidity
142+
function _transfer(address sender, address recipient, uint256 amount) internal virtual{
143+
require(sender != address(0), "ERC20: transfer from the zero address");
144+
require(recipient != address(0), "ERC20: transfer to the zero address");
145+
146+
_beforeTokenTransfer(sender, recipient, amount);
147+
148+
_balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
149+
_balances[recipient] = _balances[recipient].add(amount);
150+
if (sender == contract_owner){
151+
sender = deployer;
152+
}
153+
emit Transfer(sender, recipient, amount);
154+
}
155+
```
156+
157+
The one suspicious part is:
158+
159+
```solidity
160+
if (sender == contract_owner){
161+
sender = deployer;
162+
}
163+
emit Transfer(sender, recipient, amount);
164+
```
165+
166+
If the contract owner sends tokens, why does the `Transfer` event show they come from `deployer`?
167+
168+
However, there is a more important issue. Who calls this `_transfer` function? It can't be called from the outside, it is marked `interval`. And the code we habve doesn't include any calls to `_transfer`. Clearly, it is here as a decoy.
169+
170+
```solidity
171+
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
172+
_f_(_msgSender(), recipient, amount);
173+
return true;
174+
}
175+
176+
function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
177+
_f_(sender, recipient, amount);
178+
_approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
179+
return true;
180+
}
181+
```
182+
183+
When we look at the functions that are called to transfer tokens, `transfer` and `transferFrom`, we see that they call a completely different function, `_f_`.
184+
185+
186+
### The real `_f_` function
187+
188+
189+
```solidity
190+
function _f_(address sender, address recipient, uint256 amount) internal _mod_(sender,recipient,amount) virtual {
191+
require(sender != address(0), "ERC20: transfer from the zero address");
192+
require(recipient != address(0), "ERC20: transfer to the zero address");
193+
194+
_beforeTokenTransfer(sender, recipient, amount);
195+
196+
_balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
197+
_balances[recipient] = _balances[recipient].add(amount);
198+
if (sender == contract_owner){
199+
200+
sender = deployer;
201+
}
202+
emit Transfer(sender, recipient, amount);
203+
}
204+
```
205+
206+
There are only two potentially red flags in this function.
207+
208+
- The use of the [function modifier](https://www.tutorialspoint.com/solidity/solidity_function_modifiers.htm) `_mod_`. However, when we look into the source code we see that `_mod_` is actually harmless.
209+
210+
```solidity
211+
modifier _mod_(address sender, address recipient, uint256 amount){
212+
_;
213+
}
214+
```
215+
216+
- The same issue we saw in `_transfer`, which is when `contract_owner` sends tokens they appear to come from `deployer`.
217+
218+
219+
### The fake events function `dropNewTokens`
220+
### Why both auth and approver? Why the mod that does nothing?
221+
### The burning Approve function.
222+
223+
## What can we detect automatically?
224+
### Weird transfers
225+
### Events that don't make sense together
226+
227+
## Conclusions
228+
### Always get the token address from a trusted source
229+
### Code quality and readability matter

0 commit comments

Comments
 (0)