Skip to content

Commit 35e266c

Browse files
authored
Merge pull request #4 from streamr-dev/audit2-fixes
explicit tests for transferAndCall
2 parents 6af417b + c1d22f0 commit 35e266c

File tree

6 files changed

+76
-6
lines changed

6 files changed

+76
-6
lines changed

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,17 @@ Some of the following features are inherited from [OpenZeppelin contracts](https
2525
| Minting | mint | contracts/DATAv2.sol |
2626
| Burning | burn, burnFrom | @openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol |
2727

28+
## Minting
29+
30+
DATAv2 has an admin role (controlled by Streamr) that can add and remove minters, who can mint tokens tokens at will. The admin and minters will be contracts that are not controlled by a single wallet.
31+
32+
The whole old DATA supply is minted as part of migration, and new tokens may be minted later as decided by the community process in the form of [Streamr Improvement Proposals](https://snapshot.org/#/streamr.eth), see e.g. [SIP-6](https://snapshot.org/#/streamr.eth/proposal/QmU383LMPAHdzMevcxY6UzyL5vkBaNHQhCcp2WbXw5kXS1).
33+
34+
# ERC677 functionality
35+
36+
DATAv2 follows the convention of [LINK](https://etherscan.io/address/0x514910771af9ca656af840dff83e8264ecf986ca#code) and other tokens with regard to the callback function `onTokenTransfer` invoked by `transferAndCall`: DATAv2 does not check the bool return value of `onTokenTransfer`. Instead `onTokenTransfer` should revert if the transfer is to be rejected by the recipient contract. If the recipient is a contract, it must implement `onTokenTransfer` or else `transferAndCall` reverts.
37+
38+
2839
## Migration process
2940

3041
1. We deploy the DATAv2 and DataTokenMigrator contracts

contracts/MockRecipient.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//SPDX-License-Identifier: Unlicense
1+
// SPDX-License-Identifier: MIT
22
pragma solidity 0.8.6;
33

44
import "hardhat/console.sol";
@@ -16,5 +16,6 @@ contract MockRecipient is IERC677Receiver {
1616
console.log("Amount", _value);
1717
console.log("With data", string(_data));
1818
txCount += 1;
19+
require(keccak256(_data) != keccak256("err")); // for testing: revert if passed "err"
1920
}
2021
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.6;
3+
4+
/** Just a blank contract that doesnt implement onTokensTransferred */
5+
contract MockRecipientNotERC677Receiver {
6+
uint public x = 1;
7+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.6;
3+
4+
import "hardhat/console.sol";
5+
6+
/**
7+
* ERC677 recipient that returns false for error instead of reverting
8+
* The return value gets ignored, and transfer proceeds succesfully. This is by design.
9+
*/
10+
contract MockRecipientReturnBool {
11+
uint public txCount;
12+
13+
function onTokenTransfer(
14+
address _sender,
15+
uint256 _value,
16+
bytes calldata _data
17+
) external returns (bool) {
18+
console.log("Received from", _sender);
19+
console.log("Amount", _value);
20+
console.log("With data", string(_data));
21+
txCount += 1;
22+
bool retval = keccak256(_data) != keccak256("err"); // for testing: return false if passed "err"
23+
console.log("retval", retval);
24+
return retval;
25+
}
26+
}

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/DATAv2-test.js

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ const { id } = require("@ethersproject/hash")
33
const { expect } = require("chai")
44
const { ethers } = require("hardhat")
55

6+
// "err" as bytes, induces a simulated error in MockRecipient.sol and MockRecipientReturnBool.sol
7+
const errData = "0x657272"
8+
69
describe("DATAv2", () => {
710
it("transferAndCall triggers ERC677 callback", async () => {
811
const [signer, minter] = await ethers.getSigners()
@@ -11,18 +14,40 @@ describe("DATAv2", () => {
1114
const recipient = await MockRecipient.deploy()
1215
await recipient.deployed()
1316

17+
const MockRecipientNotERC677Receiver = await ethers.getContractFactory("MockRecipientNotERC677Receiver")
18+
const nonReceiverRecipient = await MockRecipientNotERC677Receiver.deploy()
19+
await nonReceiverRecipient.deployed()
20+
21+
const MockRecipientReturnBool = await ethers.getContractFactory("MockRecipientReturnBool")
22+
const returnBoolRecipient = await MockRecipientReturnBool.deploy()
23+
await returnBoolRecipient.deployed()
24+
1425
const DATAv2 = await ethers.getContractFactory("DATAv2")
1526
const token = await DATAv2.deploy()
1627
await token.deployed()
1728

1829
await expect(token.grantRole(id("MINTER_ROLE"), minter.address)).to.emit(token, "RoleGranted")
19-
await expect(token.connect(minter).mint(signer.address, parseEther("1"))).to.emit(token, "Transfer(address,address,uint256)")
30+
await expect(token.connect(minter).mint(signer.address, parseEther("10"))).to.emit(token, "Transfer(address,address,uint256)")
2031

21-
const before = await recipient.txCount()
32+
// revert in callback => should revert transferAndCall
33+
await expect(token.transferAndCall(recipient.address, parseEther("1"), errData)).to.be.reverted
34+
35+
// no callback => should revert transferAndCall
36+
await expect(token.transferAndCall(nonReceiverRecipient.address, parseEther("1"), "0x")).to.be.reverted
37+
38+
// contract that implements ERC677Receiver executes the callback
39+
const txsBefore = await recipient.txCount()
2240
await token.transferAndCall(recipient.address, parseEther("1"), "0x6c6f6c")
23-
const after = await recipient.txCount()
41+
const txsAfter = await recipient.txCount()
42+
43+
// callback returns true or false but doesn't revert => should NOT revert
44+
const txsBeforeBool = await returnBoolRecipient.txCount()
45+
await token.transferAndCall(returnBoolRecipient.address, parseEther("1"), errData)
46+
await token.transferAndCall(returnBoolRecipient.address, parseEther("1"), "0x")
47+
const txsAfterBool = await returnBoolRecipient.txCount()
2448

25-
expect(after).to.equal(before.add(1))
49+
expect(txsAfter).to.equal(txsBefore.add(1))
50+
expect(txsAfterBool).to.equal(txsBeforeBool.add(2))
2651
})
2752

2853
it("transferAndCall just does normal transfer for non-contract accounts", async () => {

0 commit comments

Comments
 (0)