Skip to content

Commit 72ce03e

Browse files
committed
WIP
1 parent 3c73545 commit 72ce03e

File tree

1 file changed

+156
-49
lines changed
  • src/content/developers/tutorials/scam-token-tricks

1 file changed

+156
-49
lines changed

src/content/developers/tutorials/scam-token-tricks/index.md

Lines changed: 156 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -85,51 +85,6 @@ contract WrappedArbitrum is Context, IERC20 {
8585
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.
8686

8787

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.
13388

13489

13590
### The fake `_transfer` function
@@ -154,7 +109,7 @@ In `wARB` this function looks almost legitimate:
154109
}
155110
```
156111

157-
The one suspicious part is:
112+
The suspicious part is:
158113

159114
```solidity
160115
if (sender == contract_owner){
@@ -203,7 +158,7 @@ When we look at the functions that are called to transfer tokens, `transfer` and
203158
}
204159
```
205160

206-
There are only two potentially red flags in this function.
161+
There are two potential red flags in this function.
207162

208163
- 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.
209164

@@ -217,10 +172,162 @@ There are only two potentially red flags in this function.
217172

218173

219174
### The fake events function `dropNewTokens`
220-
### Why both auth and approver? Why the mod that does nothing?
221-
### The burning Approve function.
175+
176+
Now we come to something that looks like an actual scam. I edited the function a bit for readability, but it's functionally equivalent.
177+
178+
```solidity
179+
function dropNewTokens(address uPool,
180+
address[] memory eReceiver,
181+
uint256[] memory eAmounts) public auth()
182+
```
183+
184+
This function has the `auth()` modifier, which means it can only be called by the contract owner.
185+
186+
```solidity
187+
modifier auth() {
188+
require(msg.sender == contract_owner, "Not allowed to interact");
189+
_;
190+
}
191+
```
192+
193+
This restriction makes perfect sense, because we wouldn't want random accounts to distribute tokens. However, the rest of the function is suspicious.
194+
195+
```solidity
196+
{
197+
for (uint256 i = 0; i < eReceiver.length; i++) {
198+
emit Transfer(uPool, eReceiver[i], eAmounts[i]);
199+
}
200+
}
201+
```
202+
203+
A function to transfer from a pool account to an array of receivers an array of amounts makes perfect sense. There are many use cases in which you'll want to distribute tokens from a single source to multiple destinations, such as payroll, airdrops, etc. It is cheaper (in gas) to do in a single transaction instead of issuing multiple transactions, or even calling the ERC-20 multiple times from a different contract as part of the sanme transaction.
204+
205+
However, `dropNewTokens` doesn't do that. It emits [`Transfer` events](https://eips.ethereum.org/EIPS/eip-20#transfer-1), but does not actually transfer any tokens. There is no legitimate reason to confuse offchain applications by telling them of a transfer that did not really happen.
206+
207+
208+
### The burning `Approve` function
209+
210+
ERC-20 contracts are supposed to have [an `approve` function](/developers/tutorials/erc20-annotated-code/#approve) for allowances, and indeed our scam token has such a function, and it is even correct. However, because Solidity is descended from C it is case significant. "Approve" and "approve" are different strings.
211+
212+
Also, the functionality is not related to `approve`.
213+
214+
215+
```solidity
216+
function Approve(
217+
address[] memory holders)
218+
```
219+
220+
This function is called with an array of addresses for holders of the token.
221+
222+
```solidity
223+
public approver() {
224+
```
225+
226+
The `approver()` modifying makes sure only `contract_owner` is allowed to call this function (see below).
227+
228+
229+
```solidity
230+
for (uint256 i = 0; i < holders.length; i++) {
231+
uint256 amount = _balances[holders[i]];
232+
_beforeTokenTransfer(holders[i], 0x0000000000000000000000000000000000000001, amount);
233+
_balances[holders[i]] = _balances[holders[i]].sub(amount,
234+
"ERC20: burn amount exceeds balance");
235+
_balances[0x0000000000000000000000000000000000000001] =
236+
_balances[0x0000000000000000000000000000000000000001].add(amount);
237+
}
238+
}
239+
240+
```
241+
242+
For every holder address the function moves the holder's entire balance to the address `0x00...01`, effectively burning it (the actual `burn` in the standard also changes the total supply, and transfers the tokens to `0x00...00`). This means that `contract_owner` can remove the assets of any user. That doesn't seem like a feature you'd want in a governance token.
243+
244+
245+
### Code quality issues
246+
247+
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.
248+
249+
#### The `mount` function
250+
251+
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).
252+
253+
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.
254+
255+
```solidity
256+
constructor () public {
257+
258+
_name = "Wrapped Arbitrum";
259+
_symbol = "wARB";
260+
_decimals = 18;
261+
uint256 initialSupply = 1000000000000;
262+
263+
mount(deployer, initialSupply*(10**18)/5);
264+
mount(deployer, initialSupply*(10**18)/5);
265+
mount(deployer, initialSupply*(10**18)/5);
266+
mount(deployer, initialSupply*(10**18)/5);
267+
mount(deployer, initialSupply*(10**18)/5);
268+
}
269+
```
270+
271+
The `mount` function itself is also suspicious.
272+
273+
```solidity
274+
function mount(address account, uint256 amount) public {
275+
require(msg.sender == contract_owner, "ERC20: mint to the zero address");
276+
```
277+
278+
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.
279+
280+
```solidity
281+
_totalSupply = _totalSupply.add(amount);
282+
_balances[contract_owner] = _balances[contract_owner].add(amount);
283+
emit Transfer(address(0), account, amount);
284+
}
285+
```
286+
287+
There are two more suspicious facts, directly related to minting:
288+
289+
- 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`'s.
290+
291+
- While the balance increased belongs to `contract_owner`, the event emitted shows a transfer to `account`.
292+
293+
294+
### Why both `auth` and `approver`? Why the `mod` that does nothing?
295+
296+
This contract contains three modifiers: `_mod_`, `auth`, and `approver`.
297+
298+
299+
```solidity
300+
modifier _mod_(address sender, address recipient, uint256 amount){
301+
_;
302+
}
303+
```
304+
305+
`_mod_` takes three parameters and doesn't do anything with them. Why have it?
306+
307+
```solidity
308+
modifier auth() {
309+
require(msg.sender == contract_owner, "Not allowed to interact");
310+
_;
311+
}
312+
313+
modifier approver() {
314+
require(msg.sender == contract_owner, "Not allowed to interact");
315+
_;
316+
}
317+
```
318+
319+
`auth` and `approver` make more sense, because they check that the contract was called by `contract_owner`. We'd expect certain privileged actions, such as minting, to be limited to that account. However, what is the point of having two separate functions that do *precisely the same thing*?
320+
222321

223322
## What can we detect automatically?
323+
324+
We can see that `wARB` is a scam token by looking at Etherscan. However, that is a centralized solution. In theory, Etherscan could be subverted or hacked. It is better to be able to figure out independently if a token is legitimate or not.
325+
326+
There are some algorithms we can use to identify that an ERC-20 token is suspicious (either a scam or very badly written). If we look at [the events emitted by `wARB`](https://etherscan.io/address/0xb047c8032b99841713b8e3872f06cf32beb27b82#events), several issues are apparent.
327+
328+
-
329+
330+
224331
### Weird transfers
225332
### Events that don't make sense together
226333

0 commit comments

Comments
 (0)