Showing preview only (288K chars total). Download the full file or copy to clipboard to get everything.
Repository: ConsenSysDiligence/smart-contract-best-practices
Branch: master
Commit: f3a9741ce395
Files: 87
Total size: 264.7 KB
Directory structure:
gitextract_7c2uq4a4/
├── .editorconfig
├── .github/
│ └── workflows/
│ └── main.yml
├── .gitignore
├── CONTRIBUTING.md
├── README-vi.md
├── README-zh.md
├── README.md
├── custom/
│ ├── .icons/
│ │ ├── fontawesome/
│ │ │ └── LICENSE.txt
│ │ └── octicons/
│ │ └── LICENSE
│ ├── main.html
│ └── partials/
│ └── integrations/
│ └── analytics/
│ └── custom.html
├── docs/
│ ├── about/
│ │ ├── index.md
│ │ └── license.md
│ ├── attacks/
│ │ ├── denial-of-service.md
│ │ ├── deprecated.md
│ │ ├── force-feeding.md
│ │ ├── frontrunning.md
│ │ ├── griefing.md
│ │ ├── index.md
│ │ ├── insecure-arithmetic.md
│ │ ├── more.md
│ │ ├── oracle-manipulation.md
│ │ ├── reentrancy.md
│ │ └── timestamp-dependence.md
│ ├── bug-bounty-programs.md
│ ├── development-recommendations/
│ │ ├── deprecated/
│ │ │ ├── constructor-naming.md
│ │ │ ├── division-by-zero.md
│ │ │ └── functions-and-events.md
│ │ ├── documentation/
│ │ │ ├── contact.md
│ │ │ ├── general.md
│ │ │ ├── history.md
│ │ │ ├── known-issues.md
│ │ │ ├── procedures.md
│ │ │ ├── specification.md
│ │ │ └── status.md
│ │ ├── general/
│ │ │ ├── external-calls.md
│ │ │ ├── force-feeding.md
│ │ │ ├── negative-int.md
│ │ │ ├── participants.md
│ │ │ └── public-data.md
│ │ ├── index.md
│ │ ├── precautions/
│ │ │ ├── circuit-breakers.md
│ │ │ ├── deployment.md
│ │ │ ├── general.md
│ │ │ ├── rate-limiting.md
│ │ │ ├── safe-haven.md
│ │ │ ├── speed-bumps.md
│ │ │ └── upgradeability.md
│ │ ├── solidity-specific/
│ │ │ ├── abstract-vs-interfaces.md
│ │ │ ├── assert-require-revert.md
│ │ │ ├── complex-inheritance.md
│ │ │ ├── event-monitoring.md
│ │ │ ├── extcodesize-checks.md
│ │ │ ├── fallback-functions.md
│ │ │ ├── integer-division.md
│ │ │ ├── interface-types.md
│ │ │ ├── locking-pragmas.md
│ │ │ ├── modifiers-as-guards.md
│ │ │ ├── payability.md
│ │ │ ├── shadowing.md
│ │ │ ├── timestamp-dependence.md
│ │ │ ├── tx-origin.md
│ │ │ └── visibility.md
│ │ └── token-specific/
│ │ ├── contract-address.md
│ │ ├── frontrunning.md
│ │ ├── standardization.md
│ │ └── zero-address.md
│ ├── general-philosophy/
│ │ ├── blockchain-properties.md
│ │ ├── index.md
│ │ ├── prepare-for-failure.md
│ │ ├── rollout.md
│ │ ├── simplicity-vs-complexity.md
│ │ ├── simplicity.md
│ │ └── stay-up-to-date.md
│ ├── index.md
│ ├── security-tools/
│ │ ├── classification.md
│ │ ├── disassemblers.md
│ │ ├── index.css
│ │ ├── index.md
│ │ ├── linters-and-formatters.md
│ │ ├── static-and-dynamic-analysis.md
│ │ ├── testing.md
│ │ ├── verification.md
│ │ └── visualization.md
│ └── stylesheets/
│ └── extra.css
├── mkdocs.yml
└── requirements.txt
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
insert_final_newline = true
================================================
FILE: .github/workflows/main.yml
================================================
name: Lint, build, and deploy
on: [push, pull_request]
jobs:
build:
name: Test and deploy
runs-on: ubuntu-latest
env:
SHOULD_DEPLOY: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
steps:
- name: Clone the repository
uses: actions/checkout@v2
- name: Install Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: Install requirements
run: pip install -r requirements.txt
- name: Build the site with mkdocs
run: mkdocs build
# Disabled as Github IPs trigger a 403 on Etherscan.
# - name: Check dead links
# run: linkcheckMarkdown -r --sync docs
- name: Deploy to Github Pages
if: fromJSON(env.SHOULD_DEPLOY)
run: mkdocs gh-deploy --force
================================================
FILE: .gitignore
================================================
.DS_Store
.tmp
.vscode/
# virtualenv directory
/venv
.python-version
# build directory
/site
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to the Smart Contract Security Best Practices
Please take a moment to review this document to make the contribution
process easy and effective for everyone involved. Following these guidelines
helps to communicate that you respect the maintainers' time in managing and
developing this open-source project. They should reciprocate that
respect in addressing your issue or assessing pull requests.
## Using the Issue Tracker
The issue tracker is the preferred channel for feature requests and submitting
pull requests. There are some cases where the issue tracker should not be used:
- Please **do not** use the issue tracker for personal support requests to ask
the community for help. There are many Discord and Telegram communities
around Smart Contract development where people will be more than happy to
help.
- Please **do not** derail or troll issues. Keep the discussion on topic and
respect the opinions of others.
## Content-related Issues
Sections on attacks and security best practices should include examples and
further reading. Security-related information is bound to change over time,
however. Content-related issues should flag areas that need more explanations
or contain outdated content.
A well-written issue flagging areas of the Best Practices that need attention
are very beneficial - thank you! Some guidelines for content-related issues:
1. **Use the GitHub issue search:** Check if the issue has already been
reported. If the issue is already present, use a thumbs-up reaction to
help the developers prioritize.
2. **Check if the issue has been fixed:** Try checking out open pull requests
and development branches. The problem you are looking to flag might already
be fixed.
3. **Isolate the problem:** An excellent content-related issue shouldn't leave
others needing to chase you up for more information. Please try to be as
detailed as possible. If content needs fixing across multiple areas,
please flag one issue for each case.
## Feature Requests
Feature requests are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to _you_ to make a solid
case to convince the maintainers of the merits of an additional piece of content.
Please provide as much detail and context as possible. Keep in mind that the
Best Practices do not aim to mirror general security considerations that can
be found in the project's respective documentation. Additional content to the
repository should be original and flag potential pitfalls developers
might encounter when building Smart Contract systems.
## Pull Requests
Reasonable pull requests - patches, improvements, new content - are a fantastic help.
They should remain focused in scope and avoid containing unrelated commits.
**Please ask first** before embarking on any significant pull request (e.g.
adding new content, refactoring example code). Otherwise, you risk spending a
lot of time working on something that the maintainers might not want to merge
into the project. Please adhere to the coding conventions used throughout the
project (indentation, comments, etc.). Adhering to the following this process
is the best way to get your work merged:
1. [Fork](http://help.github.com/fork-a-repo/) the repo, clone your fork,
and configure the remotes:
```bash
# Clone your fork of the repo into the current directory
git clone https://github.com/<your-username>/<repo-name>
# Navigate to the newly cloned directory
cd <repo-name>
# Assign the original repo to a remote called "upstream"
git remote add upstream https://github.com/<upsteam-owner>/<repo-name>
```
2. If you cloned a while ago, get the latest changes from upstream:
```bash
git checkout <dev-branch>
git pull upstream <dev-branch>
```
3. Create a new topic branch (off the main project development branch) to
contain your new content, change, or fix:
```bash
git checkout -b <topic-branch-name>
```
4. Commit your changes in logical chunks. Please adhere to these [git commit
message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
or your code is unlikely to be merged into the main project. Use Git's
[interactive rebase](https://help.github.com/articles/interactive-rebase)
feature to tidy up your commits before making them public.
5. Locally merge (or rebase) the upstream development branch into your topic branch:
```bash
git pull [--rebase] upstream <dev-branch>
```
6. Push your topic branch up to your fork:
```bash
git push origin <topic-branch-name>
```
7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
with a clear title and description.
## Style Guidelines
To maintain a consistent writing style across the documents, a few
considerations need to be taken.
1. **Succinctness:** Use precise language and don't assume knowledge beyond
Solidity and Ethereum basics.
2. **Show, don't tell:** Examples speak more than a lengthy exposition.
Provide code examples where relevant and raise issues or best practices
in the code around them.
3. **Give credit:** When further reading is recommended or resources are
available that corroborate security issues, they should contain credit
and link to the original resource.
4. **Label code:** Examples containing code must be labeled as insecure or
secure where relevant. Specifically, in content regarding attacks,
vulnerable code should be preceded by `// INSECURE`.
================================================
FILE: README-vi.md
================================================
# Smart contract best practices
**Notice: this translation was generously provided by a contributor. The maintainers are not able to verify the content. Any issues or PRs to help improve it are welcome.**
Bài viết này được dịch nguyên văn từ https://consensys.github.io/smart-contract-best-practices. Để phù hợp với các diễn đạt văn phong tiếng việt, chúng tôi cố gắng diễn đạt tư tưởng của tài liệu chứ không dịch theo từng chữ một.
- [**Những lời khuyến nghị cho việc phát triển hợp đồng thông minh bằng Solidity**](#solidity-tips)
- [**Hiểu biết về các phương thức tấn công phổ biến**](#known-attacks)
- [**Áp dụng các nguyên tắc trong phát triển phần mềm để viết hợp đồng thông minh**](#eng-techniques)
- [**Lời khuyên cho việc implement mã token**](#token)
- [**Tài liệu và thủ tục**](#document)
- [**Các công cụ bảo mật**](#tools)
- [**EIPs**](#eip)
- [**Các tài nguyên tham khảo**](#resource)
<a name="solidity-tips"></a>
# Những khuyến nghị cho việc phát triển hợp đồng thông minh an toàn bằng solidity
### Lời gọi ngoài (External Calls)
#### Hãy thật cẩn trọng khi sử dụng external calls
Các message gọi đến những hợp đồng không đáng tin cậy có thể gây ra một số rủi ro hoặc lỗi không mong muốn. Các lời gọi ngoài có thể thực thi mã độc trong hợp đồng đó hoặc bất kỳ hợp đồng nào khác mà nó phụ thuộc vào. Như vậy, mọi lời gọi ngoài nên được xem là ẩn chứa rủi ro bảo mật. Trong trường hợp bất khả kháng, hãy sử dụng các đề xuất dưới đây để giảm thiểu rủi ro có thể xảy ra.
#### Đánh dấu các hợp đồng không đáng tin cậy
Khi tương tác với các lời gọi ngoài, tên các biến, phương thức và các interface nên được đặt sao cho nó thể hiện được việc tương tác với các lời gọi từ bên ngoài có an toàn hay là không ? Điều này áp dụng cho các hàm mà nó có thể được gọi từ các hợp đồng bên ngoài.
```javascript
// bad
Bank.withdraw(100); // Unclear whether trusted or untrusted
function makeWithdrawal(uint amount) { // Isn't clear that this function is potentially unsafe
Bank.withdraw(amount);
}
// good
UntrustedBank.withdraw(100); // untrusted external call
TrustedBank.withdraw(100); // external but trusted bank contract maintained by XYZ Corp
function makeUntrustedWithdrawal(uint amount) {
UntrustedBank.withdraw(amount);
}
```
#### Tránh sử dụng `transfer()` và `send()`
`.transfer()` và `.send()` giới hạn chính xác 2.300 gas lời gọi. Mục tiêu của quy định nhằm để ngăn ngừa các lỗ hổng reentrancy, nhưng điều này chỉ có ý nghĩa theo giả định rằng chi phí gas là không đổi. Gần đây EIP 1283 (được hỗ trợ từ hard fork Constantinople vào phút cuối) và EIP 1884 (dự kiến sẽ đến hard fork Istanbul) sẽ làm cho việc gửi tiền bằng hai hàm này không thực sự an toàn nữa.
Để tránh mọi thứ bị đổ bể khi chi phí gas thay đổi trong tương lai, tốt nhất nên sử dụng `.call.value (số tiền) ("")` để thay thế. Lưu ý rằng điều này không đảm bảo giảm thiểu các cuộc tấn công reentrancy, mà cần kết hợp với các biện pháp khác.
#### Sự khác nhau giữa send(), transfer() và call.value()
Khi thực hiện một giao dịch từ hợp đồng thông minh, cần phân biệt sự giống và khác giữa `someAddress.send()`, `someAddress.transfer()`, `someAddress.call().value()`.
- `someAddress.send()` và `someAddress.transfer()` được coi là an toàn để chống lại reentrancy. Chúng giới hạn 2.300 gas, chỉ đủ để ghi lại một sự kiện thay vì chạy một đoạn mã khai thác.
- x.transfer(y) tương đương với lệnh x.send (y), nó sẽ tự động revert nếu giao dịch thất bại.
- Khác với `someAddress.send()` và `someAddress.transfer()`, `someAddress.call.value(y) `không giới hạn gas cho lời gọi và do đó hacker có thể thực thi lời gọi đến một đoạn mã độc nhằm mục đích xấu. Do đó, nó không an toàn để chống lại reentrancy.
Sử dụng send() hoặc transfer() sẽ ngăn chặn reentrancy nhưng nó sẽ không thích hợp với các hợp đồng mà fallback function yêu cầu hơn 2.300 gas. Chúng ta cũng có thể sử dụng someAddress.call.value(ethAmount) .gas(gasAmount) để giới hạn lượng gas cho lời gọi một cách tùy ý.
#### Xử lý lỗi từ các lời gọi ngoài
Solidity cung cấp các phương thức gọi mức thấp (low level) : `address.call()`, `address.callcode()`, `address.delegatecall()` và `address.send()`. Các phương thức ở mức thấp này không bao giờ ném ra ngoại lệ (throw an exception), nhưng sẽ trả về false nếu lời gọi gặp phải ngoại lệ. Mặt khác, các lời gọi hợp đồng (contract calls) (ví dụ như `ExternalContract.doSomething()`) sẽ tự động ném ra một ngoại lệ và báo lỗi.
Nếu bạn lựa chọn sử dụng các phương thức gọi ở mức thấp, hãy kiểm tra xem lời gọi sẽ thất bại hay thành công, bằng cách kiểm tra giá trị trả về là `true` hay` false`.
```javascript
// bad
someAddress.send(55);
someAddress.call.value(55)(""); // this is doubly dangerous, as it will forward all remaining gas and doesn't check for result
someAddress.call.value(100)(bytes4(sha3('deposit()'))); // if deposit throws an exception, the raw call() will only return false and transaction will NOT be reverted
(bool success, ) = someAddress.call.value(55)("");
if(!success) {
// handle failure code
}
ExternalContract(someAddress).deposit.value(100)();
```
#### Ưu tiên pull hơn là push cho các lời gọi ngoài
Các lời gọi từ bên ngoài có thể thất bại một cách vô tình hoặc cố ý. Để giảm thiểu rủi ro từ các lỗi đó gây ra, tốt hơn hết là chia từng lời gọi thành các lời gọi nhỏ hơn. Điều này đặc biệt phù hợp với các giao dịch thanh toán, trong đó cho phép người dùng rút tiền sẽ tốt hơn là tự động chuyển tiền cho họ. (Điều này cũng làm giảm khả năng xảy ra sự cố với gasLimit.) và tránh việc thực hiện cùng một lúc nhiều hàm transfer() trong một giao dịch.
```javascript
// bad
contract auction {
address highestBidder;
uint highestBid;
function bid() payable {
require(msg.value >= highestBid);
if (highestBidder != address(0)) {
(bool success, ) = highestBidder.call.value(highestBid)("");
require(success); // if this call consistently fails, no one else can bid
}
highestBidder = msg.sender;
highestBid = msg.value;
}
}
// good
contract auction {
address highestBidder;
uint highestBid;
mapping(address => uint) refunds;
function bid() payable external {
require(msg.value >= highestBid);
if (highestBidder != address(0)) {
refunds[highestBidder] += highestBid; // record the refund that this user can claim
}
highestBidder = msg.sender;
highestBid = msg.value;
}
function withdrawRefund() external {
uint refund = refunds[msg.sender];
refunds[msg.sender] = 0;
(bool success, ) = msg.sender.call.value(refund)("");
require(success);
}
}
```
#### Không nên dùng delegatecall với đoạn mã không được tin cậy
Hàm `delegatecall` được sử dụng để gọi các hàm từ các hợp đồng khác như thể chúng thuộc về hợp đồng của người gọi. Do đó, người gọi có thể thay đổi trạng thái của hợp đồng được gọi đến. Điều này có thể không an toàn. Ví dụ dưới đây cho thấy cách sử dụng `delegatecall` có thể dẫn đến việc hợp đồng bị phá hủy và mất hết số dư.
```javascript
contract Destructor
{
function doWork() external
{
selfdestruct(0);
}
}
contract Worker
{
function doWork(address _internalWorker) public
{
// unsafe
_internalWorker.delegatecall(bytes4(keccak256("doWork()")));
}
}
```
Nếu `worker.doWork()` được gọi với tham số là địa chỉ của hợp đồng Destructor, hợp đồng Worker sẽ tự hủy. Bạn chỉ nên thực hiện delegate call cho các hợp đồng đáng tin cậy.
**Lưu ý**: Đừng cho rằng các hợp đồng khi được khởi tạo có số dư bằng 0. Một kẻ tấn công có thể gửi ether đến địa chỉ của hợp đồng trước khi nó được khởi tạo. [Xem vấn đề 61](https://github.com/ConsenSys/smart-contract-best-practices/issues/61) để biết thêm chi tiết.
### Ether có thể được gửi đến bất kỳ hợp đồng nào
Kẻ tấn công có thể gửi ether đến bất kỳ tài khoản nào và điều này không thể ngăn chặn được (ngay cả với fallback function với câu lệnh revert).
Kẻ tấn công có thể làm điều này bằng cách tạo ra một hợp đồng, gửi cho nó 1 wei và hàm `selfdestruct(victimAddress`), ở đây `victimAddress` là địa chỉ hợp đồng cần gửi ether vào.
### Hãy nhớ rằng Ethereum là mạng public blockchain, mọi dữ liệu trên các block đều được công khai
Nhiều ứng dụng yêu cầu dữ liệu được gửi phải ở chế độ riêng tư cho đến một lúc nào đó. Các trò chơi (ví dụ: oản tù tì) và việc đấu giá kín là hai ví dụ chính. Nếu bạn đang xây dựng một ứng dụng mà sự riêng tư là một vấn đề, hãy đảm bảo bạn tránh yêu cầu người dùng công khai thông tin quá sớm. Chiến lược tốt nhất là chia thành các giai đoạn riêng biệt: đầu tiên thì sử dụng hàm băm của các giá trị và trong giai đoạn tiếp theo thì tiết lộ các giá trị.
Ví dụ:
- Trong trò chơi oản tù tì, yêu cầu cả hai người chơi gửi giá trị băm của kéo, đá hay giấy (do người chơi quyết định), sau đó trò chơi yêu cầu cả hai người chơi gửi kết quả mình lựa chọn. Tiếp đó so sánh giá trị băm, nếu khớp thì hợp lệ, trò chơi sẽ phân thắng hòa hay thua dựa trên kết quả chọn của 2 người chơi.
- Trong phiên đấu giá kín, yêu cầu người đấu giá gửi giá trị băm mức giá mà họ chọn trong giai đoạn ban đầu (cùng với khoản tiền gửi lớn hơn giá trị giá thầu của họ), sau đó gửi giá trị đấu giá của họ trong giai đoạn thứ hai.
- Khi phát triển một ứng dụng mang tính ngẫu nhiên, thứ tự phải luôn là: (1) người chơi submit, (2) số ngẫu nhiên được tạo, (3) người chơi hoàn thành giao dịch. Phương thức mà các số ngẫu nhiên được tạo ra là cả một lĩnh vực nghiên cứu, các giải pháp tốt nhất hiện tại bao gồm việc sử dụng block header Bitcoin (được xác minh thông qua http://btcrelay.org), các cơ chế hash-commit-reveal (tức là một bên tạo ra một số, xuất bản hàm băm của nó để "cam kết" và sau đó tiết lộ giá trị sau), cùng với đó là [RANDAO](https://github.com/randao/randao). Vì Ethereum là một giao thức xác định, không có biến nào trong giao thức có thể được sử dụng như một số ngẫu nhiên không thể đoán trước. Ngoài ra, hãy lưu ý rằng thợ đào (miner) trong một chừng mực nào đó kiểm soát giá trị block.blockhash().
### Cảnh giác với khả năng một số người tham gia có thể "drop offline" và không quay lại
Ví dụ, trong trò chơi oản tù tì, một ván đấu được tiếp tục cho đến khi cả hai người chơi gửi lựa chọn của họ. Tuy nhiên, một người chơi có thể không bao giờ gửi lựa chọn của họ - thực tế, nếu một người chơi thấy động thái được tiết lộ từ người chơi khác và xác định rằng họ đã thua, họ không có lý do gì để tự gửi kết quả. Khi gặp các tình huống như vậy thì , (1) cung cấp một cách để tránh những người chơi không tham gia, có thể giới hạn thời gian và (2) xem xét thêm lợi ích bổ sung cho những người tham gia khi gửi kết quả trong tất cả các tình huống.
### Trường hợp đổi dấu số âm bé nhất
Solidity cung cấp một số kiểu dữ liệu số nguyên. Giống như trong hầu hết các ngôn ngữ lập trình khác, trong Solidity, một số nguyên N bit có thể biểu thị các giá trị từ `-2 ^ (N-1) đến 2 ^ (N-1) - 1`. Điều này có nghĩa là không có giá trị dương mà có trị tuyệt đối bằng `MIN_INT`. `- MIN_INT` sẽ được gắn bằng `MIN_INT`.
Điều này đúng với tất cả các kiểu số nguyên trong Solidity (int8, int16, ..., int256).
```javascript
contract Negation {
function negate8(int8 _i) public pure returns(int8) {
return -_i;
}
function negate16(int16 _i) public pure returns(int16) {
return -_i;
}
int8 public a = negate8(-128); // -128
int16 public b = negate16(-128); // 128
int16 public c = negate16(-32768); // -32768
}
}
```
Một cách để xử lý điều này là kiểm tra giá trị của biến trước khi đảo dấu và ném ra ngoại lệ nếu nó bằng `MIN_INT`. Một tùy chọn khác là đảm bảo rằng số âm nhất bé nhất sẽ không bao giờ đạt được bằng cách sử kiểu biến có khoảng giá trị lớn (ví dụ: int32 thay vì int16).
### Sử dụng assert(), revert() và require() đúng cách
Các hàm assert và require được sử dụng để kiểm tra các điều kiện và ném ra một ngoại lệ nếu điều kiện không được đáp ứng.
Hàm `assert` chỉ nên được sử dụng để kiểm tra các lỗi bên trong (internal error) và các biến hằng.
Hàm `require` nên được dùng để đảm bảo các điều kiện hợp lệ, chẳng hạn như biến đầu vào, biến trạng thái của hợp đồng hoặc để xác thực giá trị trả về từ các lời gọi đến hợp đồng bên ngoài.
Ví dụ dưới đây cho thấy rằng các opcode không hợp lệ không có cơ hội để thực thi: các biến đều được xác minh và nếu có sai số thì đoạn mã sẽ ném ra lỗi.
```javascript
pragma solidity ^0.5.0;
contract Sharer {
function sendHalf(address payable addr) public payable returns (uint balance) {
require(msg.value % 2 == 0, "Even value required."); //Require() can have an optional message string
uint balanceBeforeTransfer = address(this).balance;
addr.transfer(msg.value / 2);
// Since transfer throws an exception on failure and
// cannot call back here, there should be no way for us to
// still have half of the money.
assert(address(this).balance == balanceBeforeTransfer - msg.value / 2); // used for internal error checking
return address(this).balance;
}
}
```
### Chỉ sử modifier khi cần kiểm tra dữ liệu
Mã bên trong modifier được thực thi trước khi chạy mã bên trong hàm. Do đó, bất kỳ thay đổi trạng thái hoặc lời gọi ngoài nào được tạo ra bởi đoạn mã trong modifier cũng sẽ vi phạm thiết kế [Checks-Effects-Interactions](https://solidity.readthedocs.io/en/develop/security-considerations.html#use-the-checks-effects-interactions-pattern) mà chúng tôi đã đề ra. Ví dụ dưới đây, một lời gọi ngoài hợp đồng được chèn trong modifier có thể dẫn đến lỗ hổng reentrancy.
```javascript
contract Registry {
address owner;
function isVoter(address _addr) external returns(bool) {
// Code
}
}
contract Election {
Registry registry;
modifier isEligible(address _addr) {
require(registry.isVoter(_addr));
_;
}
function vote() isEligible(msg.sender) public {
// Code
}
}
```
Trong trường hợp này, hợp đồng Registry có thể bị tấn công reentracy bằng cách gọi đến Election.vote()
**Lưu ý**: Sử dụng modifier để thay các câu lệnh kiểm tra điều kiện bên trong thân của hàm. Điều này làm cho mã nguồn hợp đồng thông minh của bạn gọn nhẹ và dễ đọc hơn.
### Hãy cẩn thận với việc làm tròn kết quả trong phép chia
Tất cả các phép chia số nguyên được làm tròn bằng cách lấy số nguyên gần nhất. Nếu bạn cần độ chính xác cao hơn, hãy cân nhắc lưu trữ cả tử và mẫu số, hoặc số nhân vào một biến trung gian nào đó.
(Trong tương lai, Solidity sẽ có [fixed_point type](https://solidity.readthedocs.io/en/develop/types.html#fixed-point-numbers), điều này sẽ khiến vấn đề dễ dàng hơn.)
```javascript
// bad
uint x = 5 / 2; // Result is 2, all integer divison rounds DOWN to the nearest integer
```
Sử dụng số nhân sẽ ngăn việc làm tròn xuống, số nhân này cần được tính toán khi làm việc với x trong tương lai:
```javascript
// good
uint multiplier = 10;
uint x = (5 * multiplier) / 2;
```
Lưu trữ tử số và mẫu số có nghĩa là bạn có thể tính kết quả của tử số/mẫu số ngoài chuỗi:
```javascript
// good
uint numerator = 5;
uint denominator = 2;
```
### Abstract contract và interfaces
Cả interface và hợp đồng trừu tượng (abstract contract) đều có chung một tư tưởng là cho phép tùy chỉnh mã nguồn của các function dựa trên prototype có sẵn. Interface, được giới thiệu trong phiên bản Solidity 0.4.11, tương tự như các abstract contract nhưng interface chỉ có các prototype mà không có chứa thân hàm. Interface cũng có những hạn chế như không thể truy cập vào storage hoặc kế thừa từ các interface khác, điều này làm cho các abstract contract có ưu thế hơn một chút. Ngoài ra, điều quan trọng cần lưu ý là nếu một hợp đồng kế thừa từ một abstract contract thì các hàm sẽ được thực thi bằng cách ghi đè (overriding).
### Fallback functions
#### Giữ cho Fallback function đơn giản
[Fallback function](https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function) được thực thi khi hợp đồng được gọi bởi một message không có tham số (hoặc message đó gọi đến một hàm không tồn tại trong hợp đồng). Nếu bạn chỉ muốn nhận Ether từ fallback function bằng cách gọi `.send()` hoặc `.transfer()`, thì 2300 gas đủ để cho bạn kích hoạt một event. Nếu cần sử nhiều tính toán hơn thì có thể cấu hình lượng gas tối đa mà fallback function có thể sử dụng.
```javascript
// bad
function() payable { balances[msg.sender] += msg.value; }
// good
function deposit() payable external { balances[msg.sender] += msg.value; }
function() payable { require(msg.data.length == 0); emit LogDepositReceived(msg.sender); }
```
### Kiểm tra độ dài dữ liệu trong fallback function
Fallback function không chỉ được dùng để nhận ether gửi vào hợp đồng (không có dữ liệu trong message) mà còn dùng kh gọi hàm không có trong hợp đồng hoặc tham số không đúng. Do đó, kiểm tra độ dài data trước khi thực thi các mã trong fallback function nhằm tránh việc bị thực thi mã độc.
```javascript
// bad
function() payable { emit LogDepositReceived(msg.sender); }
// good
function() payable { require(msg.data.length == 0); emit LogDepositReceived(msg.sender); }
```
### Định nghĩa rõ ràng các hàm và các biến có thể nhận ether
Bắt đầu từ phiên bản Solidity 0.4.0, mọi hàm nhận ether phải có modifier `payable`, mặt khác, nếu lời gọi đến hàm payable có msg.value = 0 thì giao dịch sẽ bị revert ([trừ khi bị bắt buộc](https://consensys.github.io/smart-contract-best-practices/recommendations/#remember-that-ether-can-be-forcibly-sent-to-an-account)).
Nếu bạn muốn dùng chức năng chuyển tiền, hãy khai báo các biến và các tham số của hàm có dạng `address payable`. Bạn chỉ có thể sử dụng .transfer (..) và .send (..) trên `address payable` thay vì `address`. Bạn có thể sử dụng .call (..) cho cả `address payable` và `address`. Nhưng điều này không được khuyến khích.
**Lưu ý**: Modifier payable chỉ áp dụng cho các lời gọi từ bên ngoài. Nếu một hàm non-payable gọi hàm payable trên cùng một hợp đồng, thì hàm non-payable sẽ không thành công, mặc dù msg.value vẫn được đặt.
### Định nghĩa rõ ràng phạm vi truy cập của các hàm, các biến
Các hàm có bốn loại phạm vi truy cập là external, public, private, internal. . Đối với các biến, không thể định nghĩa phạm vi external. Định nghĩa đầy đủ, rõ ràng phạm vi truy cập của các biến, các hàm giúp dễ dàng nắm được được phạm vi của từng thành phần trong hợp đồng, tránh các lỗi không đáng có.
- Các hàm `external` là một phần chức năng của contract interface. Các hàm external hiểu quả hơn các hàm public khi tham số là các mảng dữ liệu lớn do hàm external sẽ tốn ít gas hơn.
- Các hàm `public` có thể được gọi từ bất cứ đâu, trong hợp đồng, hoặc từ một hợp đồng khác.
- Các hàm `internal` chỉ có thể được gọi từ bên trong hợp đồng hoặc các hợp đồng kế thừa.
- Các hàm `private` chỉ có thể được gọi từ bên trong hợp đồng.
```javascript
// bad
uint x; // the default is internal for state variables, but it should be made explicit
function buy() { // the default is public
// public code
}
// good
uint private y;
function buy() external {
// only callable externally or using this.buy()
}
function utility() public {
// callable externally, as well as internally: changing this code requires thinking about both cases.
}
function internalAction() internal {
// internal code
}
```
### Fix cứng phiên bản trình biên dịch của Solidity
Các hợp đồng nên được triển khai với cùng phiên bản trình biên dịch mà chúng đã được kiểm thử nhiều nhất. Fix cứng phiên bản trình biên dịch để tránh trường hợp được triển khai bởi một phiên bản mới hơn (vốn có thể tiềm ẩn lỗi).
```
// bad
pragma solidity ^0.4.4;
// good
pragma solidity 0.4.4;
```
### Sử dụng các sự kiện (event) để theo dõi hoạt động của hợp đồng
Cần có cách giám sát hoạt động của hợp đồng sau khi nó được triển khai. Một cách để thực hiện điều này là xem xét tất cả các giao dịch của hợp đồng, tuy nhiên điều đó là chưa đủ, vì các lời gọi (message call) giữa các hợp đồng không được ghi lại trên blockchain. Hơn nữa, nó cũng chỉ hiển thị các tham số đầu vào, không phải là những thay đổi trạng thái. Ngoài việc theo dõi hoạt động của hợp đồng ra, các sự kiện có thể được sử dụng để tương tác với giao diện người dùng.
```javascript
contract Charity {
mapping(address => uint) balances;
function donate() payable public {
balances[msg.sender] += msg.value;
}
}
contract Game {
function buyCoins() payable public {
// 5% goes to charity
charity.donate.value(msg.value / 20)();
}
}
```
Ở trên, hợp đồng Game sẽ thực hiện lời gọi (internal call) đến Charity.donate(). Giao dịch này sẽ không xuất hiện trong danh sách các giao dịch bên ngoài (external transaction) của hợp đồng Charity mà có trong danh sách các giao dịch nội bộ (internal transaction).
Sự kiện là cách thuận tiện để ghi lại một điều gì đó đã xảy ra trong hợp đồng. Các sự kiện được phát ra (emit) được lưu lại trong blockchain cùng với dữ liệu khác của hợp đồng. Đây là một cải tiến cho ví dụ ở trên, chúng ta sử dụng sự kiện để ghi lại lịch sử quyên góp của Hội từ thiện.
```javascript
contract Charity {
// define event
event LogDonate(uint _amount);
mapping(address => uint) balances;
function donate() payable public {
balances[msg.sender] += msg.value;
// emit event
emit LogDonate(msg.value);
}
}
contract Game {
function buyCoins() payable public {
// 5% goes to charity
charity.donate.value(msg.value / 20)();
}
}
```
Tất cả các giao dịch gọi hàm donate của hợp đồng Charity, dù trực tiếp hay không, sẽ hiển thị trong danh sách sự kiện của hợp đồng đó cùng với số tiền quyên góp.
### Sự phức tạp của ngôn từ (Lườm rau gắp thịt)
Khi bạn định nghĩa tên một hàm, nếu nó trùng với tên các hàm có sẵn của Solidity. Nó sẽ ghi đè (override) hàm mặc định nhưng nó sẽ gây hiểu nhầm cho người đọc với ý nghĩa của đoạn mã.
```javascript
contract PretendingToRevert {
function revert() internal constant {}
}
contract ExampleContract is PretendingToRevert {
function somethingBad() public {
revert();
}
}
```
```javascript
contract FakingItsOwnDeath {
function selfdestruct(address x) internal constant {}
}
contract SelfDestructive is FakingItsOwnDeath {
function die() public {
selfdestruct(address(0x0));
}
}
```
Ở ví dụ thứ nhất, hàm `revert()` được gọi là hàm revert của hợp đồng `PretendingToRevert` thay vì hàm `revert()` mặc định. Do đó, khi gọi hàm `someThingBad()` của Example thì nó vẫn hoạt động bình thường.
Tương tự ở ví dụ thứ hai, không có hợp đồng nào bị hủy cả khi gọi đến hàm `die()` của SelfDestructive.
### Tránh sử dụng tx.origin
tx.origin chỉ có thể là tài khoản người dùng, không thể là tài khoản hợp đồng.
msg.sender có thể là tài khoản người dùng hoặc tài khoản hợp đồng.
Ví dụ các lời gọi theo chuỗi như sau: A->B->C->D, một hàm của hợp đồng D được gọi thì msg.sender là địa chỉ của C còn tx.origin là địa chỉ của A.
```javascript
contract MyContract {
address owner;
function MyContract() public {
owner = msg.sender;
}
function sendTo(address receiver, uint amount) public {
require(tx.origin == owner);
receiver.transfer(amount);
}
}
contract AttackingContract {
MyContract myContract;
address attacker;
function AttackingContract(address myContractAddress) public {
myContract = MyContract(myContractAddress);
attacker = msg.sender;
}
function() public {
myContract.sendTo(attacker, msg.sender.balance);
}
}
```
Đoạn mã ở trên tận dụng đặc điểm của `tx.orign` để chuyển tiền từ hợp đồng `MyContract` vào tài khoản của kẻ xấu bằng cách viết hợp đồng `AttackingContract`và gọi đến hàm `sendTo` trong `MyContract`.
Khả năng trong tương lai, `tx.origin` sẽ bị loại bỏ khỏi nền tảng Ethereum. Chính nhà đồng sáng lập Ethereum Vatalik Buterin cho rằng tx.orgin không có ý nghĩa để có thể sử dụng trong hợp đồng thông minh.
Điều đáng nói là bằng cách sử dụng tx.origin, bạn sẽ hạn chế khả năng tương tác giữa các hợp đồng vì hợp đồng sử dụng tx.origin không thể được sử dụng bởi một hợp đồng khác vì tài khoản hợp đồng không thể là tx.origin.
### Nhãn thời gian (timestamp)
Có ba điều cần lưu ý khi sử dụng nhãn thời gian để thực hiện các chức năng quan trọng trong hợp đồng thông minh, đặc biệt là khi các hành động liên có quan đến việc chuyển tiền.
#### Thao tác với nhãn thời gian
Cần lưu ý rằng nhãn thời gian của một block có thể được tác động bởi thợ đào. Chúng ta cùng xem xét ví dụ sau đây:
```javascript
uint256 constant private salt = block.timestamp;
function random(uint Max) constant private returns (uint256 result){
//get the best seed for randomness
uint256 x = salt * 100/Max;
uint256 y = salt * block.number/(salt % 5) ;
uint256 seed = block.number/3 + (salt % 300) + Last_Payout + y;
uint256 h = uint256(block.blockhash(seed));
return uint256((h / x)) % Max + 1; //random number between 1 and Max
}
```
Khi hợp đồng sử dụng nhãn thời gian để tạo số ngẫu nhiên, người thợ đào thực sự có thể đóng nhãn thời gian trong vòng 15 giây khi block đang được xác thực, cho phép người thợ đào có thể tính toán trước các tùy chọn. Nhãn thời gian không phải là ngẫu nhiên và không nên được sử dụng trong bối cảnh đó.
#### Quy tắc 15 giây
Trong Yellow Paper của Ethereum không mô tả về số lượng block có thể tạo ra trong một khoảng thời gian nhất định, nhưng nó đề cập rằng mỗi nhãn thời gian của block con phải lớn hơn nhãn thời gian của block cha mẹ. Các giao thức Ethereum trên Geth và Parity đều từ chối các block với nhãn thời gian lớn hơn 15 giây so với cha mẹ nó. Do đó, một nguyên tắc nhỏ trong việc đánh giá việc sử dụng nhãn thời gian là:
**Nếu sự kiện hợp đồng bạn triển khai có thể thay đổi trong 15 giây và duy trì tính toàn vẹn, thì việc sử dụng block.timestamp là an toàn.**
#### Tránh sử dụng block.number như là nhãn thời gian
Có thể ước tính một khoảng thời gian bằng cách sử dụng thuộc tính block.number và [thời gian khối trung bình](https://etherscan.io/chart/blocktime), tuy nhiên đây không phải là cách hay vì thời gian một block mới được tạo mới (block times) có thể thay đổi (ví dụ như việc xảy ra [fork reorganisations](https://blog.ethereum.org/2015/08/08/chain-reorganisation-depth-expectations/) hoặc thay đổi [difficulty bomb](https://github.com/ethereum/EIPs/issues/649)).
### Hãy thận trọng khi sử dụng tính năng đa kế thừa
Khi sử dụng đa kế thừa trong Solidity, điều quan trọng là phải hiểu cách nó hoạt động như thế nào.
```javascript
contract Final {
uint public a;
function Final(uint f) public {
a = f;
}
}
contract B is Final {
int public fee;
function B(uint f) Final(f) public {
}
function setFee() public {
fee = 3;
}
}
contract C is Final {
int public fee;
function C(uint f) Final(f) public {
}
function setFee() public {
fee = 5;
}
}
contract A is B, C {
function A() public B(3) C(5) {
setFee();
}
}
```
Khi một hợp đồng được triển khai, trình biên dịch sẽ tuyến tính hóa sự kế thừa từ phải sang trái (sau từ khóa `is` là danh sách các hợp đồng cha mẹ được liệt kê).
#### Final \<- B \<- C \<- A
Hàm khởi tạo của hợp đồng A sẽ trả về 5, vì C là gần A nhất theo sự tuyết tính hóa từ phải qua trái.
Để biết thêm về bảo mật và kế thừa, hãy xem [bài viết này](https://pdaian.com/blog/solidity-anti-patterns-fun-with-inheritance-dag-abuse/)
Để giúp đóng góp, Github của Solidity có một [dự án](https://github.com/ethereum/solidity/projects/9#card-8027020) với tất cả các vấn đề liên quan đến thừa kế.
### Sử dụng interface thay vì address
Khi một hàm có tham số đầu vào là địa chỉ của một hợp đồng, tốt hơn là nên truyền vào interface hoặc một tham chiếu đến hợp đồng đó thay vì truyền vào địa chỉ của hợp đồng.
```javascript
contract Validator {
function validate(uint) external returns(bool);
}
contract TypeSafeAuction {
// good
function validateBet(Validator _validator, uint _value) internal returns(bool) {
bool valid = _validator.validate(_value);
return valid;
}
}
contract TypeUnsafeAuction {
// bad
function validateBet(address _addr, uint _value) internal returns(bool) {
Validator validator = Validator(_addr);
bool valid = validator.validate(_value);
return valid;
}
}
```
Những lợi ích của việc sử dụng hợp đồng `TypeSafeAuction` ở trên có thể được nhìn thấy từ ví dụ dưới đây. Nếu hàm `validateBet()` có tham số đầu vào là địa chỉ của hợp đồng, hoặc tham chiếu của một hợp đồng không phải là `TypeSafeAuction` thì trình biên dịch ném ra lỗi.
```javascript
contract NonValidator{}
contract Auction is TypeSafeAuction {
NonValidator nonValidator;
function bet(uint _value) {
bool valid = validateBet(nonValidator, _value); // TypeError: Invalid type for argument in function call.
// Invalid implicit conversion from contract NonValidator
// to contract Validator requested.
}
}
```
### Tránh sử dụng extcodesize để kiểm tra tài khoản người dùng (Externally Owned Accounts)
Modifier dưới đây có chức năng kiểm tra xem message gọi đến là tài khoản hợp đồng hay tài khoản người dùng.
```javascript
// bad
modifier isNotContract(address _a) {
uint size;
assembly {
size := extcodesize(_a)
}
require(size == 0);
_;
}
```
Ý tưởng rất đơn giản: nếu một địa chỉ có chứa mã nguồn, đó không phải là tài khoản người dùng mà là tài khoản hợp đồng. Tuy nhiên, một hợp đồng sẽ chưa bao gồm mã nguồn trong quá trình khởi tạo. Điều này có nghĩa là trong khi hàm contructor của hợp đồng đang được thực hiện, nó có thể thực hiện các lời gọi đến các hợp đồng khác với `extcodesize` trả về 0. Dưới đây là một ví dụ để làm rõ hơn.
```javascript
contract OnlyForEOA {
uint public flag;
// bad
modifier isNotContract(address _a){
uint len;
assembly { len := extcodesize(_a) }
require(len == 0);
_;
}
function setFlag(uint i) public isNotContract(msg.sender){
flag = i;
}
}
contract FakeEOA {
constructor(address _a) public {
OnlyForEOA c = OnlyForEOA(_a);
c.setFlag(1);
}
}
```
Đoạn mã trong hàm contructor của `FakeEOA` gọi đến hàm `setFlag` của `OnlyForEOA`, do hàm constructor của hợp đồng `FakeEOA` chưa được thực hiện xong, nên extcodesize của nó sẽ trả về 0 và vượt qua được bộ lọc của modifier `isNotContract` từ đó thay đổi giá trị flag trong` OnlyForEOA` một cách trái phép.
**Cảnh báo**\*:
Nếu mục tiêu của bạn là ngăn chặn các hợp đồng khác có thể gọi đến hợp đồng của bạn, thì việc dùng `extcodesize` có lẽ cũng là tương đối tốt. Một cách tiếp cận khác là kiểm tra giá trị của `(tx.origin == msg.sender)`, mặc dù điều này cũng có nhược điểm.
### Cẩn thận với phép chia cho 0 (Sodility \<0,4)
Trước phiên bản 0.4, Solidity [trả về 0](https://github.com/ethereum/solidity/issues/670) và không ném ngoại lệ khi một số được chia cho 0. Đảm bảo bạn đang chạy phiên bản solidity từ 0.4 trở lên.
### Cách đặt tên function và event (Solidity \<0.4.21)
Viết in hoa chữ đầu tiên tên của event, để ngăn ngừa rủi ro nhầm lẫn giữa các function và event. Đối với các function, luôn luôn bắt đầu bằng một chữ cái viết thường, ngoại trừ hàm khởi tạo (constructor).
<a name="known-attacks"></a>
# Hiểu về các phương thức tấn công phổ biến
Biết tấn công để biết cách phòng thủ là một trong những nguyên tắc cơ bản trong an toàn thông tin. Nội dung phần này đề cập đến các lỗ hổng, phương thức tấn công đã biết để có thể khai thác hợp đồng thông minh.
## Reentrancy
Một trong những mối nguy hiểm lớn khi gọi đến các hợp đồng bên ngoài là chúng có thể chiếm quyền điều khiển và thực hiện các thay đổi không mong muốn. Loại lỗi này có thể có nhiều biến thể và cả hai lỗi lớn dẫn vụ DAO hack nổi tiếng đều là các lỗi thuộc loại này.
### Reentrancy trên một hàm (Reentrancy on single function)
```javascript
// INSECURE
mapping (address => uint) private userBalances;
function withdrawBalance() public {
uint amountToWithdraw = userBalances[msg.sender];
(bool success, ) = msg.sender.call.value(amountToWithdraw)(""); // At this point, the caller's code is executed, and can call withdrawBalance again
require(success);
userBalances[msg.sender] = 0;
}
```
Vì số dư tài khoản của người dùng không gắn bằng 0 cho đến khi kết thúc hàm, hacker có thể lợi dụng điều này để rút tiền nhiều lần bằng cách gọi liên tục hàm withdrawBalance để ngăn cản hàm đó chạy đến câu lệnh `userBalances[msg.sender] = 0; `
> DAO (Decentralized Autonomous Organization). Mục đích mà nó hướng đến là tự động hóa các quy tắc, bộ máy điều hành của một các tổ chức, từ đó loại bỏ vai trò của tài liệu và con người trong quá trình quản lý, tạo ra một cấu trúc với sự kiểm soát phi tập trung.
>
> Vào ngày 17 tháng 6 năm 2016, DAO đã bị hack và 3,6 triệu Ether (50 triệu đô la) đã bị đánh cắp bằng cách khai thác lỗ hổng Reetrancy.
>
> Ethereum Foundation đã phát hành một bản cập nhật quan trọng để khôi phục lại trạng thái trước vụ hack. Điều này dẫn đến việc Ethereum được chia thành Ethereum Classic và Ethereum.
Trong ví dụ trên, cách giảm thiểu tối đa rủi ro là sử dụng hàm `send()` thay vì hàm `call.value ()`. Điều này sẽ hạn chế bất kỳ mã bên ngoài nào được thực thi.
Tuy nhiên, nếu bạn không thể tránh các lời gọi ngoài, thì cách đơn giản để ngăn chặn cuộc tấn công này là đảm bảo bạn không gọi thực hiện lời gọingoài trước khi các đoạn mã internal được thực hiện xong.
```javascript
mapping (address => uint) private userBalances;
function withdrawBalance() public {
uint amountToWithdraw = userBalances[msg.sender];
userBalances[msg.sender] = 0;
(bool success, ) = msg.sender.call.value(amountToWithdraw)(""); // The user's balance is already 0, so future invocations won't withdraw anything
require(success);
}
```
### Reentrancy liên hàm (Cross-function Reentrancy)
Kẻ tấn công cũng có thể thực hiện một cuộc tấn công bằng cách sử dụng hai hay nhiều hàm khác nhau có cùng trạng thái.
```javascript
// INSECURE
mapping (address => uint) private userBalances;
function transfer(address to, uint amount) {
if (userBalances[msg.sender] >= amount) {
userBalances[to] += amount;
userBalances[msg.sender] -= amount;
}
}
function withdrawBalance() public {
uint amountToWithdraw = userBalances[msg.sender];
(bool success, ) = msg.sender.call.value(amountToWithdraw)(""); // At this point, the caller's code is executed, and can call transfer()
require(success);
userBalances[msg.sender] = 0;
}
```
Trong trường hợp này, kẻ tấn công sẽ gọi hàm transfer() khi chúng đang thực hiện lời gọi ngoài `.call.value(amountToWithdraw)("")` ở hàm `withdrawBalance`. Vì khi đó số dư của kẻ tấn công chưa được gán bằng 0, nên chúng có thể chuyển mã số dư đến một tài khoản khác mặc dù chúng đã nhận được tiền từ hàm withdrawBalance. Lỗ hổng này cũng đa được sử dụng trong cuộc tấn công vào DAO.
Lưu ý rằng trong ví dụ này, cả hai hàm đều cùng thuộc một hợp đồng. Tuy nhiên, lỗi có thể xảy ra trên nhiều hợp đồng, nếu các hợp đồng đó chia sẻ trạng thái với nhau.
### Pitfalls in Reentrancy Solutions
Reentrancy có thể xảy ra trên một chuỗi các hàm, thậm chí là chuỗi các hợp đồng. Cho nên, bất kỳ giải pháp nào nhằm ngăn chặn reentrancy chỉ với một hàm là không đủ.
Thay vào đó, chúng tôi khuyến nghị nên chạy hết các câu lệnh trong hợp đồng (internal work) (ví dụ: thay đổi trạng thái các biến, mapping) trước việc thực hiện các lời gọi hàm bên ngoài (external call). Quy tắc này, nếu được tuân thủ cẩn thận, sẽ phòng chống được các lỗ hổng do reentrancy có thể gây ra. Tuy nhiên, bạn không chỉ cần tránh các lời gọi ngoài được thực thi quá sớm mà còn nên tránh việc các hàm bên trong hợp đồng gọi đến các hàm khác quá sớm (nên để các lời gọi hàm thực thi sau khi các đoạn mã logic khác trong thân hàm đã chạy qua). Ví dụ sau đây minh chứng cho việc viết mã không an toàn.
```javascript
// INSECURE
mapping (address => uint) private userBalances;
mapping (address => bool) private claimedBonus;
mapping (address => uint) private rewardsForA;
function withdrawReward(address recipient) public {
uint amountToWithdraw = rewardsForA[recipient];
rewardsForA[recipient] = 0;
(bool success, ) = recipient.call.value(amountToWithdraw)("");
require(success);
}
function getFirstWithdrawalBonus(address recipient) public {
require(!claimedBonus[recipient]); // Each recipient should only be able to claim the bonus once
rewardsForA[recipient] += 100;
withdrawReward(recipient); // At this point, the caller will be able to execute getFirstWithdrawalBonus again.
claimedBonus[recipient] = true;
}
```
Mặc dù hàm `getFirstWithdrawalBonus()` không gọi thực hiện lời gọi ngoài, nhưng đoạn mã `withdrawReward(recipient)` gọi đến hàm `withdrawReward()` đã tạo ra lỗ hổng mà kẻ xấu có thể khai thác. Do đó, phải xem lời gọi đến hàm `withdrawReward()` trong hàm `getFirtWithdrawBonus()` là không đáng tin cậy và nên để nó được thực thi ở sau cùng trong hàm `getFirtWithdrawBonus()`.
```javascript
mapping (address => uint) private userBalances;
mapping (address => bool) private claimedBonus;
mapping (address => uint) private rewardsForA;
function untrustedWithdrawReward(address recipient) public {
uint amountToWithdraw = rewardsForA[recipient];
rewardsForA[recipient] = 0;
(bool success, ) = recipient.call.value(amountToWithdraw)("");
require(success);
}
function untrustedGetFirstWithdrawalBonus(address recipient) public {
require(!claimedBonus[recipient]); // Each recipient should only be able to claim the bonus once
claimedBonus[recipient] = true;
rewardsForA[recipient] += 100;
untrustedWithdrawReward(recipient); // claimedBonus has been set to true, so reentry is impossible
}
```
Ngoài việc chuyển dòng code gọi đến hàm withdrawReward xuống cuối cùng trong hàm GetFirstWithdrawalBonus, chúng ta còn nên thay đổi tên hàm để đánh dấu nó là không đáng tin cậy.
Một giải pháp khác được đề xuất là [mutex](https://en.wikipedia.org/wiki/Mutual_exclusion). Điều này cho phép bạn "khóa" một số trạng thái để chủ sở hữu khóa mới có thể thay đổi trạng thái. Một ví dụ đơn giản có thể trông như thế này:
```javascript
// Note: This is a rudimentary example, and mutexes are particularly useful where there is substantial logic and/or shared state
mapping (address => uint) private balances;
bool private lockBalances;
function deposit() payable public returns (bool) {
require(!lockBalances);
lockBalances = true;
balances[msg.sender] += msg.value;
lockBalances = false;
return true;
}
function withdraw(uint amount) payable public returns (bool) {
require(!lockBalances && amount > 0 && balances[msg.sender] >= amount);
lockBalances = true;
(bool success, ) = msg.sender.call(amount)("");
if (success) { // Normally insecure, but the mutex saves it
balances[msg.sender] -= amount;
}
lockBalances = false;
return true;
}
```
Nếu kẻ xấu cố gắng gọi hàm withdraw() một lần nữa trước khi câu lệnh `msg.sender.call(amount)()` kết thúc, khóa sẽ ngăn cản điều đó. Đây có thể là một giải pháp hiệu quả, nhưng nó trở nên khó khăn khi bạn có nhiều hợp đồng liên kết với nhau. Sau đây ví dụ minh chứng cho nhận định trên:
```javascript
// INSECURE
contract StateHolder {
uint private n;
address private lockHolder;
function getLock() {
require(lockHolder == address(0));
lockHolder = msg.sender;
}
function releaseLock() {
require(msg.sender == lockHolder);
lockHolder = address(0);
}
function set(uint newState) {
require(msg.sender == lockHolder);
n = newState;
}
}
```
Kẻ tấn công có thể gọi hàm getLock(), và sẽ không bao giờ gọi đến releaseLock(). Nếu điều này xảy ra, thì hợp đồng sẽ bị khóa vĩnh viễn và không thể thay đổi trạng thái của lockHolder. Nếu bạn sử dụng mutexes để phòng chống reentrancy, bạn sẽ cần chắc chắn rằng sẽ không có trường hợp bị khóa cứng như đoạn mã ở trên. (Có những nguy cơ tiềm tàng khác khi lập trình với mutexes, chẳng hạn như deadlocks và livelocks).
## Front-Running (AKA Transaction-Ordering Dependence)
Ở phần trên chúng ta đã đề cập đến thức khai thác và phòng chống kiểu tấn công reentrancy. Bây giờ chúng ta sẽ cùng tìm hiểu một kiểu tấn công khác trong Blockchain dựa vào đặc điểm rằng: thứ tự của các giao dịch (ví dụ: trong một block) có thể bị kiểm soát.
Một giao dịch trước khi được xác minh được nằm trong mempool một thời gian ngắn, người ta có thể biết được những gì xảy ra với giao dịch trước khi nó được đưa vào một block. Điều này có thể gây rắc rối cho những sàn giao dịch phi tập trung, nơi mọi người có thể nhìn thấy tất cả các giao dịch. Để ngăn cản điều này thực sự rất khó. Ví dụ, tại các sàn giao dịch, sẽ tốt hơn nếu thực hiện đấu giá hàng loạt (điều này cũng bảo vệ chống lại việc phân tích giao dịch). Một cách khác là sử dụng sơ đồ pre-commit (Chúng tôi sẽ đi vào chi tiết ở phần sau).
## Tràn số nguyên (underfow và overflow)
Chúng ta hãy còn xem đoạn mã dưới đây:
```javascript
mapping (address => uint256) public balanceOf;
// INSECURE
function transfer(address _to, uint256 _value) {
/* Check if sender has balance */
require(balanceOf[msg.sender] >= _value);
/* Add and subtract new balances */
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
}
// SECURE
function transfer(address _to, uint256 _value) {
/* Check if sender has balance and for overflows */
require(balanceOf[msg.sender] >= _value && balanceOf[_to] + _value >= balanceOf[_to]);
/* Add and subtract new balances */
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
}
```
Nếu giá trị `balanceOf[_to]` đạt đến giá trị lớn nhất có thể (2 ^ 256 - 1), chúng ta tăng `balanceOf[_to]` lên, nó sẽ quay vòng về giá trị 0. Khi thực hiện phép tính với các biến, hãy xem xem việc giá trị uint có cơ hội tiếp cận một số lớn như vậy hay không. Hãy xem rằng các biến uint có thể thay đổi giá trị bởi những ai. Nếu bất kỳ người dùng nào có thể gọi các hàm cập nhật giá trị uint, thì rất là nguy hiểm. Sẽ an toàn hơn nếu chỉ có quản trị viên có quyền truy cập để thay đổi trạng thái của biến. Nếu người dùng chỉ có thể tăng thêm 1 lần một, có lẽ bạn cũng an toàn vì khó có cách nào khả thi để đạt đến giới hạn số.
Tương tự với underflow. Nếu một uint bị giảm giá trị đến một số âm nhỏ hơn 0, nó sẽ quay vòng lại số lớn nhất của kiểu dữ liệu.
Một giải pháp đơn giản để giảm thiểu lỗi tràn số là sử dụng thư viện [SafeMath.sol](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/math/SafeMath.sol) cho các hàm số học.
### Tìm hiểu chi tiết hơn về underflow
Bài dự thi của Doug Hoyte trong Solidity contest năm 2017 đã đề cập đến một vấn đề quan trọng. Đây là một phiên bản đơn giản hóa bài dự thi của Doug Hoyte:
```javascript
contract UnderflowManipulation {
address public owner;
uint256 public manipulateMe = 10;
function UnderflowManipulation() {
owner = msg.sender;
}
uint[] public bonusCodes;
function pushBonusCode(uint code) {
bonusCodes.push(code);
}
function popBonusCode() {
require(bonusCodes.length >=0); // this is a tautology
bonusCodes.length--; // an underflow can be caused here
}
function modifyBonusCode(uint index, uint update) {
require(index < bonusCodes.length);
bonusCodes[index] = update; // write to any index less than bonusCodes.length
}
}
```
Nhìn vào đoạn mã trên, thật khó để có thể gây tràn số dưới với biến `manipulateMe`. Tuy nhiên, với các mảng động được lưu trữ tuần tự, nên nếu một kẻ xấu muốn thay đổi thao tác, tất cả những gì họ cần làm là:
- Gọi `popBonusCode` để underflow (Lưu ý: phương thức `Array.pop()` đã được thêm vào trong phiên bản Solidity 0.5.0)
- Tính toán vị trí lưu trữ của biến `manipulateMe`
- Sửa đổi và cập nhật giá trị của mảng bằng cách sử dụng `notifyBonusCode`
### Tấn công từ chối dịch vụ với revert
Chúng ta cùng xem một hợp đồng đấu giá đơn giản dưới đây
```javascript
// INSECURE
contract Auction {
address currentLeader;
uint highestBid;
function bid() payable {
require(msg.value > highestBid);
require(currentLeader.send(highestBid)); // Refund the old leader, if it fails then revert
currentLeader = msg.sender;
highestBid = msg.value;
}
}
```
Nếu kẻ tấn công sử dụng một hợp đồng thông minh với hàm fallback có chức năng revert mọi giao dịch đến, kẻ tấn công có thể giành chiến thắng trong bất kỳ cuộc đấu giá nào. Có nghĩa là hacker sẽ gửi số tiền cao hơn số tiền hiện tại vào hàm `bid()` và trở thành leader, sau đó hắn đảm bảo rằng khi ai đó gửi số tiền lớn hơn, thì khi xảy ra giao dịch hoàn lại tiền cho hacker, nó đều sẽ không thành công.. Bằng cách này, chúng có thể ngăn bất kỳ ai khác gọi hàm `bid()` và chúng sẽ là leader mãi mãi. Lời khuyên ở đây là chúng ta sẽ chia thành 2 hàm gửi tiền và rút tiền, người dùng khi không là leader nữa thì sẽ gọi hàm rút tiền để thu lại số tiền đã gửi vào hàm gửi tiền.
Một ví dụ khác là khi hợp đồng sử dụng vòng lặp để duyệt qua mảng nhằm trả tiền cho các người dùng (ví dụ: những người ủng hộ trong hợp đồng gây quỹ cộng đồng). Điều thông thường là muốn đảm bảo rằng mỗi khoản thanh toán thành công. Nếu không, giao dịch sẽ bị revert. Vấn đề là nếu giao dịch thất bại, bạn đang revert toàn bộ hệ thống thanh toán, nghĩa là vòng lặp sẽ không bao giờ được hoàn thành. Không ai được trả tiền vì một địa chỉ giao dịch bị lỗi.
```javascript
address[] private refundAddresses;
mapping (address => uint) public refunds;
// bad
function refundAll() public {
for(uint x; x < refundAddresses.length; x++) { // arbitrary length iteration based on how many addresses participated
require(refundAddresses[x].send(refunds[refundAddresses[x]])) // doubly bad, now a single failure on send will hold up all funds
}
}
```
### Tấn công từ chối dịch vụ dựa với GasLimit
Mỗi block có giới hạn trên về lượng gas có thể được sử dụng, và do đó suy ra khối lượng tính toán có thể được thực hiện. Nếu lượng gas chi vượt quá giới hạn này, giao dịch sẽ thất bại. Điều này dẫn đến một vài nguy cơ có thể bị hacker tấn công từ chối dịch vụ.
### Gas Limit DoS on a Contract via Unbounded Operations
Một vấn đề khác với ví dụ trước: bằng cách thanh toán cho mọi người cùng một lúc, bạn có nguy cơ sử dụng quá lượng gas giới hạn.
Điều này có thể dẫn đến các vấn đề ngay cả khi không có một cuộc tấn công có chủ đích. Tuy nhiên, thật tệ nếu kẻ tấn công có thể thao túng lượng gas cần thiết. Trong trường hợp của ví dụ trước, kẻ tấn công có thể tạo một loạt địa chỉ, mỗi địa chỉ cần được hoàn lại một số tiền rất nhỏ. Do đó, chi phí gas cho việc hoàn trả tiền cho từng địa chỉ của kẻ tấn công có thể vượt lượng gas, ngăn chặn giao dịch hoàn tiền có thể xảy ra cho những người dùng khác.
Nếu bạn phải sử dụng vòng lặp để duyệt qua một mảng có kích thước không xác định, bạn nên để chúng diễn ra dàn trải trên nhiều block. Như trong ví dụ sau:
```javascript
struct Payee {
address addr;
uint256 value;
}
Payee[] payees;
uint256 nextPayeeIndex;
function payOut() {
uint256 i = nextPayeeIndex;
while (i < payees.length && msg.gas > 200000) {
payees[i].addr.send(payees[i].value);
i++;
}
nextPayeeIndex = i;
}
```
Bạn sẽ cần đảm bảo rằng sẽ không có gì xấu xảy ra nếu các giao dịch khác được xử lý trong khi chờ lần lặp tiếp theo của hàm `payOut()`. Vì vậy, chỉ sử dụng mô hình này nếu thực sự cần thiết.
### Gas Limit DoS on the Network via Block Stuffing
Ngay cả khi hợp đồng của bạn không chứa vòng lặp, kẻ tấn công có thể ngăn các giao dịch khác được đưa vào blockchain trong một vài block bằng cách đặt một số giao dịch với lượng gas cao.
Để làm được điều này, kẻ tấn công sẽ phát hành một số giao dịch sẽ với lượng tổng lượng gas tiêu thụ tương đương với gasLimit, với phí gas đủ cao thì những giao dịch đấy sẽ được đưa vào block mới nhất. Không có gì đảm bảo một giao dịch có phí gas cao sẽ được đưa vào block, nhưng giá càng cao, cơ hội càng cao.
Nếu cuộc tấn công thành công, sẽ không có giao dịch nào khác được đưa vào block. Đôi khi, mục tiêu của kẻ tấn công là ngăn chặn các giao dịch gọi đến một hợp đồng cụ thể trong một khoảng thời gian.
Cuộc tấn công này đã xảy ra trên [Fomo3D](https://solmaz.io/2018/10/18/anatomy-block-stuffing/), một ứng dụng đánh bạc. Trò chơi sẽ có một bộ đếm ngược thời gia, khi bộ đếm ngược chạy về 0, ai là người mua "chìa khóa" cuối cùng sẽ là người lãnh thưởng. Kẻ tấn công đã mua một khóa và sau đó chúng đã nhồi các giao dịch có phí gas cao trong 13 block liên tiếp cho đến khi bộ đếm thời gian hết giờ và khoản tiền thưởng được giải phóng. Các giao dịch được gửi bởi kẻ tấn công đã tốn mất 7,9 triệu gas trên mỗi block, do đó lượng gas còn lại chỉ cho phép một vài giao dịch nhỏ (mất 21.000 gas mỗi lần), nhưng không cho phép bất kỳ giao dịch nào gọi đến hàm buyKey() (tốn 300.000+ gas).
Một cuộc tấn công Block Stuffing có thể được sử dụng trên bất kỳ hợp đồng nào yêu cầu một hành động trong một khoảng thời gian nhất định. Tuy nhiên, như với bất kỳ cuộc tấn công nào, nó chỉ có lợi khi phần thưởng đạt được lớn hơn chi phí mà những kẻ tấn công phải bỏ ra. Chi phí của cuộc tấn công này tỷ lệ thuận với số block cần tấn công. Nếu hợp đồng của bạn gồm một khoản thanh toán lớn có thể có được bằng cách ngăn chặn các hành động từ những người dùng khác, thì nó có thể sẽ là mục tiêu của một cuộc tấn công như vậy.
## Insufficient gas griefing
Phương thức tấn công này có thể xảy ra đối với một hợp đồng chấp nhận sử dụng dữ liệu chung và nó thực hiện lời gọi một hợp trung gian (sub call) thông qua phương thức ở mức thấp `address.call()` .
Nếu một lời gọi thất bại, hợp đồng có 2 lựa chọn
- Revert toàn bộ giao dịch
- Tiếp tục thực thi
Ví dụ sau đây về lời gọi của hợp đồng Relayer sẽ tiếp tục thực thi bất kể kết quả của subcall:
```javascript
contract Relayer {
mapping (bytes => bool) executed;
function relay(bytes _data) public {
// replay protection; do not call the same transaction twice
require(executed[_data] == 0, "Duplicate call");
executed[_data] = true;
innerContract.call(bytes4(keccak256("execute(bytes)")), _data);
}
}
```
Hợp đồng này cho phép chuyển tiếp giao dịch. Ai đó muốn thực hiện giao dịch nhưng không thể tự thực hiện giao dịch (ví dụ do thiếu ether để thanh toán tiền gas) có thể ký dữ liệu mà anh ta muốn chuyển và chuyển dữ liệu bằng chữ ký của mình. Sau đó, một "người chuyển tiếp" bên thứ ba có thể gửi giao dịch này tới mạng thay mặt cho người dùng.
Nếu chỉ được cung cấp đủ lượng gas, Relayer sẽ hoàn thành việc ghi tham số `_data` vào trong mapping \`\`excuted\`\`\`, nhưng subcall sẽ thất bại vì không nhận đủ gas để thực hiện xong.
Kẻ tấn công có thể sử dụng điều này để kiểm duyệt các giao dịch, khiến chúng thất bại bằng cách gửi chúng đi với một lượng gas thấp. Cuộc tấn công này không trực tiếp mang lại lợi ích cho kẻ tấn công, nhưng gây ra thiệt hại cho nạn nhân. Kẻ tấn công, sẵn sàng tiêu tốn một lượng khí nhỏ về mặt lý thuyết có thể kiểm duyệt tất cả các giao dịch theo cách này, nếu chúng là người đầu tiên gửi chúng cho Relayer.
Một cách để giải quyết vấn đề này là triển khai logic mã nguồn yêu cầu các hợp đồng cung cấp đủ gas để hoàn thành subcall. Nếu một thợ đào đã cố gắng tiến hành cuộc tấn công theo kịch bản này, câu lệnh `require` sẽ thất bại và giao dịch sẽ bị revert. Người dùng có thể chỉ định gasLimit tối thiểu cùng với dữ liệu khác (trong ví dụ này, thông thường, giá trị \_gasLimit sẽ được xác minh bằng chữ ký, nhưng điều đó được khuyến nghị vì đơn giản trong trường hợp này).
```javascript
// contract called by Relayer
contract Executor {
function execute(bytes _data, uint _gasLimit) {
require(gasleft() >= _gasLimit);
...
}
}
```
Một giải pháp khác là chỉ cho phép các tài khoản đáng tin cậy chuyển tiếp giao dịch.
## Bắt buộc một hợp đồng phải nhận ether
Có thể gửi Ether vào một hợp đồng mà không cần kích hoạt fallback function của hợp đồng đó. Đây là một cân nhắc quan trọng khi viết mã cho fallback function hoặc thực hiện các tính toán dựa trên số dư của hợp đồng. Lấy ví dụ sau:
```javascript
contract Vulnerable {
function () payable {
revert();
}
function somethingBad() {
require(this.balance > 0);
// Do something bad
}
}
```
Logic hợp đồng dường như không cho phép gửi ether vào hợp đồng. Tuy nhiên, một vài phương thức tồn tại để buộc hợp đồng nhận ether và làm cho số dư của nó lớn hơn 0.
Phương thức `sefldestruct` cho phép chỉ định tài khoản nhận ether và không có cách gì để ngăn cản điều đó. `sefldestruct` không kích hoạt fallback function của hợp đồng.
## Các vụ hack lịch sử
Đây là những cuộc tấn công không còn có thể thực hiện do những thay đổi trong giao thức hoặc do các bản cập nhật solidity.
### Các lỗ hổng khác
[Smart Contract Weakness Classification Registry](https://smartcontractsecurity.github.io/SWC-registry/) cơ cung cấp một danh mục đầy đủ cập nhật về các lỗ hổng hợp đồng thông minh đã biết và các cách chống lại chúng với các ví dụ trong thế giới thực. Hãy thường xuyên xem qua là một cách tốt để cập nhật các cuộc tấn công mới nhất.
<a name="eng-techniques"></a>
# Áp dụng các nguyên tắc trong phát triển phần mềm để viết hợp đồng thông minh
Việc bảo vệ trước các cuộc tấn công đã biết là không đủ. Vì chi phí thiệt hại trên blockchain có thể rất cao, bạn cũng phải điều chỉnh cách bạn viết phần mềm, để phòng tránh những rủi ro đó.
Cách tiếp cận chúng tôi là "chuẩn bị cho thất bại". Không thể biết trước được liệu mã nguồn của bạn có an toàn hay không ? Tuy nhiên, bạn có thể thiết kế các hợp đồng của mình theo cách cho phép chúng thất bại với thiệt hại tối thiểu. Phần này trình bày một loạt các kỹ thuật sẽ giúp bạn chuẩn bị cho thất bại.
*Lưu ý*: Luôn có rủi ro khi bạn thêm một thành phần mới vào hệ thống của mình. Hãy suy nghĩ kỹ về từng kỹ thuật bạn sử dụng trong các hợp đồng của mình và xem xét cẩn thận cách chúng phối hợp với nhau để tạo ra một hệ thống an toàn.
## Nâng cấp hợp đồng bị lỗi
Mã nguồn sẽ cần phải được thay đổi nếu phát hiện lỗi hoặc vì lý do cần cải thiện.
Thiết kế một hệ thống nâng cấp hiệu quả cho các hợp đồng thông minh là một lĩnh vực nghiên cứu và chúng tôi sẽ không thể đề cập đến tất cả các vấn đề trong tài liệu này được. Tuy nhiên, có hai cách tiếp cận cơ bản được sử dụng phổ biến nhất. Cách thứ nhất là tạo một hợp đồng giữ địa chỉ phiên bản mới nhất của các hợp đồng khác. Một cách tiếp cận khác là có một hợp đồng chuyển tiếp các lời gọi và dữ liệu đến phiên bản mới nhất của hợp đồng.
Dù là kỹ thuật nào, điều quan trọng là mã nguồn được mô đun hóa và phân tách tốt giữa các thành phần, để nếu một thay đổi xảy ra sẽ không phá vỡ cấu trúc của toàn bộ hệ thống.
Điều quan trọng là có một cách an toàn để các bên quyết định nâng cấp mã. Tùy thuộc vào hợp đồng của bạn, các thay đổi mã nguồn có thể cần được chấp thuận bởi một bên đáng tin cậy, một nhóm thành viên hoặc bỏ phiếu của toàn bộ các bên liên quan. Nếu quá trình này có thể mất một chút thời gian, bạn sẽ muốn xem xét liệu có cách nào khác để phản ứng nhanh hơn trong trường hợp bị tấn công, chẳng hạn như dừng khẩn cấp hoặc ngắt mạch.
### Ví dụ 1: Sử dụng hợp đồng để lưu trữ địa chỉ phiên bản mới nhất của các hợp đồng khác
```javascript
contract SomeRegister {
address backendContract;
address[] previousBackends;
address owner;
function SomeRegister() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner)
_;
}
function changeBackend(address newBackend) public
onlyOwner()
returns (bool)
{
if(newBackend != backendContract) {
previousBackends.push(backendContract);
backendContract = newBackend;
return true;
}
return false;
}
}
```
Có hai nhược điểm chính trong cách tiếp cận này:
1. Người dùng phải luôn luôn kiểm tra địa chỉ mới nhất của hợp đồng cần gọi đến và bất kỳ ai không thực hiện điều này có thể gặp rủi ro khi tương tác với các phiên bản cũ của hợp đồng.
1. Bạn sẽ cần suy nghĩ cẩn thận về cách xử lý dữ liệu hợp đồng khi bạn thay thế hợp đồng mới.
### Ví dụ 2: Sử dụng \`\`\`delegatecall\`\` cho việc chuyển tiếp lời gọi và dữ liệu đến hợp đồng.
```javascript
contract Relay {
address public currentVersion;
address public owner;
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
function Relay(address initAddr) {
currentVersion = initAddr;
owner = msg.sender; // this owner may be another contract with multisig, not a single contract owner
}
function changeContract(address newVersion) public
onlyOwner()
{
currentVersion = newVersion;
}
function() {
require(currentVersion.delegatecall(msg.data));
}
}
```
Cách tiếp cận này tránh được các vấn đề ở ví dụ 1 ở trên nhưng cũng có vấn đề của riêng nó. Bạn phải cực kỳ cẩn thận với cách bạn lưu trữ dữ liệu trong hợp đồng này. Nếu hợp đồng mới của bạn có bố cục lưu trữ khác với hợp đồng đầu tiên, dữ liệu của bạn có thể bị hỏng. Ngoài ra, phiên bản đơn giản này của không thể trả về giá trị từ các hàm, chỉ chuyển tiếp chúng, làm hạn chế khả năng ứng dụng của nó.
## Bộ ngắt (Tạm dừng chức năng của hợp đồng)
Bộ ngắt sẽ dừng việc thực thi nếu thỏa mãn một số điều kiện nhất định và nó có thể hữu ích khi phát hiện ra lỗi mới. Ví dụ: hầu hết các hành động có thể bị đình chỉ trong hợp đồng nếu phát hiện ra lỗi và hành động duy nhất hiện đang hoạt động là rút tiền. Bạn có thể cung cấp cho các bên đáng tin cậy khả năng kích hoạt bộ ngắt hoặc có thể lập trình để tự động hóa kích hoạt bộ ngắt khi gặp một số trường hợp nhất định.
```javascript
bool private stopped = false;
address private owner;
modifier isAdmin() {
require(msg.sender == owner);
_;
}
function toggleContractActive() isAdmin public {
// You can add an additional modifier that restricts stopping a contract to be based on another action, such as a vote of users
stopped = !stopped;
}
modifier stopInEmergency { if (!stopped) _; }
modifier onlyInEmergency { if (stopped) _; }
function deposit() stopInEmergency public {
// some code
}
function withdraw() onlyInEmergency public {
// some code
}
```
## Trì hoãn hành động của hợp đồng
Với việc trì hoãn các hành đồng từ hợp đồng, chúng ta sẽ có thêm thời gian để phục hồi hệ thống nếu bị tấn công. Ví dụ, đoạn mã ở dưới chỉ cho phép người dùng rút tiền sau 28 ngày kể từ lúc yêu câu rút tiền được gửi đến hợp đồng.
```javascript
struct RequestedWithdrawal {
uint amount;
uint time;
}
mapping (address => uint) private balances;
mapping (address => RequestedWithdrawal) private requestedWithdrawals;
uint constant withdrawalWaitPeriod = 28 days; // 4 weeks
function requestWithdrawal() public {
if (balances[msg.sender] > 0) {
uint amountToWithdraw = balances[msg.sender];
balances[msg.sender] = 0; // for simplicity, we withdraw everything;
// presumably, the deposit function prevents new deposits when withdrawals are in progress
requestedWithdrawals[msg.sender] = RequestedWithdrawal({
amount: amountToWithdraw,
time: now
});
}
}
function withdraw() public {
if(requestedWithdrawals[msg.sender].amount > 0 && now > requestedWithdrawals[msg.sender].time + withdrawalWaitPeriod) {
uint amountToWithdraw = requestedWithdrawals[msg.sender].amount;
requestedWithdrawals[msg.sender].amount = 0;
require(msg.sender.send(amountToWithdraw));
}
}
```
## Giới hạn tỷ lệ
Ví dụ: người gửi tiền chỉ có thể được phép rút một số tiền hoặc tỷ lệ phần trăm của tổng số tiền gửi trong một khoảng thời gian nhất định (ví dụ: tối đa 100 ether trong 1 ngày) - rút tiền bổ sung trong khoảng thời gian đó có thể thất bại hoặc hợp đồng sẽ yêu cầu một số phê duyệt đặc biệt. Hoặc ta giới hạn tỷ lệ có thể ở hợp đồng, chỉ với một số lượng mã token nhất định do hợp đồng phát hành trong một khoảng thời gian.
```javascript
contract CircuitBreaker {
struct Transfer {
uint amount;
address to;
uint releaseBlock;
bool released;
bool stopped;
}
Transfer[] public transfers;
address public curator;
address public authorizedSender;
uint public period;
uint public limit;
uint public currentPeriodEnd;
uint public currentPeriodAmount;
event PendingTransfer(uint id, uint amount, address to, uint releaseBlock);
function CircuitBreaker(address _curator, address _authorizedSender, uint _period, uint _limit) {
curator = _curator;
period = _period;
limit = _limit;
authorizedSender = _authorizedSender;
currentPeriodEnd = block.number + period;
}
function transfer(uint amount, address to) {
if (msg.sender == authorizedSender) {
updatePeriod();
if (currentPeriodAmount + amount > limit) {
uint releaseBlock = block.number + period;
PendingTransfer(transfers.length, amount, to, releaseBlock);
transfers.push(Transfer(amount, to, releaseBlock, false, false));
} else {
currentPeriodAmount += amount;
transfers.push(Transfer(amount, to, block.number, true, false));
if(!to.send(amount)) throw;
}
}
}
function updatePeriod() {
if (currentPeriodEnd < block.number) {
currentPeriodEnd = block.number + period;
currentPeriodAmount = 0;
}
}
function releasePendingTransfer(uint id) {
Transfer transfer = transfers[id];
if (transfer.releaseBlock <= block.number && !transfer.released && !transfer.stopped) {
transfer.released = true;
if(!transfer.to.send(transfer.amount)) throw;
}
}
function stopTransfer(uint id) {
if (msg.sender == curator) {
transfers[id].stopped = true;
}
}
}
```
## Triển khai hợp đồng
Hợp đồng nên có thời gian thử nghiệm - trước khi triển khai chính thức.
Tối thiểu, bạn nên:
- Kiểm thử đầy đủ với test coverage 100% (hoặc gần bằng)
- Deploy lên mạng thử nghiệm local (local testnet)
- Triển khai trên testnet public
- Triển khai trên mainnet ở phiên bản beta
#### AUTOMATIC DEPRECATION
Trong quá trình thử nghiệm, bạn có thể ngăn chặn mọi hành động, sau một khoảng thời gian nhất định. Ví dụ: hợp đồng alpha có thể hoạt động trong vài tuần và sau đó tự động tắt tất cả các hành động, ngoại trừ lần rút tiền cuối cùng.
```javascript
modifier isActive() {
require(block.number <= SOME_BLOCK_NUMBER);
_;
}
function deposit() public isActive {
// some code
}
function withdraw() public {
// some code
}
```
#### GIỚI HẠN SỐ TIỀN CỦA MỌI NGƯỜI DÙNG / HỢP ĐỒNG
Trong giai đoạn đầu, bạn có thể hạn chế lượng Ether cho bất kỳ người dùng nào (hoặc cho toàn bộ hợp đồng) - để thiểu giảm rủi ro.
### Trả thưởng cho những người tìm ra lỗi (Bug Bounty Program)
Các tips cho việc áp dụng việc trả thưởng cho người tìm ra lỗi:
- Quyết định xem loại tiền nào sẽ được dùng để trả thưởng (ETH hay BTC ...)
- Quyết định xem tổng ngân sách trả thưởng là bao nhiêu
- Từ ngân sách, xác định ba loại phần thưởng:
- phần thưởng nhỏ nhất bạn sẵn sàng đưa ra
- phần thưởng cao nhất được trao là bao nhiêu
- một số điều khoản bổ sung nếu trong trường hợp lỗ hổng rất nghiêm trọng
- Xác định các chuyên gia để đánh giá mức độ nghiêm trọng của lỗ hổng
- Lead developer có thể là một trong các chuyên gia để đánh giá mức độ nghiêm trọng của lỗ hổng
- Khi nhận được báo cáo lỗ hổng, các chuyên gia sẽ đánh giá xem lỗ hổng có mức độ nghiêm trọng như thế nào
- Hỏi xem người săn lỗi có bản vá hay chưa ? Nếu chưa có thì đội phát triển cần đưa ra bản vá một cách nhanh chóng
- Trả thưởng cho người tìm ra lỗi
Các bạn có thể tham khảo chương trình của Ethereum [Ethereum's Bounty Program](https://bounty.ethereum.org/)
<a name="token"></a>
# Lời khuyên cho việc implement mã Token
## Tuân thủ tiêu chuẩn mới nhất
Nói chung, hợp đồng thông minh của mã token phải tuân theo tiêu chuẩn đax được cộng đồng chấp nhận và xem là ổn định. Ví dụ về các tiêu chuẩn hiện được chấp nhận là:
- [EIP20](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md)
- [EIP721](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md)
## Lưu ý về các cuộc tấn công với EIP-20
Hàm `approve()` của token EIP-20 có thể dẫn đến trường hợp người dùng có số tiền chi tiêu được phê duyệt chi tiêu nhiều hơn số tiền dự định. Một cuộc tấn công front-running có thể được sử dụng, cho phép một kẻ xấu gọi `transferFrom()` cả trước và sau khi gọi `approve()` được xử lý. Thông tin chi tiết có sẵn trên [EIP](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md#approve) và trong [tài liệu này](https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/edit).
## Tránh việc chuyển token đến địa chỉ 0x0
Tại thời điểm viết, địa chỉ "không" [0x0000000000000000000000000000000000000000](https://etherscan.io/address/0x0000000000000000000000000000000000000000) giữ các mã token có giá trị hơn 80 triệu đô la !
## Tránh việc chuyển token đến chính hợp đồng của gửi token
Xem xét cũng ngăn chặn việc chuyển token đến chính địa chỉ của hợp đồng thông minh gửi token.
Một ví dụ về trường hợp này là hợp đồng thông minh [EOS](https://etherscan.io/address/0x86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0) nơi có hơn 90.000 token bị kẹt tại địa chỉ hợp đồng.
## Ví dụ
Một ví dụ về việc thực hiện cả hai khuyến nghị trên sẽ viết một modifier; xác thực rằng địa chỉ "đến" không phải là 0x0 cũng không phải là địa chỉ của hợp đồng thông minh:
```javascript
modifier validDestination( address to ) {
require(to != address(0x0));
require(to != address(this) );
_;
}
```
Modifier được áp dụng cho các phương thức "transfer" và "transferFrom":
```javascript
function transfer(address _to, uint _value)
validDestination(_to)
returns (bool)
{
(... your logic ...)
}
function transferFrom(address _from, address _to, uint _value)
validDestination(_to)
returns (bool)
{
(... your logic ...)
}
```
<a name="document"></a>
# Tài liệu và thủ tục
Khi triển khai một hợp đồng, điều quan trọng là phải bao gồm tài liệu thích hợp cho các biên liên quan có thể tìm hiểu. Một số tài liệu liên quan đến bảo mật bao gồm:
## Thông số kỹ thuật và kế hoạch triển khai
- Thông số kỹ thuật, sơ đồ, trạng thái, mô hình và tài liệu khác giúp người đánh giá và cộng đồng hiểu hệ thống dự định làm gì.
- Nhiều lỗi có thể được tìm thấy chỉ từ các thông số kỹ thuật, và chúng không tốn kém lắm để có thể sửa chữa.
## Trạng thái
- Nơi mã nguồn hiện tại được triển khai
- Phiên bản trình biên dịch, các bước để xác minh bytecode được triển khai khớp với mã nguồn
- Các phiên bản trình biên dịch sẽ được sử dụng cho các giai đoạn khác nhau.
- Trạng thái hiện tại của mã nguồn được triển khai (bao gồm các sự cố còn tồn tại, số liệu thống kê hiệu suất, v.v.)
## Nắm bắt được các vấn đề
- Ước lượng rủi ro chính với hợp đồng
- ví dụ: Bạn có thể mất tất cả tiền của mình, hacker có thể thực hiện một số hành đồng trái phép
- Nắm được tất cả các lỗi/giới hạn
- Các cuộc tấn công và giảm nhẹ thiệt hại
## Lịch sử
- Kiểm thử (bao gồm thống kê sử dụng, phát hiện lỗi, thời gian thử nghiệm)
- Những người đã review mã nguồn (và phản hồi chính của họ)
## Thủ tục
- Kế hoạch hành động trong trường hợp phát hiện ra lỗi (ví dụ: tùy chọn khẩn cấp, quy trình thông báo công khai, v.v.)
- Kết thúc quá trình nếu có sự cố xảy ra (ví dụ: các nhà tài trợ sẽ nhận được phần trăm số dư của bạn trước khi tấn công, từ số tiền còn lại)
- Chính sách công bố có trách nhiệm (ví dụ: nơi báo cáo lỗi được tìm thấy, các quy tắc của bất kỳ chương trình tiền thưởng lỗi nào)
- Phòng ngừa trong trường hợp thất bại (ví dụ: bảo hiểm, ...)
## Thông tin liên lạc
- Ai liên hệ được liên hệ khi xảy ra các vấn đề
- Phòng chat nơi câu hỏi có thể được hỏi
<a name="tools"></a>
# Các công cụ bảo mật
## Công cụ trực quan hóa
- [Sūrya](https://github.com/ConsenSys/surya) - Công cụ tiện ích cho các hệ thống hợp đồng thông minh, cung cấp một số kết quả đầu ra trực quan và thông tin về cấu trúc của hợp đồng. Nó hỗ trợ tính năng biểu đồ cho các lời gọi hàm.
- [Solgraph](https://github.com/raineorshine/solgraph) - Tạo biểu đồ DOT trực quan hóa luồng điều khiển chức năng của hợp đồng Solidity và làm nổi bật các lỗ hổng bảo mật tiềm ẩn.
- [EVM Lab](https://github.com/ethereum/evmlab) - Gói công cụ phong phú để tương tác với EVM. Bao gồm VM, API Etherchain.
- [ethereum-graph-debugger](https://github.com/fergarrui/ethereum-graph-debugger) - Một trình gỡ lỗi EVM bằng đồ họa. Hiển thị toàn bộ biểu đồ luồng điều khiển chương trình.
## Static and Dynamic Analysis
- [MythX](https://mythx.io/) - Các công cụ và extension phân tích bảo mật chuyên nghiệp cho Truffle, Embark và các môi trường khác.
- [Mythril](https://github.com/ConsenSys/mythril) - Một con dao quân đội Thụy Sĩ thực thụ cho bảo mật hợp đồng thông minh.
- [Slither](https://github.com/trailofbits/slither) - Framework phân tích cho nhiều vấn đề phổ biến với Solidity. Nó viết bằng Python.
- [Echidna](https://github.com/trailofbits/echidna) - Trong môi trường testing, tạo đầu vào độc hại nhằm phá vỡ hợp đồng thông minh.
- [Manticore](https://github.com/trailofbits/manticore) - Công cụ phân tích mã nhị phân với sự hỗ trợ EVM.
- [Oyente](https://github.com/melonproject/oyente) - Phân tích mã Ethereum để tìm các lỗ hổng phổ biến.
- [Securify](https://github.com/eth-sri/securify2) -phân tích trực tuyến một cách hoàn toàn tự động cho các hợp đồng thông minh, cung cấp các báo cáo bảo mật dựa trên các lỗ hổng đã biết.
- [SmartCheck](https://tool.smartdec.net/) - Phân tích tĩnh mã nguồn Solidity với các lỗ hổng bảo mật.
- [Octopus](https://github.com/quoscient/octopus) - Công cụ phân tích bảo mật cho hợp đồng thông minh Blockchain với sự hỗ trợ của EVM và (e) WASM.
## Weakness OSSClassifcation & Test Cases
- [SWC-registry](https://github.com/SmartContractSecurity/SWC-registry/) - Các định nghĩa SWC và một repo lớn các mẫu hợp đồng thông minh dễ bị tấn công.
- [SWC Pages](https://smartcontractsecurity.github.io/SWC-registry/) - Repo đăng ký SWC được xuất bản trên Github Pages.
## Test Coverage
- [solidity-coverage](https://github.com/sc-forks/solidity-coverage) - Code coverage và Solidity testing.
## Linters
Linters cải thiện chất lượng mã nguồn bằng cách thực thi các quy tắc làm cho mã dễ đọc và xem xét hơn.
- [Solcheck](https://github.com/federicobond/solcheck) - Một phiên bản eslint cho mã Solidity được viết bằng JS.
- [Solint](https://github.com/weifund/solint) - Solid linting giúp bạn thực thi các quy ước nhất quán và tránh các lỗi trong hợp đồng thông minh Solidity của bạn.
- [Solium](https://github.com/duaraghav8/Solium)
- [Solhint](https://github.com/protofire/solhint) - Cung cấp cho người dùng viết Solidity một cách Bảo mật và Phong cách.
<a name="eip"></a>
# EIPS
## Các đề xuất EIP liên quan đến bảo mật
Các đề xuất EIP sau đây rất quan trọng để nhận biết, hiểu cách EVM hoạt động hoặc thông báo các kỹ thuật mới áp dụng khi phát triển hệ thống hợp đồng thông minh.
Dưới đây chưa phải là một danh sách thật sự đầy đủ
### Các đề xuất hoàn chỉnh
- [EIP 155](https://eips.ethereum.org/EIPS/eip-155) Bảo vệ trước các vụ tấn công replay đơn giản
- [EIP 214](https://eips.ethereum.org/EIPS/eip-214) Opcode STATICCALL mới - Thêm một opcode mới có thể được sử dụng để gọi một hợp đồng khác (hoặc chính nó) trong khi không cho phép bất kỳ sửa đổi nào đối với trạng thái trong suốt lời gọi.
- [EIP 607](https://eips.ethereum.org/EIPS/eip-607) Triển khai các biện pháp phồng chống tấn công replay đơn giản từ EIP 155.
- [EIP 779](https://eips.ethereum.org/EIPS/eip-779) Tài liệu này ghi lại những thay đổi có trong hard fork có tên là "DAO Fork". Không giống như các hard fork khác, DAO Fork không thay đổi giao thức; tất cả các mã EVM, định dạng giao dịch, cấu trúc block, v.v vẫn giữ nguyên.
### Các đề xuất còn đang trong quá trình sửa đổi, phát triển
- [EIP 1470](https://eips.ethereum.org/EIPS/eip-1470) Phân loại điểm yếu hợp đồng thông minh - Đề xuất một sơ đồ phân loại cho các điểm yếu bảo mật trong hợp đồng thông minh Ethereum. (SWC)
- [EIP 1051](https://eips.ethereum.org/EIPS/eip-1051) Kiểm tra lỗi Overflow cho EVM - Thêm hai opcode mới cho phép phát hiện và ngăn chặn overflow hiệu quả.
- [EIP 1271](https://eips.ethereum.org/EIPS/eip-1271) Phương thức xác thực chữ ký tiêu chuẩn cho hợp đồng thông minh - Thiết kế hiện tại của nhiều hợp đồng thông minh là không có khóa riêng (private key) và do đó không thể ký trực tiếp message. Đề xuất ở đây phác thảo một cách tiêu chuẩn cho các hợp đồng để xác minh xem chữ ký được cung cấp có hợp lệ khi tài khoản là hợp đồng không ?
<a name="resource"></a>
# Các tài nguyên tham khảo
Dưới đây là danh sách các nguồn tài nguyên hữu ích để tham khải về bảo mật trong Ethereum cũng như Solidity. Nguồn tin chính thống thông báo về các vấn đề bảo mật là Blog Ethereum, nhưng trong nhiều trường hợp, các lỗ hổng sẽ được tiết lộ và thảo luận trước đó ở các nơi khác.
- [Ethereum Blog](https://blog.ethereum.org/): The official Ethereum blog
- [Ethereum Gitter](https://gitter.im/orgs/ethereum/rooms) chat rooms
- [Solidity](https://gitter.im/ethereum/solidity)
- [Go-Ethereum](https://gitter.im/ethereum/go-ethereum)
- [CPP-Ethereum](https://gitter.im/ethereum/cpp-ethereum)
- [Research](https://gitter.im/ethereum/research)
- [Smart Contract Security Weekly](https://tinyletter.com/smart-contract-security):Cập nhật hàng tuần về thông Hợp đồng thông minh Ethereum và Bảo mật cơ sở hạ tầng ([Past Articles](https://tinyletter.com/smart-contract-security/archive))
- [Reddit](https://www.reddit.com/r/ethereum)
- [Network Stats](https://ethstats.net/)
Chúng tôi khuyên bạn nên thường xuyên đọc các nguồn này, vì các phương pháp khai thác phát hiện có thể ảnh hưởng đến hợp đồng của bạn.
Ngoài ra, đây là danh sách các nhà phát triển của Ethereum:
- **Vitalik Buterin**: [Twitter](https://twitter.com/vitalikbuterin), [Github](https://github.com/vbuterin), [Reddit](https://www.reddit.com/user/vbuterin), [Ethereum Blog](https://blog.ethereum.org/author/vitalik-buterin/)
- **Dr. Christian Reitwiessner**: [Twitter](https://twitter.com/ethchris), [Github](https://github.com/chriseth), [Ethereum Blog](https://blog.ethereum.org/author/christian_r/)
- **Dr. Gavin Wood**: [Twitter](https://twitter.com/gavofyork), [Blog](http://gavwood.com/), [Github](https://github.com/gavofyork)
- **Vlad Zamfir**: [Twitter](https://twitter.com/vladzamfir), [Github](https://github.com/vladzamfir), [Ethereum Blog](https://blog.ethereum.org/author/vlad/)
Và cuối cùng, chúng ta có the tham khảo các danh sách các bài viết về bảo mật trong Ethereum được viết từ các nhà phát triển Ethereum đến những người khác trong cộng động.
#### Viết bởi các nhà phát triển Ethereum
- [How to Write Safe Smart Contracts](https://chriseth.github.io/notes/talks/safe_solidity) (Christian Reitwiessner)
- [Smart Contract Security](https://blog.ethereum.org/2016/06/10/smart-contract-security/) (Christian Reitwiessner)
- [Thinking about Smart Contract Security](https://blog.ethereum.org/2016/06/19/thinking-smart-contract-security/) (Vitalik Buterin)
- [Solidity](http://solidity.readthedocs.io/)
- [Solidity Security Considerations](http://solidity.readthedocs.io/en/latest/security-considerations.html)
#### Viết bởi cộng đồng
- [https://blog.sigmaprime.io/solidity-security.html](https://blog.sigmaprime.io/solidity-security.html)
- [http://forum.ethereum.org/discussion/1317/reentrant-contracts](http://forum.ethereum.org/discussion/1317/reentrant-contracts)
- [http://hackingdistributed.com/2016/06/16/scanning-live-ethereum-contracts-for-bugs/](http://hackingdistributed.com/2016/06/16/scanning-live-ethereum-contracts-for-bugs/)
- [http://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/](http://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/)
- [http://hackingdistributed.com/2016/06/22/smart-contract-escape-hatches/](http://hackingdistributed.com/2016/06/22/smart-contract-escape-hatches/)
- [http://martin.swende.se/blog/Devcon1-and-contract-security.html](http://martin.swende.se/blog/Devcon1-and-contract-security.html)
- [http://publications.lib.chalmers.se/records/fulltext/234939/234939.pdf](http://publications.lib.chalmers.se/records/fulltext/234939/234939.pdf)
- [http://vessenes.com/deconstructing-thedao-attack-a-brief-code-tour](http://vessenes.com/deconstructing-thedao-attack-a-brief-code-tour)
- [http://vessenes.com/ethereum-griefing-wallets-send-w-throw-considered-harmful](http://vessenes.com/ethereum-griefing-wallets-send-w-throw-considered-harmful)
- [http://vessenes.com/more-ethereum-attacks-race-to-empty-is-the-real-deal](http://vessenes.com/more-ethereum-attacks-race-to-empty-is-the-real-deal)
- [https://blog.blockstack.org/simple-contracts-are-better-contracts-what-we-can-learn-from-the-dao-6293214bad3a](https://blog.blockstack.org/simple-contracts-are-better-contracts-what-we-can-learn-from-the-dao-6293214bad3a)
- [https://blog.slock.it/deja-vu-dao-smart-contracts-audit-results-d26bc088e32e](https://blog.slock.it/deja-vu-dao-smart-contracts-audit-results-d26bc088e32e)
- [https://blog.vdice.io/wp-content/uploads/2016/11/vsliceaudit_v1.3.pdf](https://blog.vdice.io/wp-content/uploads/2016/11/vsliceaudit_v1.3.pdf)
- [https://eprint.iacr.org/2016/1007.pdf](https://eprint.iacr.org/2016/1007.pdf)
- [https://github.com/Bunjin/Rouleth/blob/master/Security.md](https://github.com/Bunjin/Rouleth/blob/master/Security.md)
- [https://github.com/LeastAuthority/ethereum-analyses](https://github.com/LeastAuthority/ethereum-analyses)
- [https://github.com/bokkypoobah/ParityMultisigRecoveryReconciliation](https://github.com/bokkypoobah/ParityMultisigRecoveryReconciliation)
- [https://medium.com/@ConsenSys/assert-guards-towards-automated-code-bounties-safe-smart-contract-coding-on-ethereum-8e74364b795c](https://medium.com/@ConsenSys/assert-guards-towards-automated-code-bounties-safe-smart-contract-coding-on-ethereum-8e74364b795c)
- [https://medium.com/@coriacetic/in-bits-we-trust-4e464b418f0b](https://medium.com/@coriacetic/in-bits-we-trust-4e464b418f0b)
- [https://medium.com/@hrishiolickel/why-smart-contracts-fail-undiscovered-bugs-and-what-we-can-do-about-them-119aa2843007](https://medium.com/@hrishiolickel/why-smart-contracts-fail-undiscovered-bugs-and-what-we-can-do-about-them-119aa2843007)
- [https://medium.com/@peterborah/we-need-fault-tolerant-smart-contracts-ec1b56596dbc](https://medium.com/@peterborah/we-need-fault-tolerant-smart-contracts-ec1b56596dbc)
- [https://medium.com/zeppelin-blog/zeppelin-framework-proposal-and-development-roadmap-fdfa9a3a32ab](https://medium.com/zeppelin-blog/zeppelin-framework-proposal-and-development-roadmap-fdfa9a3a32ab)
- [https://pdaian.com/blog/chasing-the-dao-attackers-wake](https://pdaian.com/blog/chasing-the-dao-attackers-wake)
- [http://www.comp.nus.edu.sg/~loiluu/papers/oyente.pdf](http://www.comp.nus.edu.sg/~loiluu/papers/oyente.pdf)
# Các chương trình thưởng cho những người tìm ra lỗi
Sau đây là các chương trình tiền thưởng cho lỗi đang diễn ra, tập trung vào việc tìm lỗi cuar các hợp đồng thông minh.
- [0xProject](https://0xproject.com/wiki#Bug-Bounty)
- [Airswap](https://medium.com/fluidity/smart-contracts-and-bug-bounty-ad75733eb53f)
- [Augur](https://www.augur.net/bounty/)
- [Aragon](https://wiki.aragon.org/dev/bug_bounty/)
- [BrickBlock](https://blog.brickblock.io/join-the-brickblock-bug-bounty-program-7b431f2bcc02)
- [Colony.io](https://blog.colony.io/announcing-the-colony-network-bug-bounty-f44cabaca9a3/)
- [Ethereum Foundation](https://bounty.ethereum.org/#bounty-scope)
- [Etherscan.io](https://etherscan.io/bugbounty)
- [Gitcoin Bounties](https://gitcoin.co/explorer)
- [MelonPort](https://melonport.com/bug-bounty)
- [Parity](https://www.parity.io/bug-bounty/)
- [Raiden.network](https://raiden.network/bug-bounty.html)
## Reviewers
## The following people have reviewed this document (date and commit they reviewed in parentheses): Bill Gleim (07/29/2016 3495fb5) Bill Gleim (03/15/2017 0244f4e)
## License
Licensed under [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0)
Licensed under [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)
================================================
FILE: README-zh.md
================================================
# 以太坊智能合约 —— 最佳安全开发指南
**Notice: this translation was generously provided by a contributor. The maintainers are not able to verify the content. Any issues or PRs to help improve it are welcome.**
\*本文翻译自:https://github.com/ConsenSys/smart-contract-best-practices
为了使语句表达更加贴切,个别地方未按照原文逐字逐句翻译,如有出入请以原文为准。\*
**主要章节如下**:
- [**Solidity安全贴士**](#solidity-tips)
- [**已知的攻击手段**](#known-attacks)
- [***竞态***](#race-conditions)
- [***可重入***](#reentrancy)
- [***交易顺序依赖***](#transaction-ordering-dependence)
- [***针对Gas的攻击***](#dos-with-block-gas-limit)
- [***整数上溢/整数下溢***](#integer-overflow-and-underflow)
- [**软件工程开发技巧**](#eng-techniques)
- [**参考文献**](#bibliography)
这篇文档旨在为Solidity开发人员提供一些智能合约的安全准则(**security baseline**)。当然也包括智能合约的**安全开发理念、bug赏金计划指南、文档例程以及工具。**
我们邀请社区对该文档提出修改或增补建议,欢迎各种合并请求(Pull Request)。若有相关的文章或者博客的发表,也清将其加入到[参考文献](#bibliography)中,具体详情请参见我们的[社区贡献指南](CONTRIBUTING.md)。
#### 更多期待内容
我们欢迎并期待社区开发者贡献以下几个方面的内容:
- Solidity代码测试(包括代码结构,程序框架 以及 常见软件工程测试)
- 智能合约开发经验总结,以及更广泛的基于区块链的开发技巧分享
## 基本理念
<a name="general-philosophy"></a>
以太坊和其他复杂的区块链项目都处于早期阶段并且有很强的实验性质。因此,随着新的bug和安全漏洞被发现,新的功能不断被开发出来,其面临的安全威胁也是不断变化的。这篇文章对于开发人员编写安全的智能合约来说只是个开始。
开发智能合约需要一个全新的工程思维,它不同于我们以往项目的开发。因为它犯错的代价是巨大的,并且很难像传统软件那样轻易的打上补丁。就像直接给硬件编程或金融服务类软件开发,相比于web开发和移动开发都有更大的挑战。因此,仅仅防范已知的漏洞是不够的,你还需要学习新的开发理念:
- **对可能的错误有所准备**。任何有意义的智能合约或多或少都存在错误。因此你的代码必须能够正确的处理出现的bug和漏洞。始终保证以下规则:
- 当智能合约出现错误时,停止合约,(“断路开关”)
- 管理账户的资金风险(限制(转账)速率、最大(转账)额度)
- 有效的途径来进行bug修复和功能提升
- [**谨慎发布智能合约**](#contract-rollout)。 尽量在正式发布智能合约之前发现并修复可能的bug。
- 对智能合约进行彻底的测试,并在任何新的攻击手法被发现后及时的测试(包括已经发布的合约)
- 从alpha版本在测试网(testnet)上发布开始便提供[bug赏金计划](#bounties)
- 阶段性发布,每个阶段都提供足够的测试
- **保持智能合约的简洁**。复杂会增加出错的风险。
- 确保智能合约逻辑简洁
- 确保合约和函数模块化
- 使用已经被广泛使用的合约或工具(比如,不要自己写一个随机数生成器)
- 条件允许的话,清晰明了比性能更重要
- 只在你系统的去中心化部分使用区块链
- **保持更新**。通过下一章节所列出的资源来确保获取到最新的安全进展。
- 在任何新的漏洞被发现时检查你的智能合约
- 尽可能快的将使用到的库或者工具更新到最新
- 使用最新的安全技术
- **清楚区块链的特性**。尽管你先前所拥有的编程经验同样适用于以太坊开发,但这里仍然有些陷阱你需要留意:
- 特别小心针对外部合约的调用,因为你可能执行的是一段恶意代码然后更改控制流程
- 清楚你的public function是公开的,意味着可以被恶意调用。(在以太坊上)你的private data也是对他人可见的
- 清楚gas的花费和区块的gas limit
### 基本权衡:简单性与复杂性
<a name="fundamental-tradeoffs"></a>
在评估一个智能合约的架构和安全性时有很多需要权衡的地方。对任何智能合约的建议是在各个权衡点中找到一个平衡点。
从传统软件工程的角度出发:一个理想的智能合约首先需要模块化,能够重用代码而不是重复编写,并且支持组件升级。从智能合约安全架构的角度出发同样如此,模块化和重用被严格审查检验过的合约是最佳策略,特别是在复杂智能合约系统里。
然而,这里有几个重要的例外,它们从合约安全和传统软件工程两个角度考虑,所得到的重要性排序可能不同。当中每一条,都需要针对智能合约系统的特点找到最优的组合方式来达到平衡。
- 固化 vs 可升级
- 一体化 vs 模块化
- 重复 vs 可重用
#### 固化 vs 可升级
在很多文档或者开发指南中,包括该指南,都会强调延展性比如:可终止,可升级或可更改的特性,不过对于智能合约来说,延展性和安全之间是个*基本权衡*。
延展性会增加程序复杂性和潜在的攻击面。对于那些只在特定的时间段内提供有限的功能的智能合约,简单性比复杂性显得更加高效,比如无管治功能,有限短期内使用的代币发行的智能合约系统(governance-fee,finite-time-frame token-sale contracts)。
#### 一体化 vs 模块化
一个一体化的独立的智能合约把所有的变量和模块都放到一个合约中。尽管只有少数几个大家熟知的智能合约系统真的做到了大体量,但在将数据和流程都放到一个合约中还是享有部分优点--比如,提高代码审核(code review)效率。
和在这里讨论的其他权衡点一样,传统软件开发策略和从合约安全角度出发考虑,两者不同主要在对于简单、短生命周期的智能合约;对于更复杂、长生命周期的智能合约,两者策略理念基本相同。
#### 重复 vs 可重用
从软件工程角度看,智能合约系统希望在合理的情况下最大程度地实现重用。 在Solidity中重用合约代码有很多方法。 使用**你拥有的**以前部署的经过验证的智能合约是实现代码重用的最安全的方式。
在以前所拥有已部署智能合约不可重用时重复还是很需要的。 现在[Live Libs](https://github.com/ConsenSys/live-libs) 和[Zeppelin Solidity](https://github.com/OpenZeppelin/zeppelin-solidity) 正寻求提供安全的智能合约组件使其能够被重用而不需要每次都重新编写。任何合约安全性分析都必须标明重用代码,特别是以前没有建立与目标智能合同系统中处于风险中的资金相称的信任级别的代码。
## 安全通知
以下这些地方通常会通报在Ethereum或Solidity中新发现的漏洞。安全通告的官方来源是Ethereum Blog,但是一般漏洞都会在其他地方先被披露和讨论。
- [Ethereum Blog](https://blog.ethereum.org/): The official Ethereum blog
- [Ethereum Blog - Security only](https://blog.ethereum.org/category/security/): 所有相关博客都带有**Security**标签
- [Ethereum Gitter](https://gitter.im/orgs/ethereum/rooms) 聊天室
- [Solidity](https://gitter.im/ethereum/solidity)
- [Go-Ethereum](https://gitter.im/ethereum/go-ethereum)
- [CPP-Ethereum](https://gitter.im/ethereum/cpp-ethereum)
- [Research](https://gitter.im/ethereum/research)
- [Reddit](https://www.reddit.com/r/ethereum)
- [Network Stats](https://ethstats.net/)
强烈建议你经常浏览这些网站,尤其是他们提到的可能会影响你的智能合约的漏洞。
另外, 这里列出了以太坊参与安全模块相关的核心开发成员, 浏览 [bibliography](https://github.com/ConsenSys/smart-contract-best-practices#smart-contract-security-bibliography) 获取更多信息。
- **Vitalik Buterin**: [Twitter](https://twitter.com/vitalikbuterin), [Github](https://github.com/vbuterin), [Reddit](https://www.reddit.com/user/vbuterin), [Ethereum Blog](https://blog.ethereum.org/author/vitalik-buterin/)
- **Dr. Christian Reitwiessner**: [Twitter](https://twitter.com/ethchris), [Github](https://github.com/chriseth), [Ethereum Blog](https://blog.ethereum.org/author/christian_r/)
- **Dr. Gavin Wood**: [Twitter](https://twitter.com/gavofyork), [Blog](http://gavwood.com/), [Github](https://github.com/gavofyork)
- **Vlad Zamfir**: [Twitter](https://twitter.com/vladzamfir), [Github](https://github.com/vladzamfir), [Ethereum Blog](https://blog.ethereum.org/author/vlad/)
除了关注核心开发成员,参与到各个区块链安全社区也很重要,因为安全漏洞的披露或研究将通过各方进行。
<a name="solidity-tips"></a>
## 关于使用Solidity开发的智能合约安全建议
### 外部调用
#### 尽量避免外部调用
<a name="avoid-external-calls"></a>
调用不受信任的外部合约可能会引发一系列意外的风险和错误。外部调用可能在其合约和它所依赖的其他合约内执行恶意代码。因此,每一个外部调用都会有潜在的安全威胁,尽可能的从你的智能合约内移除外部调用。当无法完全去除外部调用时,可以使用这一章节其他部分提供的建议来尽量减少风险。
<a name="send-vs-call-value"></a>
#### 仔细权衡“send()”、“transfer()”、以及“call.value()”
当转账Ether时,需要仔细权衡“someAddress.send()”、“someAddress.transfer()”、和“someAddress.call.value()()”之间的差别。
- `x.transfer(y)`和`if (!x.send(y)) throw;`是等价的。send是transfer的底层实现,建议尽可能直接使用transfer。
- `someAddress.send()`和`someAddress.transfer()` 能保证[可重入](#reentrancy) **安全** 。
尽管这些外部智能合约的函数可以被触发执行,但补贴给外部智能合约的2,300 gas,意味着仅仅只够记录一个event到日志中。
- `someAddress.call.value()()` 将会发送指定数量的Ether并且触发对应代码的执行。被调用的外部智能合约代码将享有所有剩余的gas,通过这种方式转账是很容易有可重入漏洞的,非常 **不安全**。
使用`send()` 或`transfer()` 可以通过制定gas值来预防可重入, 但是这样做可能会导致在和合约调用fallback函数时出现问题,由于gas可能不足,而合约的fallback函数执行至少需要2,300 gas消耗。
一种被称为[*push* 和*pull*](#favor-pull-over-push-payments)的 机制试图来平衡两者, 在 *push* 部分使用`send()` 或`transfer()`,在*pull* 部分使用`call.value()()`。(\*译者注:在需要对外未知地址转账Ether时使用`send()` 或`transfer()`,已知明确内部无恶意代码的地址转账Ether使用`call.value()()`)
需要注意的是使用`send()` 或`transfer()` 进行转账并不能保证该智能合约本身重入安全,它仅仅只保证了这次转账操作时重入安全的。
<a name="handle-external-errors"></a>
#### 处理外部调用错误
Solidity提供了一系列在raw address上执行操作的底层方法,比如: `address.call()`,`address.callcode()`, `address.delegatecall()`和`address.send`。这些底层方法不会抛出异常(throw),只是会在遇到错误时返回false。另一方面, *contract calls* (比如,`ExternalContract.doSomething()`))会自动传递异常,(比如,`doSomething()`抛出异常,那么`ExternalContract.doSomething()` 同样会进行`throw`) )。
如果你选择使用底层方法,一定要检查返回值来对可能的错误进行处理。
```sh
// bad
someAddress.send(55);
someAddress.call.value(55)(); // this is doubly dangerous, as it will forward all remaining gas and doesn't check for result
someAddress.call.value(100)(bytes4(sha3("deposit()"))); // if deposit throws an exception, the raw call() will only return false and transaction will NOT be reverted
// good
if(!someAddress.send(55)) {
// Some failure code
}
ExternalContract(someAddress).deposit.value(100);
```
<a name="expect-control-flow-loss"></a>
#### 不要假设你知道外部调用的控制流程
无论是使用**raw calls** 或是**contract calls**,如果这个`ExternalContract`是不受信任的都应该假设存在恶意代码。即使`ExternalContract`不包含恶意代码,但它所调用的其他合约代码可能会包含恶意代码。一个具体的危险例子便是恶意代码可能会劫持控制流程导致竞态。(浏览[Race Conditions](https://github.com/ConsenSys/smart-contract-best-practices/#race-conditions)获取更多关于这个问题的讨论)
<a name="favor-pull-over-push-payments"></a>
#### 对于外部合约优先使用*pull* 而不是*push*
外部调用可能会有意或无意的失败。为了最小化这些外部调用失败带来的损失,通常好的做法是将外部调用函数与其余代码隔离,最终是由收款发起方负责发起调用该函数。这种做法对付款操作尤为重要,比如让用户自己撤回资产而不是直接发送给他们。(*译者注:事先设置需要付给某一方的资产的值,表明接收方可以从当前账户撤回资金的额度,然后由接收方调用当前合约提现函数完成转账*)。(这种方法同时也避免了造成 [gas limit相关问题](https://github.com/ConsenSys/smart-contract-best-practices/#dos-with-block-gas-limit)。)
```sh
// bad
contract auction {
address highestBidder;
uint highestBid;
function bid() payable {
if (msg.value < highestBid) throw;
if (highestBidder != 0) {
if (!highestBidder.send(highestBid)) { // if this call consistently fails, no one else can bid
throw;
}
}
highestBidder = msg.sender;
highestBid = msg.value;
}
}
// good
contract auction {
address highestBidder;
uint highestBid;
mapping(address => uint) refunds;
function bid() payable external {
if (msg.value < highestBid) throw;
if (highestBidder != 0) {
refunds[highestBidder] += highestBid; // record the refund that this user can claim
}
highestBidder = msg.sender;
highestBid = msg.value;
}
function withdrawRefund() external {
uint refund = refunds[msg.sender];
refunds[msg.sender] = 0;
if (!msg.sender.send(refund)) {
refunds[msg.sender] = refund; // reverting state because send failed
}
}
}
```
<a name="mark-untrusted-contracts"></a>
#### 标记不受信任的合约
当你自己的函数调用外部合约时,你的变量、方法、合约接口命名应该表明和他们可能是不安全的。
```sh
// bad
Bank.withdraw(100); // Unclear whether trusted or untrusted
function makeWithdrawal(uint amount) { // Isn't clear that this function is potentially unsafe
Bank.withdraw(amount);
}
// good
UntrustedBank.withdraw(100); // untrusted external call
TrustedBank.withdraw(100); // external but trusted bank contract maintained by XYZ Corp
function makeUntrustedWithdrawal(uint amount) {
UntrustedBank.withdraw(amount);
}
```
### 使用`assert()`强制不变性
当断言条件不满足时将触发断言保护 -- 比如不变的属性发生了变化。举个例子,代币在以太坊上的发行比例,在代币的发行合约里可以通过这种方式得到解决。断言保护经常需要和其他技术组合使用,比如当断言被触发时先挂起合约然后升级。(否则将一直触发断言,你将陷入僵局)
例如:
```
contract Token {
mapping(address => uint) public balanceOf;
uint public totalSupply;
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
totalSupply += msg.value;
assert(address(this).balance >= totalSupply);
}
}
```
注意断言保护 **不是** 严格意义的余额检测, 因为智能合约可以不通过`deposit()` 函数被 [强制发送Ether](#ether-forcibly-sent)!
### 正确使用`assert()`和`require()`
在Solidity 0.4.10 中`assert()`和`require()`被加入。`require(condition)`被用来验证用户的输入,如果条件不满足便会抛出异常,应当使用它验证所有用户的输入。 `assert(condition)` 在条件不满足也会抛出异常,但是最好只用于固定变量:内部错误或你的智能合约陷入无效的状态。遵循这些范例,使用分析工具来验证永远不会执行这些无效操作码:意味着代码中不存在任何不变量,并且代码已经正式验证。
<a name="beware-rounding-with-integer-division"></a>
### 小心整数除法的四舍五入
所有整数除数都会四舍五入到最接近的整数。 如果您需要更高精度,请考虑使用乘数,或存储分子和分母。
(将来Solidity会有一个fixed-point类型来让这一切变得容易。)
```sh
// bad
uint x = 5 / 2; // Result is 2, all integer divison rounds DOWN to the nearest integer
// good
uint multiplier = 10;
uint x = (5 * multiplier) / 2;
uint numerator = 5;
uint denominator = 2;
```
<a name="ether-forcibly-sent"></a>
### 记住Ether可以被强制发送到账户
谨慎编写用来检查账户余额的不变量。
攻击者可以强制发送wei到任何账户,而且这是不能被阻止的(即使让fallback函数`throw`也不行)
攻击者可以仅仅使用1 wei来创建一个合约,然后调用`selfdestruct(victimAddress)`。在`victimAddress`中没有代码被执行,所以这是不能被阻止的。
### 不要假设合约创建时余额为零
攻击者可以在合约创建之前向合约的地址发送wei。合约不能假设它的初始状态包含的余额为零。浏览[issue 61](https://github.com/ConsenSys/smart-contract-best-practices/issues/61) 获取更多信息。
### 记住链上的数据是公开的
许多应用需要提交的数据是私有的,直到某个时间点才能工作。游戏(比如,链上游戏rock-paper-scissors(石头剪刀布))和拍卖机(比如,sealed-bid second-price auctions)是两个典型的例子。如果你的应用存在隐私保护问题,一定要避免过早发布用户信息。
例如:
- 在游戏石头剪刀布中,需要参与游戏的双方提交他们“行动计划”的hash值,然后需要双方随后提交他们的行动计划;如果双方的“行动计划”和先前提交的hash值对不上则抛出异常。
- 在拍卖中,要求玩家在初始阶段提交其所出价格的hash值(以及超过其出价的保证金),然后在第二阶段提交他们所出价格的资金。
- 当开发一个依赖随机数生成器的应用时,正确的顺序应当是(1)玩家提交行动计划,(2)生成随机数,(3)玩家支付。产生随机数是一个值得研究的领域;当前最优的解决方案包括比特币区块头(通过http://btcrelay.org验证),hash-commit-reveal方案(比如,一方产生number后,将其散列值提交作为对这个number的“提交”,然后在随后再暴露这个number本身)和 [RANDAO](http://github.com/randao/randao)。
- 如果你正在实现频繁的批量拍卖,那么hash-commit机制也是个不错的选择。
### 权衡Abstract合约和Interfaces
Interfaces和Abstract合约都是用来使智能合约能更好的被定制和重用。Interfaces是在Solidity 0.4.11中被引入的,和Abstract合约很像但是不能定义方法只能申明。Interfaces存在一些限制比如不能够访问storage或者从其他Interfaces那继承,通常这些使Abstract合约更实用。尽管如此,Interfaces在实现智能合约之前的设计智能合约阶段仍然有很大用处。另外,需要注意的是如果一个智能合约从另一个Abstract合约继承而来那么它必须实现所有Abstract合约内的申明并未实现的函数,否则它也会成为一个Abstract合约。
### 在双方或多方参与的智能合约中,参与者可能会“脱机离线”后不再返回
不要让退款和索赔流程依赖于参与方执行的某个特定动作而没有其他途径来获取资金。比如,在石头剪刀布游戏中,一个常见的错误是在两个玩家提交他们的行动计划之前不要付钱。然而一个恶意玩家可以通过一直不提交它的行动计划来使对方蒙受损失 -- 事实上,如果玩家看到其他玩家泄露的行动计划然后决定他是否会损失(译者注:发现自己输了),那么他完全有理由不再提交他自己的行动计划。这些问题也同样会出现在通道结算。当这些情形出现导致问题后:(1)提供一种规避非参与者和参与者的方式,可能通过设置时间限制,和(2)考虑为参与者提供额外的经济激励,以便在他们应该这样做的所有情况下仍然提交信息。
<a name="keep-fallback-functions-simple"></a>
### 使Fallback函数尽量简单
[Fallback函数](http://solidity.readthedocs.io/en/latest/contracts.html#fallback-function)在合约执行消息发送没有携带参数(或当没有匹配的函数可供调用)时将会被调用,而且当调用 `.send()` or `.transfer()`时,只会有2,300 gas 用于失败后fallback函数的执行(*译者注:合约收到Ether也会触发fallback函数执行*)。如果你希望能够监听`.send()`或`.transfer()`接收到Ether,则可以在fallback函数中使用event(译者注:让客户端监听相应事件做相应处理)。谨慎编写fallback函数以免gas不够用。
```sh
// bad
function() payable { balances[msg.sender] += msg.value; }
// good
function deposit() payable external { balances[msg.sender] += msg.value; }
function() payable { LogDepositReceived(msg.sender); }
```
<a name="mark-visibility"></a>
### 明确标明函数和状态变量的可见性
明确标明函数和状态变量的可见性。函数可以声明为 `external`,`public`, `internal` 或 `private`。 分清楚它们之间的差异, 例如`external` 可能已够用而不是使用 `public`。对于状态变量,`external`是不可能的。明确标注可见性将使得更容易避免关于谁可以调用该函数或访问变量的错误假设。
```sh
// bad
uint x; // the default is private for state variables, but it should be made explicit
function buy() { // the default is public
// public code
}
// good
uint private y;
function buy() external {
// only callable externally
}
function utility() public {
// callable externally, as well as internally: changing this code requires thinking about both cases.
}
function internalAction() internal {
// internal code
}
```
<a name="lock-pragmas"></a>
### 将程序锁定到特定的编译器版本
智能合约应该应该使用和它们测试时使用最多的编译器相同的版本来部署。锁定编译器版本有助于确保合约不会被用于最新的可能还有bug未被发现的编译器去部署。智能合约也可能会由他人部署,而pragma标明了合约作者希望使用哪个版本的编译器来部署合约。
```sh
// bad
pragma solidity ^0.4.4;
// good
pragma solidity 0.4.4;
```
<a name="beware-division-by-zero"></a>
(*译者注:这当然也会付出兼容性的代价*)
### 小心分母为零 (Solidity \< 0.4)
早于0.4版本, 当一个数尝试除以零时,Solidity [返回zero](https://github.com/ethereum/solidity/issues/670) 并没有 `throw` 一个异常。确保你使用的Solidity版本至少为 0.4。
<a name="differentiate-functions-events"></a>
### 区分函数和事件
为了防止函数和事件(Event)产生混淆,命名一个事件使用大写并加入前缀(我们建议**LOG**)。对于函数, 始终以小写字母开头,构造函数除外。
```sh
// bad
event Transfer() {}
function transfer() {}
// good
event LogTransfer() {}
function transfer() external {}
```
<a name="prefer-newer-constructs"></a>
### 使用Solidity更新的构造器
更合适的构造器/别名,如`selfdestruct`(旧版本为`suicide`)和`keccak256`(旧版本为`sha3`)。 像`require(msg.sender.send(1 ether))`的模式也可以简化为使用`transfer()`,如`msg.sender.transfer(1 ether)`。
<a name="known-attacks"></a>
## 已知的攻击
<a name="race-conditions"></a>
### 竞态<sup><a href='#footnote-race-condition-terminology'>\*</a></sup>
调用外部合约的主要危险之一是它们可以接管控制流,并对调用函数意料之外的数据进行更改。 这类bug有多种形式,导致DAO崩溃的两个主要错误都是这种错误。
<a name="reentrancy"></a>
#### 重入
这个版本的bug被注意到是其可以在第一次调用这个函数完成之前被多次重复调用。对这个函数不断的调用可能会造成极大的破坏。
```sh
// INSECURE
mapping (address => uint) private userBalances;
function withdrawBalance() public {
uint amountToWithdraw = userBalances[msg.sender];
if (!(msg.sender.call.value(amountToWithdraw)())) { throw; } // At this point, the caller's code is executed, and can call withdrawBalance again
userBalances[msg.sender] = 0;
}
```
(*译者注:使用msg.sender.call.value()())传递给fallback函数可用的gas是当前剩余的所有gas,在这里,假如从你账户执行提现操作的恶意合约的fallback函数内递归调用你的withdrawBalance()便可以从你的账户转走更多的币。*)
可以看到当调msg.sender.call.value()()时,并没有将userBalances\[msg.sender\] 清零,于是在这之前可以成功递归调用很多次withdrawBalance()函数。 一个非常相像的bug便是出现在针对 DAO 的攻击。
在给出来的例子中,最好的方法是 [ 使用 `send()` 而不是`call.value()()`](https://github.com/ConsenSys/smart-contract-best-practices#send-vs-call-value)。这将避免多余的代码被执行。
然而,如果你没法完全移除外部调用,另一个简单的方法来阻止这个攻击是确保你在完成你所有内部工作之前不要进行外部调用:
```sh
mapping (address => uint) private userBalances;
function withdrawBalance() public {
uint amountToWithdraw = userBalances[msg.sender];
userBalances[msg.sender] = 0;
if (!(msg.sender.call.value(amountToWithdraw)())) { throw; } // The user's balance is already 0, so future invocations won't withdraw anything
}
```
注意如果你有另一个函数也调用了 `withdrawBalance()`, 那么这里潜在的存在上面的攻击,所以你必须认识到任何调用了不受信任的合约代码的合约也是不受信任的。继续浏览下面的相关潜在威胁解决办法的讨论。
#### 跨函数竞态
攻击者也可以使用两个共享状态变量的不同的函数来进行类似攻击。
```sh
// INSECURE
mapping (address => uint) private userBalances;
function transfer(address to, uint amount) {
if (userBalances[msg.sender] >= amount) {
userBalances[to] += amount;
userBalances[msg.sender] -= amount;
}
}
function withdrawBalance() public {
uint amountToWithdraw = userBalances[msg.sender];
if (!(msg.sender.call.value(amountToWithdraw)())) { throw; } // At this point, the caller's code is executed, and can call transfer()
userBalances[msg.sender] = 0;
}
```
着这个例子中,攻击者在他们外部调用`withdrawBalance`函数时调用`transfer()`,如果这个时候`withdrawBalance`还没有执行到`userBalances[msg.sender] = 0;`这里,那么他们的余额就没有被清零,那么他们就能够调用`transfer()`转走代币尽管他们其实已经收到了代币。这个弱点也可以被用到对DAO的攻击。
同样的解决办法也会管用,在执行转账操作之前先清零。也要注意在这个例子中所有函数都是在同一个合约内。然而,如果这些合约共享了状态,同样的bug也可以发生在跨合约调用中。
#### 竞态解决办法中的陷阱
由于竞态既可以发生在跨函数调用,也可以发生在跨合约调用,任何只是避免重入的解决办法都是不够的。
作为替代,我们建议首先应该完成所有内部的工作然后再执行外部调用。这个规则可以避免竞态发生。然而,你不仅应该避免过早调用外部函数而且应该避免调用那些也调用了外部函数的外部函数。例如,下面的这段代码是不安全的:
```sh
// INSECURE
mapping (address => uint) private userBalances;
mapping (address => bool) private claimedBonus;
mapping (address => uint) private rewardsForA;
function withdraw(address recipient) public {
uint amountToWithdraw = userBalances[recipient];
rewardsForA[recipient] = 0;
if (!(recipient.call.value(amountToWithdraw)())) { throw; }
}
function getFirstWithdrawalBonus(address recipient) public {
if (claimedBonus[recipient]) { throw; } // Each recipient should only be able to claim the bonus once
rewardsForA[recipient] += 100;
withdraw(recipient); // At this point, the caller will be able to execute getFirstWithdrawalBonus again.
claimedBonus[recipient] = true;
}
```
尽管`getFirstWithdrawalBonus()` 没有直接调用外部合约,但是它调用的`withdraw()` 却会导致竞态的产生。在这里你不应该认为`withdraw()`是受信任的。
```sh
mapping (address => uint) private userBalances;
mapping (address => bool) private claimedBonus;
mapping (address => uint) private rewardsForA;
function untrustedWithdraw(address recipient) public {
uint amountToWithdraw = userBalances[recipient];
rewardsForA[recipient] = 0;
if (!(recipient.call.value(amountToWithdraw)())) { throw; }
}
function untrustedGetFirstWithdrawalBonus(address recipient) public {
if (claimedBonus[recipient]) { throw; } // Each recipient should only be able to claim the bonus once
claimedBonus[recipient] = true;
rewardsForA[recipient] += 100;
untrustedWithdraw(recipient); // claimedBonus has been set to true, so reentry is impossible
}
```
除了修复bug让重入不可能成功,[不受信任的函数也已经被标记出来](https://github.com/ConsenSys/smart-contract-best-practices#mark-untrusted-contracts) 。同样的情景: `untrustedGetFirstWithdrawalBonus()` 调用`untrustedWithdraw()`, 而后者调用了外部合约,因此在这里`untrustedGetFirstWithdrawalBonus()` 是不安全的。
另一个经常被提及的解决办法是(*译者注:像传统多线程编程中一样*)使用[mutex](https://en.wikipedia.org/wiki/Mutual_exclusion)。它会"lock" 当前状态,只有锁的当前拥有者能够更改当前状态。一个简单的例子如下:
```sh
// Note: This is a rudimentary example, and mutexes are particularly useful where there is substantial logic and/or shared state
mapping (address => uint) private balances;
bool private lockBalances;
function deposit() payable public returns (bool) {
if (!lockBalances) {
lockBalances = true;
balances[msg.sender] += msg.value;
lockBalances = false;
return true;
}
throw;
}
function withdraw(uint amount) payable public returns (bool) {
if (!lockBalances && amount > 0 && balances[msg.sender] >= amount) {
lockBalances = true;
if (msg.sender.call(amount)()) { // Normally insecure, but the mutex saves it
balances[msg.sender] -= amount;
}
lockBalances = false;
return true;
}
throw;
}
```
如果用户试图在第一次调用结束前第二次调用 `withdraw()`,将会被锁住。 这看上去很有效果,但当你使用多个合约互相交互时问题变得严峻了。 下面是一段不安全的代码:
```
// INSECURE
contract StateHolder {
uint private n;
address private lockHolder;
function getLock() {
if (lockHolder != 0) { throw; }
lockHolder = msg.sender;
}
function releaseLock() {
lockHolder = 0;
}
function set(uint newState) {
if (msg.sender != lockHolder) { throw; }
n = newState;
}
}
```
攻击者可以只调用`getLock()`,然后就不再调用 `releaseLock()`。如果他们真这样做,那么这个合约将会被永久锁住,任何接下来的操作都不会发生了。如果你使用mutexs来避免竞态,那么一定要确保没有地方能够打断锁的进程或绝不释放锁。(这里还有一个潜在的威胁,比如死锁和活锁。在你决定使用锁之前最好大量阅读相关文献(*译者注:这是真的,传统的在多线程环境下对锁的使用一直是个容易犯错的地方*))
<a name="footnote-race-condition-terminology"></a>
<div style='font-size: 80%; display: inline;'>* 有些人可能会发反对使用该术语 <i>竞态</i>,因为以太坊并没有真正意思上实现并行执行。然而在逻辑上依然存在对资源的竞争,同样的陷阱和潜在的解决方案。 </div>
<a name="transaction-ordering-dependence"></a>
### 交易顺序依赖(TOD) / 前面的先运行
以上是涉及攻击者在**单个交易**内执行恶意代码产生竞态的示例。接下来演示在区块链本身运作原理导致的竞态:(同一个block内的)交易顺序很容易受到操纵。
由于交易在短暂的时间内会先存放到mempool中,所以在矿工将其打包进block之前,是可以知道会发生什么动作的。这对于一个去中心化的市场来说是麻烦的,因为可以查看到代币的交易信息,并且可以在它被打包进block之前改变交易顺序。避免这一点很困难,因为它归结为具体的合同本身。例如,在市场上,最好实施批量拍卖(这也可以防止高频交易问题)。 另一种使用预提交方案的方法(“我稍后会提供详细信息”)。
<a name="timestamp-dependence"></a>
### 时间戳依赖
请注意,块的时间戳可以由矿工操纵,并且应考虑时间戳的所有直接和间接使用。 **区块数量**和**平均出块时间**可用于估计时间,但这不是区块时间在未来可能改变(例如Casper期望的更改)的证明。
```sh
uint someVariable = now + 1;
if (now % 2 == 0) { // the now can be manipulated by the miner
}
if ((someVariable - 100) % 2 == 0) { // someVariable can be manipulated by the miner
}
```
<a name="integer-overflow-and-underflow"></a>
### 整数上溢和下溢
这里大概有 [20关于上溢和下溢的例子](https://github.com/ethereum/solidity/issues/796#issuecomment-253578925)。
考虑如下这个简单的转账操作:
```sh
mapping (address => uint256) public balanceOf;
// INSECURE
function transfer(address _to, uint256 _value) {
/* Check if sender has balance */
if (balanceOf[msg.sender] < _value)
throw;
/* Add and subtract new balances */
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
}
// SECURE
function transfer(address _to, uint256 _value) {
/* Check if sender has balance and for overflows */
if (balanceOf[msg.sender] < _value || balanceOf[_to] + _value < balanceOf[_to])
throw;
/* Add and subtract new balances */
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
}
```
如果余额到达**uint**的最大值(2^256),便又会变为0。应当检查这里。溢出是否与之相关取决于具体的实施方式。想想uint值是否有机会变得这么大或和谁会改变它的值。如果任何用户都有权利更改uint的值,那么它将更容易受到攻击。如果只有管理员能够改变它的值,那么它可能是安全的,因为没有别的办法可以跨越这个限制。
对于下溢同样的道理。如果一个uint别改变后小于0,那么将会导致它下溢并且被设置成为最大值(2^256)。
对于较小数字的类型比如uint8、uint16、uint24等也要小心:他们更加容易达到最大值。
<a name="dos-with-unexpected-throw"></a>
### 通过(Unexpected) Throw发动DoS
考虑如下简单的智能合约:
```sh
// INSECURE
contract Auction {
address currentLeader;
uint highestBid;
function bid() payable {
if (msg.value <= highestBid) { throw; }
if (!currentLeader.send(highestBid)) { throw; } // Refund the old leader, and throw if it fails
currentLeader = msg.sender;
highestBid = msg.value;
}
}
```
当有更高竞价时,它将试图退款给曾经最高竞价人,如果退款失败则会抛出异常。这意味着,恶意投标人可以成为当前最高竞价人,同时确保对其地址的任何退款**始终**失败。这样就可以阻止任何人调用“bid()”函数,使自己永远保持领先。建议向之前所说的那样建立[基于pull的支付系统](https://github.com/ConsenSys/smart-contract-best-practices/#favor-pull-over-push-payments) 。
另一个例子是合约可能通过数组迭代来向用户支付(例如,众筹合约中的支持者)时。 通常要确保每次付款都成功。 如果没有,应该抛出异常。 问题是,如果其中一个支付失败,您将恢复整个支付系统,这意味着该循环将永远不会完成。 因为一个地址没有转账成功导致其他人都没得到报酬。
```sh
address[] private refundAddresses;
mapping (address => uint) public refunds;
// bad
function refundAll() public {
for(uint x; x < refundAddresses.length; x++) { // arbitrary length iteration based on how many addresses participated
if(refundAddresses[x].send(refunds[refundAddresses[x]])) {
throw; // doubly bad, now a single failure on send will hold up all funds
}
}
}
```
再一次强调,同样的解决办法: [优先使用pull 而不是push支付系统](#favor-pull-over-push-payments)。
<a name="dos-with-block-gas-limit"></a>
### 通过区块Gas Limit发动DoS
在先前的例子中你可能已经注意到另一个问题:一次性向所有人转账,很可能会导致达到以太坊区块gas limit的上限。以太坊规定了每一个区块所能花费的gas limit,如果超过你的交易便会失败。
即使没有故意的攻击,这也可能导致问题。然而,最为糟糕的是如果gas的花费被攻击者操控。在先前的例子中,如果攻击者增加一部分收款名单,并设置每一个收款地址都接收少量的退款。这样一来,更多的gas将会被花费从而导致达到区块gas limit的上限,整个转账的操作也会以失败告终。
又一次证明了 [优先使用pull 而不是push支付系统](#favor-pull-over-push-payments)。
如果你实在必须通过遍历一个变长数组来进行转账,最好估计完成它们大概需要多少个区块以及多少笔交易。然后你还必须能够追踪得到当前进行到哪以便当操作失败时从那里开始恢复,举个例子:
```sh
struct Payee {
address addr;
uint256 value;
}
Payee payees[];
uint256 nextPayeeIndex;
function payOut() {
uint256 i = nextPayeeIndex;
while (i < payees.length && msg.gas > 200000) {
payees[i].addr.send(payees[i].value);
i++;
}
nextPayeeIndex = i;
}
```
如上所示,你必须确保在下一次执行`payOut()`之前另一些正在执行的交易不会发生任何错误。如果必须,请使用上面这种方式来处理。
<a name="call-depth-attack"></a>
### ~~Call Depth攻击~~
由于[EIP 150](https://github.com/ethereum/EIPs/issues/150) 进行的硬分叉,Call Depth攻击已经无法实施<sup><a href='http://ethereum.stackexchange.com/questions/9398/how-does-eip-150-change-the-call-depth-attack'>\*</a></sup> (由于以太坊限制了Call Depth最大为1024,确保了在达到最大深度之前gas都能被正确使用)
<a name="eng-techniques"></a>
## 软件工程开发技巧
正如我们先前在[基本理念](#general-philosophy) 章节所讨论的那样,避免自己遭受已知的攻击是不够的。由于在链上遭受攻击损失是巨大的,因此你还必须改变你编写软件的方式来抵御各种攻击。
我们倡导“时刻准备失败",提前知道你的代码是否安全是不可能的。然而,我们可以允许合约以可预知的方式失败,然后最小化失败带来的损失。本章将带你了解如何为可预知的失败做准备。
注意:当你向你的系统添加新的组件时总是伴随着风险的。一个不良设计本身会成为漏洞-一些精心设计的组件在交互过程中同样会出现漏洞。仔细考虑你在合约里使用的每一项技术,以及如何将它们整合共同创建一个稳定可靠的系统。
### 升级有问题的合约
如果代码中发现了错误或者需要对某些部分做改进都需要更改代码。在以太坊上发现一个错误却没有办法处理他们是太多意义的。
关于如何在以太坊上设计一个合约升级系统是一个正处于积极研究的领域,在这篇文章当中我们没法覆盖所有复杂的领域。然而,这里有两个通用的基本方法。最简单的是专门设计一个注册合约,在注册合约中保存最新版合约的地址。对于合约使用者来说更能实现无缝衔接的方法是设计一个合约,使用它转发调用请求和数据到最新版的合约。
无论采用何种技术,组件之间都要进行模块化和良好的分离,由此代码的更改才不会破坏原有的功能,造成孤儿数据,或者带来巨大的成本。 尤其是将复杂的逻辑与数据存储分开,这样你在使用更改后的功能时不必重新创建所有数据。
当需要多方参与决定升级代码的方式也是至关重要的。根据你的合约,升级代码可能会需要通过单个或多个受信任方参与投票决定。如果这个过程会持续很长时间,你就必须要考虑是否要换成一种更加高效的方式以防止遭受到攻击,例如[紧急停止或断路器](#circuit-breakers-pause-contract-functionality)。
**Example 1:使用注册合约存储合约的最新版本**
在这个例子中,调用没有被转发,因此用户必须每次在交互之前都先获取最新的合约地址。
```sh
contract SomeRegister {
address backendContract;
address[] previousBackends;
address owner;
function SomeRegister() {
owner = msg.sender;
}
modifier onlyOwner() {
if (msg.sender != owner) {
throw;
}
_;
}
function changeBackend(address newBackend) public
onlyOwner()
returns (bool)
{
if(newBackend != backendContract) {
previousBackends.push(backendContract);
backendContract = newBackend;
return true;
}
return false;
}
}
```
这种方法有两个主要的缺点:
1、用户必须始终查找当前合约地址,否则任何未执行此操作的人都可能会使用旧版本的合约
2、在你替换了合约后你需要仔细考虑如何处理原合约中的数据
另外一种方法是设计一个用来转发调用请求和数据到最新版的合约:
**例2: [使用`DELEGATECALL`](http://ethereum.stackexchange.com/questions/2404/upgradeable-contracts) 转发数据和调用**
```
contract Relay {
address public currentVersion;
address public owner;
modifier onlyOwner() {
if (msg.sender != owner) {
throw;
}
_;
}
function Relay(address initAddr) {
currentVersion = initAddr;
owner = msg.sender; // this owner may be another contract with multisig, not a single contract owner
}
function changeContract(address newVersion) public
onlyOwner()
{
currentVersion = newVersion;
}
function() {
if(!currentVersion.delegatecall(msg.data)) throw;
}
}
```
这种方法避免了先前的问题,但也有自己的问题。它使得你必须在合约里小心的存储数据。如果新的合约和先前的合约有不同的存储层,你的数据可能会被破坏。另外,这个例子中的模式没法从函数里返回值,只负责转发它们,由此限制了它的适用性。(这里有一个[更复杂的实现](https://github.com/ownage-ltd/ether-router) 想通过内联汇编和返回大小的注册表来解决这个问题)
无论你的方法如何,重要的是要有一些方法来升级你的合约,否则当被发现不可避免的错误时合约将没法使用。
### 断路器(暂停合约功能)
由于断路器在满足一定条件时将会停止执行,如果发现错误时可以使用断路器。例如,如果发现错误,大多数操作可能会在合约中被挂起,这是唯一的操作就是撤销。你可以授权给任何你受信任的一方,提供给他们触发断路器的能力,或者设计一个在满足某些条件时自动触发某个断路器的程序规则。
例如:
```
bool private stopped = false;
address private owner;
modifier isAdmin() {
if(msg.sender != owner) {
throw;
}
_;
}
function toggleContractActive() isAdmin public
{
// You can add an additional modifier that restricts stopping a contract to be based on another action, such as a vote of users
stopped = !stopped;
}
modifier stopInEmergency { if (!stopped) _; }
modifier onlyInEmergency { if (stopped) _; }
function deposit() stopInEmergency public
{
// some code
}
function withdraw() onlyInEmergency public
{
// some code
}
```
### 速度碰撞(延迟合约动作)
速度碰撞使动作变慢,所以如果发生了恶意操作便有时间恢复。例如,[The DAO](https://github.com/slockit/DAO/) 从发起分割DAO请求到真正执行动作需要27天。这样保证了资金在此期间被锁定在合约里,增加了系统的可恢复性。在DAO攻击事件中,虽然在速度碰撞给定的时间段内没有有效的措施可以采取,但结合我们其他的技术,它们是非常有效的。
例如:
```
struct RequestedWithdrawal {
uint amount;
uint time;
}
mapping (address => uint) private balances;
mapping (address => RequestedWithdrawal) private requestedWithdrawals;
uint constant withdrawalWaitPeriod = 28 days; // 4 weeks
function requestWithdrawal() public {
if (balances[msg.sender] > 0) {
uint amountToWithdraw = balances[msg.sender];
balances[msg.sender] = 0; // for simplicity, we withdraw everything;
// presumably, the deposit function prevents new deposits when withdrawals are in progress
requestedWithdrawals[msg.sender] = RequestedWithdrawal({
amount: amountToWithdraw,
time: now
});
}
}
function withdraw() public {
if(requestedWithdrawals[msg.sender].amount > 0 && now > requestedWithdrawals[msg.sender].time + withdrawalWaitPeriod) {
uint amountToWithdraw = requestedWithdrawals[msg.sender].amount;
requestedWithdrawals[msg.sender].amount = 0;
if(!msg.sender.send(amountToWithdraw)) {
throw;
}
}
}
```
### 速率限制
速率限制暂停或需要批准进行实质性更改。 例如,只允许存款人在一段时间内提取总存款的一定数量或百分比(例如,1天内最多100个ether) - 该时间段内的额外提款可能会失败或需要某种特别批准。 或者将速率限制做在合约级别,合约期限内只能发出发送一定数量的代币。
[浏览例程](https://gist.github.com/PeterBorah/110c331dca7d23236f80e69c83a9d58c#file-circuitbreaker-sol)
<a name="contract-rollout"></a>
### 合约发布
在将大量资金放入合约之前,合约应当进行大量的长时间的测试。
至少应该:
- 拥有100%测试覆盖率的完整测试套件(或接近它)
- 在自己的testnet上部署
- 在公共测试网上部署大量测试和错误奖励
- 彻底的测试应该允许各种玩家与合约进行大规模互动
- 在主网上部署beta版以限制风险总额
##### 自动弃用
在合约测试期间,你可以在一段时间后强制执行自动弃用以阻止任何操作继续进行。例如,alpha版本的合约工作几周,然后自动关闭所有除最终退出操作的操作。
```
modifier isActive() {
if (block.number > SOME_BLOCK_NUMBER) {
throw;
}
_;
}
function deposit() public
isActive() {
// some code
}
function withdraw() public {
// some code
}
```
##### 限制每个用户/合约的Ether数量
在早期阶段,你可以限制任何用户(或整个合约)的Ether数量 - 以降低风险。
<a name="bounties"> </a>
### Bug赏金计划
运行赏金计划的一些提示:
- 决定赏金以哪一种代币分配(BTC和/或ETH)
- 决定赏金奖励的预算总额
- 从预算来看,确定三级奖励:
- 你愿意发放的最小奖励
- 通常可发放的最高奖励
- 设置额外的限额以避免非常严重的漏洞被发现
- 确定赏金发放给谁(3是一个典型)
- 核心开发人员应该是赏金评委之一
- 当收到错误报告时,核心开发人员应该评估bug的严重性
- 在这个阶段的工作应该在私有仓库进行,并且在Github上的issue板块提出问题
- 如果这个bug需要被修复,开发人员应该在私有仓库编写测试用例来复现这个bug
- 开发人员需要修复bug并编写额外测试代码进行测试确保所有测试都通过
- 展示赏金猎人的修复;并将修复合并回公共仓库也是一种方式
- 确定赏金猎人是否有任何关于修复的其他反馈
- 赏金评委根据bug的*可能性*和*影响*来确定奖励的大小
- 在整个过程中保持赏金猎人参与讨论,并确保赏金发放不会延迟
有关三级奖励的例子,参见 [Ethereum's Bounty Program](https://bounty.ethereum.org):
> 奖励的价值将根据影响的严重程度而变化。 奖励轻微的“无害”错误从0.05 BTC开始。 主要错误,例如导致协商一致的问题,将获得最多5个BTC的奖励。 在非常严重的漏洞的情况下,更高的奖励是可能的(高达25 BTC)。
## 安全相关的文件和程序
当发布涉及大量资金或重要任务的合约时,必须包含适当的文档。有关安全性的文档包括:
**规范和发布计划**
- 规格说明文档,图表,状态机,模型和其他文档,帮助审核人员和社区了解系统打算做什么。
- 许多bug从规格中就能找到,而且它们的修复成本最低。
- 发布计划所涉及到的参考[这里](#contract-rollout)列出的详细信息和完成日期。
**状态**
- 当前代码被部署到哪里
- 编译器版本,使用的标志以及用于验证部署的字节码的步骤与源代码匹配
- 将用于不同阶段的编译器版本和标志
- 部署代码的当前状态(包括未决问题,性能统计信息等)
**已知问题**
- 合约的主要风险 (例如, 你可能会丢掉所有的钱,黑客可能会通过投票支持某些结果)
- 所有已知的错误/限制
- 潜在的攻击和解决办法
- 潜在的利益冲突(例如,筹集的Ether将纳入自己的腰包,像Slock.it与DAO一样)
**历史记录**
- 测试(包括使用统计,发现的错误,测试时间)
- 已审核代码的人员(及其关键反馈)
**程序**
- 发现错误的行动计划(例如紧急情况选项,公众通知程序等)
- 如果出现问题,就可以降级程序(例如,资金拥有者在被攻击之前的剩余资金占现在剩余资金的比例)
- 负责任的披露政策(例如,在哪里报告发现的bug,任何bug赏金计划的规则)
- 在失败的情况下的追索权(例如,保险,罚款基金,无追索权)
**联系信息**
- 发现问题后和谁联系
- 程序员姓名和/或其他重要参与方的名称
- 可以询问问题的论坛/聊天室
## 安全工具
- [Oyente](https://github.com/melonproject/oyente) - 根据[这篇文章](http://www.comp.nus.edu.sg/~loiluu/papers/oyente.pdf)分析Ethereum代码以找到常见的漏洞。
- [solidity-coverage](https://github.com/sc-forks/solidity-coverage) - Solidity代码覆盖率测试
- [Solgraph](https://github.com/raineorshine/solgraph) - 生成一个DOT图,显示了Solidity合约的功能控制流程,并highlight了潜在的安全漏洞。
## Linters
Linters通过约束代码风格和排版来提高代码质量,使代码更容易阅读和查看。
- [Solium](https://github.com/duaraghav8/Solium) - 另一种Solidity linting。
- [Solint](https://github.com/weifund/solint) - 帮助你实施代码一致性约定来避免你合约中的错误的Solidity linting
- [Solcheck](https://github.com/federicobond/solcheck) - 用JS写的Solidity linter,(实现上)深受eslint的影响。
## 将来的改进
- **编辑器安全警告**:编辑器将很快能够实现醒常见的安全错误,而不仅仅是编译错误。 Solidity浏览器即将推出这些功能。
- **新的能够被编译成EVM字节码的函数式编程语言**: 像Solidity这种函数式编程语言相比面向过程编程语言能够保证功能的不变性和编译时间检查。通过确定性行为来减少出现错误的风险。(更多相关信息请参阅 [这里](https://plus.google.com/u/0/events/cmqejp6d43n5cqkdl3iu0582f4k), Curry-Howard 一致性和线性逻辑)
<a name="bibliography"></a>
## 智能合约安全参考书目
很多包含代码,示例和见解的文档已经由社区编写完成。这里是其中的一些,你可以随意添加更多新的内容。
##### 来自以太坊核心开发人员
- [How to Write Safe Smart Contracts](https://chriseth.github.io/notes/talks/safe_solidity) (Christian Reitwiessner)
- [Smart Contract Security](https://blog.ethereum.org/2016/06/10/smart-contract-security/) (Christian Reitwiessner)
- [Thinking about Smart Contract Security](https://blog.ethereum.org/2016/06/19/thinking-smart-contract-security/) (Vitalik Buterin)
- [Solidity](http://solidity.readthedocs.io)
- [Solidity Security Considerations](http://solidity.readthedocs.io/en/latest/security-considerations.html)
##### 来自社区
- http://forum.ethereum.org/discussion/1317/reentrant-contracts
- http://hackingdistributed.com/2016/06/16/scanning-live-ethereum-contracts-for-bugs/
- http://hackingdistributed.com/2016/06/18/analysis-of-the-dao-exploit/
- http://hackingdistributed.com/2016/06/22/smart-contract-escape-hatches/
- http://martin.swende.se/blog/Devcon1-and-contract-security.html
- http://publications.lib.chalmers.se/records/fulltext/234939/234939.pdf
- http://vessenes.com/deconstructing-thedao-attack-a-brief-code-tour
- http://vessenes.com/ethereum-griefing-wallets-send-w-throw-considered-harmful
- http://vessenes.com/more-ethereum-attacks-race-to-empty-is-the-real-deal
- https://blog.blockstack.org/simple-contracts-are-better-contracts-what-we-can-learn-from-the-dao-6293214bad3a
- https://blog.slock.it/deja-vu-dao-smart-contracts-audit-results-d26bc088e32e
- https://blog.vdice.io/wp-content/uploads/2016/11/vsliceaudit_v1.3.pdf
- https://eprint.iacr.org/2016/1007.pdf
- https://github.com/Bunjin/Rouleth/blob/master/Security.md
- https://github.com/LeastAuthority/ethereum-analyses
- https://medium.com/@ConsenSys/assert-guards-towards-automated-code-bounties-safe-smart-contract-coding-on-ethereum-8e74364b795c
- https://medium.com/@coriacetic/in-bits-we-trust-4e464b418f0b
- https://medium.com/@hrishiolickel/why-smart-contracts-fail-undiscovered-bugs-and-what-we-can-do-about-them-119aa2843007
- https://medium.com/@peterborah/we-need-fault-tolerant-smart-contracts-ec1b56596dbc
- https://medium.com/zeppelin-blog/zeppelin-framework-proposal-and-development-roadmap-fdfa9a3a32ab
- https://pdaian.com/blog/chasing-the-dao-attackers-wake
- http://www.comp.nus.edu.sg/~loiluu/papers/oyente.pdf
## Reviewers
## The following people have reviewed this document (date and commit they reviewed in parentheses): Bill Gleim (07/29/2016 3495fb5) Bill Gleim (03/15/2017 0244f4e)
## License
Licensed under [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0)
Licensed under [Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-nc-sa/4.0/)
================================================
FILE: README.md
================================================
[<img width="200" alt="get in touch with Consensys Diligence" src="https://user-images.githubusercontent.com/2865694/56826101-91dcf380-685b-11e9-937c-af49c2510aa0.png">](https://consensys.net/diligence/)<br/>
<sup>
[[ 🌐 ](https://consensys.net/diligence/) [ 📩 ](mailto:diligence@consensys.net) [ 🔥 ](https://consensys.net/diligence/tools/)]
</sup><br/><br/>
# Smart Contract Security Best Practices
Visit the documentation site: https://consensysdiligence.github.io/smart-contract-best-practices/
Read the docs in Chinese: https://github.com/ConsenSysDiligence/smart-contract-best-practices/blob/master/README-zh.md
Read the docs in Vietnamese: https://github.com/ConsenSysDiligence/smart-contract-best-practices/blob/master/README-vi.md
## Contributions are welcome!
Feel free to submit a pull request, with anything from small fixes, to full new sections. If you are writing new content, please reference the [contributing](./docs/about/index.md) page for guidance on style.
See the [issues](https://github.com/ConsenSysDiligence/smart-contract-best-practices/issues) for topics that need to be covered or updated. If you have an idea you'd like to discuss, please chat with us in [Gitter](https://gitter.im/ConsenSys/smart-contract-best-practices).
## Building the documentation site
```
git clone git@github.com:ConsenSys/smart-contract-best-practices.git
cd smart-contract-best-practices
pip install -r requirements.txt
mkdocs build
```
To run the server (and restart on failure):
```
until mkdocs serve; do :; done
```
You can also use the `mkdocs serve` command to view the site on localhost, and live reload whenever you save changes.
## Redeploying the documentation site
```
mkdocs gh-deploy
```
================================================
FILE: custom/.icons/fontawesome/LICENSE.txt
================================================
Font Awesome Free License
-------------------------
Font Awesome Free is free, open source, and GPL friendly. You can use it for
commercial projects, open source projects, or really almost whatever you want.
Full Font Awesome Free license: https://fontawesome.com/license/free.
# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
In the Font Awesome Free download, the CC BY 4.0 license applies to all icons
packaged as SVG and JS file types.
# Fonts: SIL OFL 1.1 License (https://scripts.sil.org/OFL)
In the Font Awesome Free download, the SIL OFL license applies to all icons
packaged as web and desktop font files.
# Code: MIT License (https://opensource.org/licenses/MIT)
In the Font Awesome Free download, the MIT license applies to all non-font and
non-icon files.
# Attribution
Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
Awesome Free files already contain embedded comments with sufficient
attribution, so you shouldn't need to do anything additional when using these
files normally.
We've kept attribution comments terse, so we ask that you do not actively work
to remove them from files, especially code. They're a great way for folks to
learn about Font Awesome.
# Brand Icons
All brand icons are trademarks of their respective owners. The use of these
trademarks does not indicate endorsement of the trademark holder by Font
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
to represent the company, product, or service to which they refer.**
================================================
FILE: custom/.icons/octicons/LICENSE
================================================
MIT License
Copyright (c) 2020 GitHub Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: custom/main.html
================================================
{% extends "base.html" %}
{% block scripts %}
{{ super() }}
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-MCQTD6XJ"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
{% endblock %}
================================================
FILE: custom/partials/integrations/analytics/custom.html
================================================
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-MCQTD6XJ');</script>
<!-- End Google Tag Manager -->
================================================
FILE: docs/about/index.md
================================================
{! CONTRIBUTING.md !}
================================================
FILE: docs/about/license.md
================================================
Copyright 2016 Smart Contract Best Practices Authors
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
```
http://www.apache.org/licenses/LICENSE-2.0
```
Unless required by applicable law or agreed to in writing, software distributed under the License
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied. See the License for the specific language governing permissions and limitations under the
License.
================================================
FILE: docs/attacks/denial-of-service.md
================================================
## DoS with (Unexpected) revert
Consider a simple auction contract:
```sol
// INSECURE
contract Auction {
address currentLeader;
uint highestBid;
function bid() payable {
require(msg.value > highestBid);
require(currentLeader.send(highestBid)); // Refund the old leader, if it fails then revert
currentLeader = msg.sender;
highestBid = msg.value;
}
}
```
If attacker bids using a smart contract which has a fallback function that reverts any payment, the
attacker can win any auction. When it tries to refund the old leader, it reverts if the refund
fails. This means that a malicious bidder can become the leader while making sure that any refunds
to their address will *always* fail. In this way, they can prevent anyone else from calling the
`bid()` function, and stay the leader forever. A recommendation is to set up a
[pull payment system](../development-recommendations/general/external-calls.md) instead, as
described earlier.
Another example is when a contract may iterate through an array to pay users (e.g., supporters in a
crowdfunding contract). It's common to want to make sure that each payment succeeds. If not, one
should revert. The issue is that if one call fails, you are reverting the whole payout system,
meaning the loop will never complete. No one gets paid because one address is forcing an error.
```sol
address[] private refundAddresses;
mapping (address => uint) public refunds;
// bad
function refundAll() public {
for(uint x; x < refundAddresses.length; x++) { // arbitrary length iteration based on how many addresses participated
require(refundAddresses[x].send(refunds[refundAddresses[x]])) // doubly bad, now a single failure on send will hold up all funds
}
}
```
Again, the recommended solution is to
[favor pull over push payments](../development-recommendations/general/external-calls.md).
See [SWC-113](https://swcregistry.io/docs/SWC-113)
## DoS with Block Gas Limit
Each block has an upper bound on the amount of gas that can be spent, and thus the amount
computation that can be done. This is the Block Gas Limit. If the gas spent exceeds this limit, the
transaction will fail. This leads to a couple of possible Denial of Service vectors:
### Gas Limit DoS on a Contract via Unbounded Operations
You may have noticed another problem with the previous example: by paying out to everyone at once,
you risk running into the block gas limit.
This can lead to problems even in the absence of an intentional attack. However, it's especially
bad if an attacker can manipulate the amount of gas needed. In the case of the previous example,
the attacker could add a bunch of addresses, each of which needs to get a very small refund. The
gas cost of refunding each of the attacker's addresses could, therefore, end up being more than the
gas limit, blocking the refund transaction from happening at all.
This is another reason to
[favor pull over push payments](../development-recommendations/general/external-calls.md).
If you absolutely must loop over an array of unknown size, then you should plan for it to
potentially take multiple blocks, and therefore require multiple transactions. You will need to
keep track of how far you've gone, and be able to resume from that point, as in the following
example:
```sol
struct Payee {
address addr;
uint256 value;
}
Payee[] payees;
uint256 nextPayeeIndex;
function payOut() {
uint256 i = nextPayeeIndex;
while (i < payees.length && gasleft() > 200000) {
payees[i].addr.send(payees[i].value);
i++;
}
nextPayeeIndex = i;
}
```
You will need to make sure that nothing bad will happen if other transactions are processed while
waiting for the next iteration of the `payOut()` function. So only use this pattern if absolutely
necessary.
### Gas Limit DoS on the Network via Block Stuffing
Even if your contract does not contain an unbounded loop, an attacker can prevent other
transactions from being included in the blockchain for several blocks by placing computationally
intensive transactions with a high enough gas price.
To do this, the attacker can issue several transactions which will consume the entire gas limit,
with a high enough gas price to be included as soon as the next block is mined. No gas price can
guarantee inclusion in the block, but the higher the price is, the higher is the chance.
If the attack succeeds, no other transactions will be included in the block. Sometimes, an
attacker's goal is to block transactions to a specific contract prior to specific time.
This attack [was conducted](https://solmaz.io/2018/10/18/anatomy-block-stuffing/) on Fomo3D, a
gambling app. The app was designed to reward the last address that purchased a "key". Each key
purchase extended the timer, and the game ended once the timer went to 0. The attacker bought a key
and then stuffed 13 blocks in a row until the timer was triggered and the payout was released.
Transactions sent by attacker took 7.9 million gas on each block, so the gas limit allowed a few
small "send" transactions (which take 21,000 gas each), but disallowed any calls to the `buyKey()`
function (which costs 300,000+ gas).
A Block Stuffing attack can be used on any contract requiring an action within a certain time
period. However, as with any attack, it is only profitable when the expected reward exceeds its
cost. The cost of this attack is directly proportional to the number of blocks which need to be
stuffed. If a large payout can be obtained by preventing actions from other participants, your
contract will likely be targeted by such an attack.
See [SWC-128](https://swcregistry.io/docs/SWC-128)
================================================
FILE: docs/attacks/deprecated.md
================================================
These are attacks which are no longer possible due to changes in the protocol or improvements to
solidity. They are recorded here for posterity and awareness.
### Call Depth Attack (deprecated)
As of the [EIP 150](https://github.com/ethereum/EIPs/issues/150) hardfork, call depth attacks are
no longer
relevant<sup><a href='http://ethereum.stackexchange.com/questions/9398/how-does-eip-150-change-the-call-depth-attack'>\*</a></sup>
(all gas would be consumed well before reaching the 1024 call depth limit).
### Constantinople Reentrancy Attack
On January 16th, 2019, Constantinople protocol upgrade was delayed due to a security vulnerability
enabled by [EIP 1283](https://eips.ethereum.org/EIPS/eip-1283). _EIP 1283: Net gas metering for
SSTORE without dirty maps_ proposes changes to reduce excessive gas costs on dirty storage writes.
This change led to possibility of a new reentrancy vector making previously known secure withdrawal
patterns (`.send()` and `.transfer()`) unsafe in specific
situations<sup><a href='https://medium.com/chainsecurity/constantinople-enables-new-reentrancy-attack-ace4088297d9'>\*</a></sup>,
where the attacker could hijack the control flow and use the remaining gas enabled by EIP 1283,
leading to vulnerabilities due to reentrancy.
================================================
FILE: docs/attacks/force-feeding.md
================================================
!!! tip
Thank you for visiting the Smart Contract Security Best Practices. Please note that this resource is no longer actively maintained. Instead, we recommend visiting the [Smart Contract Security Field Guide](https://scsfg.io/). The Field Guide is regularly updated and curated by the same security engineer who previously contributed to the Best Practices guide.
The resource on unexpected Ether transfers can be found here: https://scsfg.io/hackers/unexpected-ether/
Forcing a smart contract to hold an Ether balance can influence its internal accounting and security assumptions.
There are multiple ways a smart contract can receive Ether. The hierarchy is as follows:
1. Check whether a payable external `receive` function is defined.
2. If not, check whether a payable external `fallback` function is defined.
3. Revert.
The precedence of each function is explained in this great graphic from the [Solidity by Example](https://solidity-by-example.org/sending-ether/) article:
```
Which function is called, fallback() or receive()?
send Ether
|
msg.data is empty?
/ \
yes no
/ \
receive() exists? fallback()
/ \
yes no
/ \
receive() fallback()
```
Consider the following example:
```sol
pragma solidity ^0.8.13;
contract Vulnerable {
receive() external payable {
revert();
}
function somethingBad() external {
require(address(this).balance > 0);
// Do something bad
}
}
```
The contract's logic seemingly disallows direct payments and prevents "something bad" from happening.
However, calling `revert` in both `fallback` and `receive` **cannot prevent the contract from receiving Ether**.
The following techniques can be used to force-feed Ether to a smart contract.
### Selfdestruct
When the `SELFDESTRUCT` opcode is called, funds of the calling address are sent to the address on the stack, and execution is immediately halted.
Since this opcode works on the EVM-level, Solidity-level functions that might block the receipt of Ether [will not be executed](https://solidity.readthedocs.io/en/develop/security-considerations.html#sending-and-receiving-ether).
### Pre-calculated Deployments
Additionally, the target address of newly deployed smart contracts is generated in a deterministic fashion.
The address generation can be looked up in any EVM implementation, such as the [py-evm reference implementation](https://github.com/ethereum/py-evm/blob/e924f63992a35212616b4e20355d161bc4348925/eth/_utils/address.py#L17-L18) by the Ethereum Foundation:
```python
def generate_contract_address(address: Address, nonce: int) -> Address:
return force_bytes_to_address(keccak(rlp.encode([address, nonce])))
```
An attacker can send funds to this address before the deployment has happened.
This is also illustrated by [this 2017 Underhanded Solidity Contest submission](https://github.com/Arachnid/uscc/tree/master/submissions-2017/ricmoo).
### Block Rewards and Coinbase
Depending on the attacker's capabilities, they can also start proof-of-work mining.
By setting the target address to their `coinbase`, block rewards will be added to its balance.
As this is yet another EVM-level capability, checks performed by Solidity are ineffective.
### Solution
The above effects illustrate that relying on exact comparisons to the contract's Ether balance is unreliable.
The smart contract's business logic must consider that the actual balance associated with it can be higher than the internal accounting's value.
**In general, we strongly advise against using the contract's balance as a guard.**
More information can be found in [SWC-132](https://swcregistry.io/docs/SWC-132).
================================================
FILE: docs/attacks/frontrunning.md
================================================
!!! tip
Thank you for visiting the Smart Contract Security Best Practices. Please note that this resource is no longer actively maintained. Instead, we recommend visiting the [Smart Contract Security Field Guide](https://scsfg.io/). The Field Guide is regularly updated and curated by the same security engineer who previously contributed to the Best Practices guide.
The resource on frontrunning attacks can be found here: https://scsfg.io/hackers/frontrunning/
Since all transactions are visible in the mempool for a short while before being executed,
observers of the network can see and react to an action before it is included in a block. An
example of how this can be exploited is with a decentralized exchange where a buy order transaction
can be seen, and second order can be broadcast and executed before the first transaction is
included. Protecting against this is difficult, as it would come down to the specific contract
itself.
Front-running, coined originally for traditional financial markets, is the race to order the chaos
to the winner's benefit. In financial markets, the flow of information gave birth to intermediaries
that could simply profit by being the first to know and react to some information. These attacks
mostly had been within stock market deals and early domain registries, such as whois gateways.
!!! cite "front-run·ning (/ˌfrəntˈrəniNG/)"
*noun*: front-running;
1. *STOCK MARKET*
> the practice by market makers of dealing on advance information provided by their brokers and investment analysts, before their clients have been given the information.
<!-- [[OXFORD](https://www.lexico.com/en/definition/front-running)] -->
### Taxonomy
By defining a [taxonomy](https://arxiv.org/abs/1902.05164) and differentiating each group from
another, we can make it easier to discuss the problem and find solutions for each group.
We define the following categories of front-running attacks:
1. Displacement
1. Insertion
1. Suppression
#### Displacement
In the first type of attack, *a displacement attack*, it is **not important** for Alice’s (User)
function call to run after Mallory (Adversary) runs her function. Alice’s can be orphaned or run
with no meaningful effect. Examples of displacement include:
- Alice trying to register a domain name and Mallory registering it first;
- Alice trying to submit a bug to receive a bounty and Mallory stealing it and submitting it first;
- Alice trying to submit a bid in an auction and Mallory copying it.
This attack is commonly performed by increasing the `gasPrice` higher than network average, often
by a multiplier of 10 or more.
#### Insertion
For this type of attack, it is **important** to the adversary that the original function call runs
after her transaction. In an insertion attack, after Mallory runs her function, the state of the
contract is changed and she needs Alice’s original function to run on this modified state. For
example, if Alice places a purchase order on a blockchain asset at a higher price than the best
offer, Mallory will insert two transactions: she will purchase at the best offer price and then
offer the same asset for sale at Alice’s slightly higher purchase price. If Alice’s transaction is
then run after, Mallory will profit on the price difference without having to hold the asset.
As with displacement attacks, this is usually done by outbidding Alice's transaction in the gas
price auction.
!!! info "Transaction Order Dependence"
Transaction Order Dependence is equivalent to race
condition in smart contracts. An example, if one function sets the reward percentage, and the
withdraw function uses that percentage; then withdraw transaction can be front-run by a change
reward function call, which impacts the amount that will be withdrawn eventually.
See [SWC-114](https://swcregistry.io/docs/SWC-114)
<!-- Based on Geth default ordering, it's easy to sandwich a transaction by sending two transactions each with 1 wei higher or lower. -->
<!-- Cite theo/daniel's talk -->
#### Suppression
In a suppression attack, a.k.a *Block Stuffing* attacks, after Mallory runs her function, she tries
to delay Alice from running her function.
This was the case with the first winner of the "Fomo3d" game and some other on-chain hacks. The
attacker sent multiple transactions with a high `gasPrice` and `gasLimit` to custom smart contracts
that assert (or use other means) to consume all the gas and fill up the block's `gasLimit`.
!!! note "Variants"
Each of these attacks has two variants, *asymmetric* and *bulk*.
In some cases, Alice and Mallory are performing different operations. For example, Alice is trying to cancel an offer, and Mallory is trying to fulfill it first. We call this *asymmetric displacement*. In other cases, Mallory is trying to run a large set of functions: for example, Alice and others are trying to buy a limited set of shares offered by a firm on a blockchain. We call this *bulk displacement*.
### Mitigations
Front-running is a pervasive issue on public blockchains such as Ethereum.
The best remediation is to **remove the benefit of front-running in your application**, mainly by
removing the importance of transaction ordering or time. For example, in markets, it would be
better to implement batch auctions (this also protects against high-frequency trading concerns).
Another way is to use a pre-commit scheme (“I’m going to submit the details later”). A third option
is to mitigate the cost of front-running by specifying a maximum or minimum acceptable price range
on a trade, thereby limiting price slippage.
**Transaction Ordering:** Go-Ethereum (Geth) nodes, order the transactions based on their
`gasPrice` and address nonce. This, however, results in a gas auction between participants in the
network to get included in the block currently being mined.
**Confidentiality:** Another approach is to limit the visibility of the transactions, this can be
done using a "commit and reveal" scheme.
<!-- cite and properly define commit and reveal -->
A simple implementation is to store the keccak256 hash of the data in the first transaction, then
reveal the data and verify it against the hash in the second transaction. However note that the
transaction itself leaks the intention and possibly the value of the collateralization. There are
enhanced commit and reveal schemes that are more secure, however require more transactions to
function, e.g. [submarine sends](https://libsubmarine.org/).
================================================
FILE: docs/attacks/griefing.md
================================================
!!! tip
Thank you for visiting the Smart Contract Security Best Practices. Please note that this resource is no longer actively maintained. Instead, we recommend visiting the [Smart Contract Security Field Guide](https://scsfg.io/). The Field Guide is regularly updated and curated by the same security engineer who previously contributed to the Best Practices guide.
The resource on griefing attacks can be found here: https://scsfg.io/hackers/griefing/
This attack may be possible on a contract which accepts generic data and uses it to make a call
another contract (a 'sub-call') via the low level `address.call()` function, as is often the case
with multisignature and transaction relayer contracts.
If the call fails, the contract has two options:
1. revert the whole transaction
1. continue execution.
Take the following example of a simplified `Relayer` contract which continues execution regardless
of the outcome of the subcall:
```sol
contract Relayer {
mapping (bytes => bool) executed;
function relay(bytes _data) public {
// replay protection; do not call the same transaction twice
require(executed[_data] == 0, "Duplicate call");
executed[_data] = true;
innerContract.call(bytes4(keccak256("execute(bytes)")), _data);
}
}
```
This contract allows transaction relaying. Someone who wants to make a transaction but can't
execute it by himself (e.g. due to the lack of ether to pay for gas) can sign data that he wants to
pass and transfer the data with his signature over any medium. A third party "forwarder" can then
submit this transaction to the network on behalf of the user.
If given just the right amount of gas, the `Relayer` would complete execution recording the
`_data`argument in the `executed` mapping, but the subcall would fail because it received
insufficient gas to complete execution.
!!! Note
When a contract makes a sub-call to another contract, the EVM limits the gas forwarded to
[to 63/64 of the remaining gas](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md),
An attacker can use this to censor transactions, causing them to fail by sending them with a low
amount of gas. This attack is a form of "[griefing](https://en.wikipedia.org/wiki/Griefer)": It
doesn't directly benefit the attacker, but causes grief for the victim. A dedicated attacker,
willing to consistently spend a small amount of gas could theoretically censor all transactions
this way, if they were the first to submit them to `Relayer`.
One way to address this is to implement logic requiring forwarders to provide enough gas to finish
the subcall. If the miner tried to conduct the attack in this scenario, the `require` statement
would fail and the inner call would revert. A user can specify a minimum gasLimit along with the
other data (in this example, typically the `_gasLimit` value would be verified by a signature, but
that is omitted for simplicity in this case).
```sol
// contract called by Relayer
contract Executor {
function execute(bytes _data, uint _gasLimit) {
require(gasleft() >= _gasLimit);
...
}
}
```
Another solution is to permit only trusted accounts to relay the transaction.
================================================
FILE: docs/attacks/index.md
================================================
!!! tip
Seeking more detailed information on smart contract attacks? The [Smart Contract Security Field Guide](https://scsfg.io/hackers/) offers an extensive range of attack strategies with in-depth explanations on vulnerabilities, including new code samples for a hands-on learning experience. Enhance your understanding and stay ahead of potential threats by visiting this continuously updated resource.
The following is a list of known attacks which you should be aware of, and defend against when
writing smart contracts.
| Category | Description |
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| [Reentrancy](./reentrancy.md) | Intra- and inter-function reentrancy attacks and potentially faulty solutions to them. |
| [Oracle Manipulation](./oracle-manipulation.md) | Manipulation of external data providers and potential solutions to oracle security issues. |
| [Frontrunning](./frontrunning.md) | A definition and taxonomy around frontrunning and related attacks. |
| [Timestamp Dependence](./timestamp-dependence.md) | Attacks relating to the timing of a transaction. |
| [Insecure Arithmetic](./insecure-arithmetic.md) | Integer overflows and underflows. |
| [Denial of Service](./denial-of-service.md) | Denial of service attacks through unexpected reverts and the block gas limit. |
| [Griefing](./griefing.md) | Attacks relating to bad faith players around a smart contract system. |
| [Force Feeding](./force-feeding.md) | Forcing Ether to be sent to smart contracts to manipulate balance checks. |
| [Deprecated/Historical](./deprecated.md) | Attacks that are part of Ethereum's history and vulnerabilities that have been fixes on a (Solidity) compiler level. |
| [More](./more.md) | Where to find more information about vulnerabilities and weaknesses. |
================================================
FILE: docs/attacks/insecure-arithmetic.md
================================================
Consider a simple token transfer:
```sol
mapping (address => uint256) public balanceOf;
// INSECURE
function transfer(address _to, uint256 _value) {
/* Check if sender has balance */
require(balanceOf[msg.sender] >= _value);
/* Add and subtract new balances */
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
}
// SECURE
function transfer(address _to, uint256 _value) {
/* Check if sender has balance and for overflows */
require(balanceOf[msg.sender] >= _value && balanceOf[_to] + _value >= balanceOf[_to]);
/* Add and subtract new balances */
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
}
```
If a balance reaches the *maximum uint value (2^256)* it will circle back to zero which checks for
the condition. This may or may not be relevant, depending on the implementation. Think about
whether or not the `uint` value has an opportunity to approach such a large number. Think about how
the `uint` variable changes state, and who has authority to make such changes. If any user can call
functions which update the `uint` value, it's more vulnerable to attack. If only an admin has
access to change the variable's state, you might be safe. If a user can increment by only 1 at a
time, you are probably also safe because there is no feasible way to reach this limit.
The same is true for underflow. If a uint is made to be less than zero, it will cause an underflow
and get set to its maximum value.
Be careful with the smaller data-types like uint8, uint16, uint24...etc: they can even more easily
hit their maximum value.
!!! Warning
Be aware there are around [20 cases for overflow and underflow](https://github.com/ethereum/solidity/issues/796#issuecomment-253578925).
One simple solution to mitigate the common mistakes for overflow and underflow is to use
`SafeMath.sol`
[library](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/ae54e6de1d77c60881e4c85ffbdb7f9d76b71efe/contracts/utils/math/SafeMath.sol)
for arithmetic functions. Solidity automatically reverts on integer overflow and underflow, as of
version 0.8.0.
See [SWC-101](https://swcregistry.io/docs/SWC-101)
================================================
FILE: docs/attacks/more.md
================================================
The [Smart Contract Weakness Classification Registry](https://smartcontractsecurity.github.io/SWC-registry/)
offers a complete and up-to-date catalogue of known smart contract vulnerabilities and
anti-patterns along with real-world examples. Browsing the registry is a good way of keeping
up-to-date with the latest attacks. It has been proposed formally in [EIP-1470](https://github.com/ethereum/EIPs/issues/1469) and is loosely aligned with the Common Weakness Enumeration ([CWE](https://cwe.mitre.org/)).
Additionally, the [Smart Contract Security Verification Standard (SCSVS)](https://github.com/securing/SCSVS) contains a checklist-style collection of vital security properties aimed at standardizing the security of smart contracts for developers, architects, security reviewers and vendors.
================================================
FILE: docs/attacks/oracle-manipulation.md
================================================
!!! tip
Thank you for visiting the Smart Contract Security Best Practices. Please note that this resource is no longer actively maintained. Instead, we recommend visiting the [Smart Contract Security Field Guide](https://scsfg.io/). The Field Guide is regularly updated and curated by the same security engineer who previously contributed to the Best Practices guide.
The resource on oracle manipulation attacks can be found here: https://scsfg.io/hackers/oracle-manipulation/
Protocols that rely on external data as inputs (from what's known as an
[oracle](https://medium.com/better-programming/what-is-a-blockchain-oracle-f5ccab8dbd72?source=friends_link&sk=d921a38466df8a9176ed8dd767d8c77d))
automatically execute even if the data is incorrect, due to the nature of smart contracts. If a
protocol relies on an oracle that is hacked, deprecated, or has malicious intent, all processes
that depend on the oracle can now operate with disastrous effects.
For example:
1. Protocol gets price from single Uniswap pool
1. Malicious actor drains one side of the pool with a large transaction
1. Uniswap pool starts responding with a price more than 100x what it should be
1. Protocol operates as if that were the actual price, giving the manipulator a better price
We've seen examples where this will liquidate positions, allow insane arbitrage, ruin DEX
positions, and more.
### Oracle Manipulation Solutions
The easiest way to solve this is to use decentralized oracles such as:
* [Chainlink](https://chain.link/) is the leading decentralized oracle provider, and the Chainlink network can be leveraged to bring decentralized data on-chain.
* [Tellor](https://tellor.io/) is an oracle that provides censorship resistant data, secured by crypto-economic incentives, that ensure data can be provided by anyone, anytime, and checked by everyone.
* [Witnet](https://witnet.io/) leverages state-of-the-art cryptographic and economic techniques to provide your smart contracts with secure data input.
Using a median of multiple oracles provides heighten security since it is harder and more expensive to attack multiple oracles than one and it ensures that your smart contract gets the data it needs even if one oracle or API call fails.
Another common solution is to use a time-weighted average price feed, so that price is averaged out
over `X` time periods and multiple sources. Not only does this prevent oracle manipulation, but it also reduces the
chance you can be front-run, as an order executed right before yours won't have as drastic an
impact on price. However, always keep in mind that low liquidity assets are generally easier/cheaper to manipulate, even for a period of time. One tool that gathers Uniswap price feeds every thirty minutes is
[Keep3r](https://docs.uniquote.finance/). If you're looking to build a custom solution,
[Uniswap provides a sliding window example](https://github.com/Uniswap/uniswap-v2-periphery/blob/master/contracts/examples/ExampleSlidingWindowOracle.sol).
================================================
FILE: docs/attacks/reentrancy.md
================================================
!!! tip
Thank you for visiting the Smart Contract Security Best Practices. Please note that this resource is no longer actively maintained. Instead, we recommend visiting the [Smart Contract Security Field Guide](https://scsfg.io/). The Field Guide is regularly updated and curated by the same security engineer who previously contributed to the Best Practices guide.
The resource on reentrancy can be found here: https://scsfg.io/hackers/reentrancy/
One of the major dangers of [calling external contracts](../development-recommendations/general/external-calls.md) is that
they can take over the control flow, and make changes to your data that the calling function wasn't
expecting. This class of bugs can take many forms, and both of the major bugs that led to the DAO's
collapse were bugs of this sort.
### Reentrancy on a Single Function
The first version of this bug to be noticed involved functions that could be called repeatedly,
before the first invocation of the function was finished. This may cause the different invocations
of the function to interact in destructive ways.
```sol
// INSECURE
mapping (address => uint) private userBalances;
function withdrawBalance() public {
uint amountToWithdraw = userBalances[msg.sender];
(bool success, ) = msg.sender.call.value(amountToWithdraw)(""); // At this point, the caller's code is executed, and can call withdrawBalance again
require(success);
userBalances[msg.sender] = 0;
}
```
Since the user's balance is not set to 0 until the very end of the function, the second (and later)
invocations will still succeed and will withdraw the balance over and over again.
!!! Factoid
A DAO is a Decentralized Autonomous Organization. Its goal is to codify the rules and
decision-making apparatus of an organization, eliminating the need for documents and people in
governing, creating a structure with decentralized control.
```
On June 17th 2016, [The DAO](https://www.coindesk.com/understanding-dao-hack-journalists) was hacked and 3.6 million Ether ($50 Million) were stolen using the first reentrancy attack.
Ethereum Foundation issued a critical update to rollback the hack. This resulted in Ethereum being forked into Ethereum Classic and Ethereum.
```
In the example given, the best way to prevent this attack is to make sure you don't call an
external function until you've done all the internal work you need to do:
```sol
mapping (address => uint) private userBalances;
function withdrawBalance() public {
uint amountToWithdraw = userBalances[msg.sender];
userBalances[msg.sender] = 0;
(bool success, ) = msg.sender.call.value(amountToWithdraw)(""); // The user's balance is already 0, so future invocations won't withdraw anything
require(success);
}
```
Note that if you had another function which called `withdrawBalance()`, it would be potentially
subject to the same attack, so you must treat any function which calls an untrusted contract as
itself untrusted. See below for further discussion of potential solutions.
### Cross-function Reentrancy
An attacker may also be able to do a similar attack using two different functions that share the
same state.
```sol
// INSECURE
mapping (address => uint) private userBalances;
function transfer(address to, uint amount) {
if (userBalances[msg.sender] >= amount) {
userBalances[to] += amount;
userBalances[msg.sender] -= amount;
}
}
function withdrawBalance() public {
uint amountToWithdraw = userBalances[msg.sender];
(bool success, ) = msg.sender.call.value(amountToWithdraw)(""); // At this point, the caller's code is executed, and can call transfer()
require(success);
userBalances[msg.sender] = 0;
}
```
In this case, the attacker calls `transfer()` when their code is executed on the external call in
`withdrawBalance`. Since their balance has not yet been set to 0, they are able to transfer the
tokens even though they already received the withdrawal. This vulnerability was also used in the
DAO attack.
The same solutions will work, with the same caveats. Also note that in this example, both functions
were part of the same contract. However, the same bug can occur across multiple contracts, if those
contracts share state.
### Pitfalls in Reentrancy Solutions
Since reentrancy can occur across multiple functions, and even multiple contracts, any solution
aimed at preventing reentrancy with a single function will not be sufficient.
Instead, **we have recommended finishing all internal work (ie. state changes) first, and only then
calling the external function**. This rule, if followed carefully, will allow you to avoid
vulnerabilities due to reentrancy. However, you need to not only avoid calling external functions
too soon, but also avoid calling functions which call external functions. For example, the
following is insecure:
```sol
// INSECURE
mapping (address => uint) private userBalances;
mapping (address => bool) private claimedBonus;
mapping (address => uint) private rewardsForA;
function withdrawReward(address recipient) public {
uint amountToWithdraw = rewardsForA[recipient];
rewardsForA[recipient] = 0;
(bool success, ) = recipient.call.value(amountToWithdraw)("");
require(success);
}
function getFirstWithdrawalBonus(address recipient) public {
require(!claimedBonus[recipient]); // Each recipient should only be able to claim the bonus once
rewardsForA[recipient] += 100;
withdrawReward(recipient); // At this point, the caller will be able to execute getFirstWithdrawalBonus again.
claimedBonus[recipient] = true;
}
```
Even though `getFirstWithdrawalBonus()` doesn't directly call an external contract, the call in
`withdrawReward()` is enough to make it vulnerable to a reentrancy. You therefore need to treat
`withdrawReward()` as if it were also untrusted.
```sol
mapping (address => uint) private userBalances;
mapping (address => bool) private claimedBonus;
mapping (address => uint) private rewardsForA;
function untrustedWithdrawReward(address recipient) public {
uint amountToWithdraw = rewardsForA[recipient];
rewardsForA[recipient] = 0;
(bool success, ) = recipient.call.value(amountToWithdraw)("");
require(success);
}
function untrustedGetFirstWithdrawalBonus(address recipient) public {
require(!claimedBonus[recipient]); // Each recipient should only be able to claim the bonus once
claimedBonus[recipient] = true;
rewardsForA[recipient] += 100;
untrustedWithdrawReward(recipient); // claimedBonus has been set to true, so reentry is impossible
}
```
In addition to the fix making reentry impossible,
[untrusted functions have been marked](../development-recommendations/general/external-calls.md). This same
pattern repeats at every level: since `untrustedGetFirstWithdrawalBonus()` calls
`untrustedWithdrawReward()`, which calls an external contract, you must also treat
`untrustedGetFirstWithdrawalBonus()` as insecure.
Another solution often suggested is a [mutex](https://en.wikipedia.org/wiki/Mutual_exclusion). This
allows you to "lock" some state so it can only be changed by the owner of the lock. A simple
example might look like this:
```sol
// Note: This is a rudimentary example, and mutexes are particularly useful where there is substantial logic and/or shared state
mapping (address => uint) private balances;
bool private lockBalances;
function deposit() payable public returns (bool) {
require(!lockBalances);
lockBalances = true;
balances[msg.sender] += msg.value;
lockBalances = false;
return true;
}
function withdraw(uint amount) payable public returns (bool) {
require(!lockBalances && amount > 0 && balances[msg.sender] >= amount);
lockBalances = true;
(bool success, ) = msg.sender.call(amount)("");
if (success) { // Normally insecure, but the mutex saves it
balances[msg.sender] -= amount;
}
lockBalances = false;
return true;
}
```
If the user tries to call `withdraw()` again before the first call finishes, the lock will prevent
it from having any effect. This can be an effective pattern, but it gets tricky when you have
multiple contracts that need to cooperate. The following is insecure:
```sol
// INSECURE
contract StateHolder {
uint private n;
address private lockHolder;
function getLock() {
require(lockHolder == address(0));
lockHolder = msg.sender;
}
function releaseLock() {
require(msg.sender == lockHolder);
lockHolder = address(0);
}
function set(uint newState) {
require(msg.sender == lockHolder);
n = newState;
}
}
```
An attacker can call `getLock()`, and then never call `releaseLock()`. If they do this, then the
contract will be locked forever, and no further changes will be able to be made. If you use mutexes
to protect against reentrancy, you will need to carefully ensure that there are no ways for a lock
to be claimed and never released. (There are other potential dangers when programming with mutexes,
such as deadlocks and livelocks. You should consult the large amount of literature already written
on mutexes, if you decide to go this route.)
See [SWC-107](https://swcregistry.io/docs/SWC-107)
______________________________________________________________________
Above were examples of reentrancy involving the attacker executing malicious code *within a single
transaction*. The following are a different type of attack inherent to Blockchains: the fact that
*the order of transactions themselves* (e.g. within a block) is easily subject to manipulation.
================================================
FILE: docs/attacks/timestamp-dependence.md
================================================
Be aware that the timestamp of the block can be manipulated by the miner, and all direct and
indirect uses of the timestamp should be considered.
!!! Note
See the [Recommendations](../development-recommendations/solidity-specific/timestamp-dependence.md) section for design
considerations related to Timestamp Dependence.
See [SWC-116](https://swcregistry.io/docs/SWC-116)
================================================
FILE: docs/bug-bounty-programs.md
================================================
---
hide:
- navigation
---
!!! tip
Looking for comprehensive information on setting up, managing, and operating a bug bounty program? Please refer to the [Smart Contract Security Field Guide's](https://scsfg.io/developers/bug-bounty/) bug bounty guide. This resource provides in-depth, up-to-date knowledge and strategies that are paramount for running a successful bug bounty program.
Over the course of time Ethereum security has evolved to include different flavours of bug bounty programs which will be detailed below:
### Bug Bounty Platforms
The first category are bug bounty platforms wherein a development team submits their project to a platform that either manages the programme for them or simply lists their project for exposure and reach toward interested security researchers. These platforms are further divided by type. The first are web3 native platforms hosting the majority of smart contract and frontend bug bounty programmes you'll find and the second are traditional platforms hosting majorly programmes with the frontend of centralized exchanges in scope. Finally, there are bounty collaboration platforms where developers are paid to code and implement new features or smart contracts.
Web3 native platforms:
- [Immunefi](https://immunefi.com/)
- [HackenProof](https://hackenproof.com/)
Traditional platforms:
- [HackerOne](https://hackerone.com/)
- [Bugcrowd](https://www.bugcrowd.com/)
Bounty collaboration platforms:
- [Gitcoin](https://gitcoin.co/explorer)
### Crowd-sourced Security Solutions
In response to the high demand and low supply for professional smart contract security review firms, a few crowd sourced solutions have emerged to solve the issue. They all employ a bug bounty-esque model hence inclusion on this list. They call them "audit contests" with freelance security researchers scrambling to find and report vulnerabilities within a set time period i.e two weeks with payouts only being issued for successful findings. Examples are listed below:
- [Code4rena](https://code4rena.com/)
### Project Managed Bounties
The final category for now consists of bug bounty programmes that are directly managed by the project team itself and are often focused on smart contracts in their scope whether that's contributing to their features or breaking them.
Issues and PRs are welcome to add new bounties, or remove those which are no longer
active.
- [Airswap](https://medium.com/fluidity/airswap-bug-bounty-4d7ec41f3ea7)
- [Ethereum Foundation](https://bounty.ethereum.org/#bounty-scope): Has a large scope, including
clients, Solidity and Vyper, and more.
- [Etherscan.io](https://etherscan.io/bugbounty)
- [ImmutableSoft](https://immutablesoft.github.io/ImmutableEcosystem/)
- [0xProject](https://0x.org/docs/guides/bug-bounty-program#rewards)
- [Parity](https://www.parity.io/bug-bounty/): Includes client and contract code
================================================
FILE: docs/development-recommendations/deprecated/constructor-naming.md
================================================
!!! tip
For comprehensive insights into secure development practices, consider visiting the [Development Recommendations](https://scsfg.io/developers/) section of the Smart Contract Security Field Guide. This resource provides in-depth articles to guide you in developing robust and secure smart contracts.
================================================
FILE: docs/development-recommendations/deprecated/division-by-zero.md
================================================
!!! tip
For comprehensive insights into secure development practices, consider visiting the [Development Recommendations](https://scsfg.io/developers/) section of the Smart Contract Security Field Guide. This resource provides in-depth articles to guide you in developing robust and secure smart contracts.
Prior to version 0.4, Solidity [returns zero](https://github.com/ethereum/solidity/issues/670) and
does not `throw` an exception when a number is divided by zero. Ensure you're running at least
version 0.4.
================================================
FILE: docs/development-recommendations/deprecated/functions-and-events.md
================================================
!!! tip
For comprehensive insights into secure development practices, consider visiting the [Development Recommendations](https://scsfg.io/developers/) section of the Smart Contract Security Field Guide. This resource provides in-depth articles to guide you in developing robust and secure smart contracts.
### Differentiate functions and events (Solidity \< 0.4.21)
Favor capitalization and a prefix in front of events (we suggest *Log*), to prevent the risk of
confusion between functions and events. For functions, always start with a lowercase letter, except
for the constructor.
```sol
// bad
event Transfer() {}
function transfer() {}
// good
event LogTransfer() {}
function transfer() external {}
```
!!! Note
In [v0.4.21](https://github.com/ethereum/solidity/blob/develop/Changelog.md#0421-2018-03-07) Solidity
introduced the `emit` keyword to indicate an event `emit EventName();`. As of 0.5.0, it is
required.
================================================
FILE: docs/development-recommendations/documentation/contact.md
================================================
!!! tip
For comprehensive insights into secure development practices, consider visiting the [Development Recommendations](https://scsfg.io/developers/) section of the Smart Contract Security Field Guide. This resource provides in-depth articles to guide you in developing robust and secure smart contracts.
- Who to contact with issues
- Names of programmers and/or other important parties
- Chat room where questions can be asked
================================================
FILE: docs/development-recommendations/documentation/general.md
================================================
!!! tip
For comprehensive insights into secure development practices, consider visiting the [Development Recommendations](https://scsfg.io/developers/) section of the Smart Contract Security Field Guide. This resource provides in-depth articles to guide you in developing robust and secure smart contracts.
When launching a contract that will have substantial funds or is required to be mission critical,
it is important to include proper documentation.
================================================
FILE: docs/development-recommendations/documentation/history.md
================================================
!!! tip
For comprehensive insights into secure development practices, consider visiting the [Development Recommendations](https://scsfg.io/developers/) section of the Smart Contract Security Field Guide. This resource provides in-depth articles to guide you in developing robust and secure smart contracts.
- Testing (including usage stats, discovered bugs, length of testing)
- People who have reviewed code (and their key feedback)
================================================
FILE: docs/development-recommendations/documentation/known-issues.md
================================================
!!! tip
For comprehensive insights into secure development practices, consider visiting the [Development Recommendations](https://scsfg.io/developers/) section of the Smart Contract Security Field Guide. This resource provides in-depth articles to guide you in developing robust and secure smart contracts.
- Key risks with contract
- e.g., You can lose all your money, hacker can vote for certain outcomes
- All known bugs/limitations
- Potential attacks and mitigants
- Potential conflicts of interest (e.g., will be using yourself, like Slock.it did with the DAO)
================================================
FILE: docs/development-recommendations/documentation/procedures.md
================================================
!!! tip
For comprehensive insights into secure development practices, consider visiting the [Development Recommendations](https://scsfg.io/developers/) section of the Smart Contract Security Field Guide. This resource provides in-depth articles to guide you in developing robust and secure smart contracts.
- Action plan in case a bug is discovered (e.g., emergency options, public notification process,
etc.)
- Wind down process if something goes wrong (e.g., funders will get percentage of your balance
before attack, from remaining funds)
- Responsible disclosure policy (e.g., where to report bugs found, the rules of any bug bounty
program)
- Recourse in case of failure (e.g., insurance, penalty fund, no recourse)
================================================
FILE: docs/development-recommendations/documentation/specification.md
================================================
!!! tip
For comprehensive insights into secure development practices, consider visiting the [Development Recommendations](https://scsfg.io/developers/) section of the Smart Contract Security Field Guide. This resource provides in-depth articles to guide you in developing robust and secure smart contracts.
- Specs, diagrams, state machines, models, and other documentation that helps auditors, reviewers,
and the community understand what the system is intended to do.
- Many bugs can be found just from the specifications, and they are the least costly to fix.
- Rollout plans that include details listed [here](../precautions/deployment.md), and
target dates.
================================================
FILE: docs/development-recommendations/documentation/status.md
================================================
!!! tip
For comprehensive insights into secure development practices, consider visiting the [Development Recommendations](https://scsfg.io/developers/) section of the Smart Contract Security Field Guide. This resource provides in-depth articles to guide you in developing robust and secure smart contracts.
- Where current code is deployed
- Compiler version, flags used, and steps for verifying the deployed bytecode matches the source
code
- Compiler versions and flags that will be used for the different phases of rollout.
- Current status of deployed code (including outstanding issues, performance stats, etc.)
================================================
FILE: docs/development-recommendations/general/external-calls.md
================================================
!!! tip
For comprehensive insights into secure development practices, consider visiting the [Development Recommendations](https://scsfg.io/developers/) section of the Smart Contract Security Field Guide. This resource provides in-depth articles to guide you in developing robust and secure smart contracts.
#### Use caution when making external calls
Calls to untrusted contracts can introduce several unexpected risks or errors. External calls may
execute malicious code in that contract _or_ any other contract that it depends upon. As such,
every external call should be treated as a potential security risk. When it is not possible, or
undesirable to remove external calls, use the recommendations in the rest of this section to
minimize the danger.
______________________________________________________________________
#### Mark untrusted contracts
When interacting with external contracts, name your variables, methods, and contract interfaces in
a way that makes it clear that interacting with them is potentially unsafe. This applies to your
own functions that call external contracts.
```sol
// bad
Bank.withdraw(100); // Unclear whether trusted or untrusted
function makeWithdrawal(uint amount) { // Isn't clear that this function is potentially unsafe
Bank.withdraw(amount);
}
// good
UntrustedBank.withdraw(100); // untrusted external call
TrustedBank.withdraw(100); // external but trusted bank contract maintained by XYZ Corp
function makeUntrustedWithdrawal(uint amount) {
UntrustedBank.withdraw(amount);
}
```
______________________________________________________________________
#### Avoid state changes after external calls
Whether using *raw calls* (of the form `someAddress.call()`) or *contract calls* (of the form
`ExternalContract.someMethod()`), assume that malicious code might execute. Even if
`ExternalContract` is not malicious, malicious code can be executed by any contracts *it* calls.
One particular danger is malicious code may hijack the control flow, leading to vulnerabilities due
to reentrancy. (See [Reentrancy](../../attacks/reentrancy.md) for a fuller discussion of this
problem).
If you are making a call to an untrusted external contract, *avoid state changes after the call*.
This pattern is also sometimes known as the
[checks-effects-interactions pattern](http://solidity.readthedocs.io/en/develop/security-considerations.html?highlight=check%20effects#use-the-checks-effects-interactions-pattern).
See [SWC-107](https://swcregistry.io/docs/SWC-107)
______________________________________________________________________
#### Don't use `transfer()` or `send()`.
`.transfer()` and `.send()` forward exactly 2,300 gas to the recipient. The goal of this hardcoded
gas stipend was to prevent [reentrancy vulnerabilities](../../attacks/reentrancy.md), but this only
makes sense under the assumption that gas costs are constant. Recently
[EIP 1884](https://eips.ethereum.org/EIPS/eip-1884) was included in the Istanbul hard fork. One of
the changes included in EIP 1884 is an increase to the gas cost of the `SLOAD` operation, causing a
contract's fallback function to cost more than 2300 gas.
It's recommended to stop using `.transfer()` and `.send()` and instead use `.call()`.
```
// bad
contract Vulnerable {
function withdraw(uint256 amount) external {
// This forwards 2300 gas, which may not be enough if the recipient
// is a contract and gas costs change.
msg.sender.transfer(amount);
}
}
// good
contract Fixed {
function withdraw(uint256 amount) external {
// This forwards all available gas. Be sure to check the return value!
(bool success, ) = msg.sender.call.value(amount)("");
require(success, "Transfer failed.");
}
}
```
Note that `.call()` does nothing to mitigate reentrancy attacks, so other precautions must be
taken. To prevent reentrancy attacks, it is recommended that you use the
[checks-effects-interactions pattern](https://solidity.readthedocs.io/en/develop/security-considerations.html?highlight=check%20effects#use-the-checks-effects-interactions-pattern).
______________________________________________________________________
#### Handle errors in external calls
Solidity offers low-level call methods that work on raw addresses: `address.call()`,
`address.callcode()`, `address.delegatecall()`, and `address.send()`. These low-level methods never
throw an exception, but will return `false` if the call encounters an exception. On the other hand,
*contract calls* (e.g., `ExternalContract.doSomething()`) will automatically propagate a throw (for
example, `ExternalContract.doSomething()` will also `throw` if `doSomething()` throws).
If you choose to use the low-level call methods, make sure to handle the possibility that the call
will fail, by checking the return value.
```sol
// bad
someAddress.send(55);
someAddress.call.value(55)(""); // this is doubly dangerous, as it will forward all remaining gas and doesn't check for result
someAddress.call.value(100)(bytes4(sha3("deposit()"))); // if deposit throws an exception, the raw call() will only return false and transaction will NOT be reverted
// good
(bool success, ) = someAddress.call.value(55)("");
if(!success) {
// handle failure code
}
ExternalContract(someAddress).deposit.value(100)();
```
See [SWC-104](https://swcregistry.io/docs/SWC-104)
______________________________________________________________________
#### Favor *pull* over *push* for external calls
External calls can fail accidentally or deliberately. To minimize the damage caused by such
failures, it is often better to isolate each external call into its own transaction that can be
initiated by the recipient of the call. This is especially relevant for payments, where it is
better to let users withdraw funds rather than push funds to them automatically. (This also reduces
the chance of [problems with the gas limit](../../attacks/denial-of-service.md).) Avoid
combining multiple ether transfers in a single transaction.
```sol
// bad
contract auction {
address highestBidder;
uint highestBid;
function bid() payable {
require(msg.value >= highestBid);
if (highestBidder != address(0)) {
(bool success, ) = highestBidder.call.value(highestBid)("");
require(success); // if this call consistently fails, no one else can bid
}
highestBidder = msg.sender;
highestBid = msg.value;
}
}
// good
contract auction {
address highestBidder;
uint highestBid;
mapping(address => uint) refunds;
function bid() payable external {
require(msg.value >= highestBid);
if (highestBidder != address(0)) {
refunds[highestBidder] += highestBid; // record the refund that this user can claim
}
highestBidder = msg.sender;
highestBid = msg.value;
}
function withdrawRefund() external {
uint refund = refunds[msg.sender];
refunds[msg.sender] = 0;
(bool success, ) = msg.sender.call.value(refund)("");
require(success);
}
}
```
See [SWC-128](https://swcregistry.io/docs/SWC-128)
______________________________________________________________________
#### Don't delegatecall to untrusted code
The `delegatecall` function is used to call functions from other contracts as if they belong to the
caller contract. Thus the callee may change the state of the calling address. This may be insecure.
An example below shows how using `delegatecall` can lead to the destruction of the contract and
loss of its balance.
```sol
contract Destructor
{
function doWork() external
{
selfdestruct(0);
}
}
contract Worker
{
function doWork(address _internalWorker) public
{
// unsafe
_internalWorker.delegatecall(bytes4(keccak256("doWork()")));
}
}
```
If `Worker.doWork()` is called with the address of the deployed `Destructor` contract as an
argument, the `Worker` contract will self-destruct. Delegate execution only to trusted contracts,
and **never to a user supplied address**.
!!! Warning
Don't assume contracts are created with zero balance. An attacker can send ether to the
address of a contract before it is created so contracts should not assume that their initial state
contains a zero balance. See
[issue 61](https://github.com/ConsenSys/smart-contract-best-practices/issues/61) for more details.
See [SWC-112](https://swcregistry.io/docs/SWC-112)
================================================
FILE: docs/development-recommendations/general/force-feeding.md
================================================
!!! tip
For comprehensive insights into secure development practices, consider visiting the [Development Recommendations](https://scsfg.io/developers/) section of the Smart Contract Security Field Guide. This resource provides in-depth articles to guide you in developing robust and secure smart contracts.
Beware of coding an invariant that strictly checks the balance of a contract.
An attacker can forcibly send ether to any account and this cannot be prevented (not even with a
fallback function that does a `revert()`).
The attacker can do this by creating a contract, funding it with 1 wei, and invoking
`selfdestruct(victimAddress)`. No code is invoked in `victimAddress`, so it cannot be prevented.
This is also true for block reward which is sent to the address of the miner, which can be any
arbitrary address.
Also, since contract addresses can be precomputed, ether can be sent to an address before the
contract is deployed.
See [SWC-132](https://swcregistry.io/docs/SWC-132)
================================================
FILE: docs/development-recommendations/general/negative-int.md
================================================
!!! tip
For comprehensive insights into secure development practices, consider visiting the [Development Recommendations](https://scsfg.io/developers/) section of the Smart Contract Security Field Guide. This resource provides in-depth articles to guide you in developing robust and secure smart contracts.
Solidity provides several types to work with signed integers. Like in most programming languages,
in Solidity a signed integer with `N` bits can represent values from `-2^(N-1)` to `2^(N-1)-1`.
This means that there is no positive equivalent for the `MIN_INT`. Negation is implemented as
finding the two's complement of a number, so the negation of the most negative number
[will result in the same number](https://en.wikipedia.org/wiki/Two%27s_complement#Most_negative_number).
This is true for all signed integer types in Solidity (`int8`, `int16`, ..., `int256`).
```sol
contract Negation {
function negate8(int8 _i) public pure returns(int8) {
return -_i;
}
function negate16(int16 _i) public pure returns(int16) {
return -_i;
}
int8 public a = negate8(-128); // -128
int16 public b = negate16(-128); // 128
int16 public c = negate16(-32768); // -32768
}
```
One way to handle this is to check the value of a variable before negation and throw if it's equal
to the `MIN_INT`. Another option is to make sure that the most negative number will never be
achieved by using a type with a higher capacity (e.g. `int32` instead of `int16`).
A similar issue with `int` types occurs when `MIN_INT` is multiplied or divided by `-1`.
================================================
FILE: docs/development-recommendations/general/participants.md
================================================
!!! tip
For comprehensive insights into secure development practices, consider visiting the [Development Recommendations](https://scsfg.io/developers/) section of the Smart Contract Security Field Guide. This resource provides in-depth articles to guide you in developing robust and secure smart contracts.
Do not make refund or claim processes dependent on a specific party performing a particular action
with no other way of getting the funds out. For example, in a rock-paper-scissors game, one common
mistake is to not make a payout until both players submit their moves; however, a malicious player
can "grief" the other by simply never submitting their move - in fact, if a player sees the other
player's revealed move and determines that they lost, they have no reason to submit their own move
at all. This issue may also arise in the context of state channel settlement. When such situations
are an issue, (1) provide a way of circumventing non-participating participants, perhaps through a
time limit, and (2) consider adding economic incentive for participants to submit information in
all of the situations in which they are supposed to do so.
================================================
FILE: docs/development-recommendations/general/public-data.md
================================================
!!! tip
For comprehensive insights into secure development practices, consider visiting the [Development Recommendations](https://scsfg.io/developers/) section of the Smart Contract Security Field Guide. This resource provides in-depth articles to guide you in developing robust and secure smart contracts.
Many applications require submitted data to be private up until some point in time in order to
work. Games (eg. on-chain rock-paper-scissors) and auction mechanisms (eg. sealed-bid
[Vickrey auctions](https://en.wikipedia.org/wiki/Vickrey_auction)) are two major categories of
examples. If you are building an application where privacy is an issue, make sure you avoid
requiring users to publish information too early. The best strategy is to use
[commitment schemes](https://en.wikipedia.org/wiki/Commitment_scheme) with separate phases: first
commit using the hash of the values and in a later phase revealing the values.
However, care must be taken to ensure that the hashed value stored isn't recognisable (and thus, de-mappable), as this would defeat the second purpose of hashing - preventing the reveal of such values. Here's an example:
Say a smart contract allows 2 players to play rock-paper-scissors, and uses this commit-reveal scheme - both players have to send a hash of their move before either of them sends the last (game ending) transaction. Here's what the keccak256 hash of `rock` is: `10977e4d68108d418408bc9310b60fc6d0a750c63ccef42cfb0ead23ab73d102`. If you were playing, and you saw your opponent commiting this, wouldn't this tell you exactly what move your opponent has committed to? A safer implementation would be to hash not just the name of the move, but also, say, a user chosen salt. That would make the resulting salt non-recognisable.
Examples:
- In rock paper scissors, require both players to submit a hash of their intended move first, then
require both players to submit their move; if the submitted move does not match the hash throw it
out.
- In an auction, require players to submit a hash of their bid value in an initial phase (along
with a deposit greater than their bid value), and then submit their auction bid value in the
second phase.
- When developing an application that depends on a random number generator, the order should always
be *(1)* players submit moves, *(2)* random number generated, *(3)* players paid out. The method
by which random numbers are generated is itself an area of active research; current best-in-class
solutions include Bitcoin block headers (verified through http://btcrelay.org),
hash-commit-reveal schemes (ie. one party generates a number, publishes its hash to "commit" to
the value, and then reveals the value later) and [RANDAO](http://github.com/randao/randao). As
Ethereum is a deterministic protocol, no variable within the protocol could be used as an
unpredictable random number. Also, be aware that [miners are in some extent in control of the
`block.blockhash()`
value](https://ethereum.stackexchange.com/questions/419/when-can-blockhash-be-safely-used-for-a-random-number-when-would-it-be-unsafe).
================================================
FILE: docs/development-recommendations/index.md
================================================
!!! tip
For comprehensive insights into secure development practices, consider visiting the [Development Recommendations](https://scsfg.io/developers/) section of the Smart Contract Security Field Guide. This resource provides in-depth articles to guide you in developing robust and secure smart contracts.
The development recommendations are split into six categories.
| Category | Description |
| ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| [General](./general/external-calls.md) | Guiding principles that should be kept in mind during development. |
| [Precautions](./precautions/general.md) | Principles that prevent attacks in general or avoid excessive damage in the worst case scenario. |
| [Solidity-specific](./solidity-specific/assert-require-revert.md) | Helpful tips when building smart contracts in Solidity - including interesting quirks. |
| [Token-specifc](./token-specific/standardization.md) | Recommendations to honour when dealing with or implementing tokens |
| [Documentation](./documentation/general.md) | Guidelines on how to properly document smart contracts and the processes surrounding them. |
| [Deprecated](./deprecated/division-by-zero.md) | Vulnerabilities that were applicable in the past but can be reasonably excluded nowadays. |
================================================
FILE: docs/development-recommendations/precautions/circuit-breakers.md
================================================
!!! tip
For comprehensive insights into secure development practices, consider visiting the [Development Recommendations](https://scsfg.io/developers/) section of the Smart Contract Security Field Guide. This resource provides in-depth articles to guide you in developing robust and secure smart contracts.
Circuit breakers stop execution if certain conditions are met, and can be useful when new errors
are discovered. For example, most actions may be suspended in a contract if a bug is discovered,
and the only action now active is a withdrawal. You can either give certain trusted parties the
ability to trigger the circuit breaker or else have programmatic rules that automatically trigger
the certain breaker when certain conditions are met.
Example:
```sol
bool private stopped = false;
address private owner;
modifier isAdmin() {
require(msg.sender == owner);
_;
}
function toggleContractActive() isAdmin public {
// You can add an addi
gitextract_7c2uq4a4/ ├── .editorconfig ├── .github/ │ └── workflows/ │ └── main.yml ├── .gitignore ├── CONTRIBUTING.md ├── README-vi.md ├── README-zh.md ├── README.md ├── custom/ │ ├── .icons/ │ │ ├── fontawesome/ │ │ │ └── LICENSE.txt │ │ └── octicons/ │ │ └── LICENSE │ ├── main.html │ └── partials/ │ └── integrations/ │ └── analytics/ │ └── custom.html ├── docs/ │ ├── about/ │ │ ├── index.md │ │ └── license.md │ ├── attacks/ │ │ ├── denial-of-service.md │ │ ├── deprecated.md │ │ ├── force-feeding.md │ │ ├── frontrunning.md │ │ ├── griefing.md │ │ ├── index.md │ │ ├── insecure-arithmetic.md │ │ ├── more.md │ │ ├── oracle-manipulation.md │ │ ├── reentrancy.md │ │ └── timestamp-dependence.md │ ├── bug-bounty-programs.md │ ├── development-recommendations/ │ │ ├── deprecated/ │ │ │ ├── constructor-naming.md │ │ │ ├── division-by-zero.md │ │ │ └── functions-and-events.md │ │ ├── documentation/ │ │ │ ├── contact.md │ │ │ ├── general.md │ │ │ ├── history.md │ │ │ ├── known-issues.md │ │ │ ├── procedures.md │ │ │ ├── specification.md │ │ │ └── status.md │ │ ├── general/ │ │ │ ├── external-calls.md │ │ │ ├── force-feeding.md │ │ │ ├── negative-int.md │ │ │ ├── participants.md │ │ │ └── public-data.md │ │ ├── index.md │ │ ├── precautions/ │ │ │ ├── circuit-breakers.md │ │ │ ├── deployment.md │ │ │ ├── general.md │ │ │ ├── rate-limiting.md │ │ │ ├── safe-haven.md │ │ │ ├── speed-bumps.md │ │ │ └── upgradeability.md │ │ ├── solidity-specific/ │ │ │ ├── abstract-vs-interfaces.md │ │ │ ├── assert-require-revert.md │ │ │ ├── complex-inheritance.md │ │ │ ├── event-monitoring.md │ │ │ ├── extcodesize-checks.md │ │ │ ├── fallback-functions.md │ │ │ ├── integer-division.md │ │ │ ├── interface-types.md │ │ │ ├── locking-pragmas.md │ │ │ ├── modifiers-as-guards.md │ │ │ ├── payability.md │ │ │ ├── shadowing.md │ │ │ ├── timestamp-dependence.md │ │ │ ├── tx-origin.md │ │ │ └── visibility.md │ │ └── token-specific/ │ │ ├── contract-address.md │ │ ├── frontrunning.md │ │ ├── standardization.md │ │ └── zero-address.md │ ├── general-philosophy/ │ │ ├── blockchain-properties.md │ │ ├── index.md │ │ ├── prepare-for-failure.md │ │ ├── rollout.md │ │ ├── simplicity-vs-complexity.md │ │ ├── simplicity.md │ │ └── stay-up-to-date.md │ ├── index.md │ ├── security-tools/ │ │ ├── classification.md │ │ ├── disassemblers.md │ │ ├── index.css │ │ ├── index.md │ │ ├── linters-and-formatters.md │ │ ├── static-and-dynamic-analysis.md │ │ ├── testing.md │ │ ├── verification.md │ │ └── visualization.md │ └── stylesheets/ │ └── extra.css ├── mkdocs.yml └── requirements.txt
Condensed preview — 87 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (284K chars).
[
{
"path": ".editorconfig",
"chars": 99,
"preview": "root = true\n\n[*]\nindent_style = space\nindent_size = 4\nend_of_line = lf\ninsert_final_newline = true\n"
},
{
"path": ".github/workflows/main.yml",
"chars": 823,
"preview": "name: Lint, build, and deploy\non: [push, pull_request]\n\njobs:\n build:\n name: Test and deploy\n runs-on: ubuntu-lat"
},
{
"path": ".gitignore",
"chars": 95,
"preview": ".DS_Store\n.tmp\n.vscode/\n\n# virtualenv directory\n/venv\n.python-version\n\n# build directory\n/site\n"
},
{
"path": "CONTRIBUTING.md",
"chars": 5588,
"preview": "# Contributing to the Smart Contract Security Best Practices\n\nPlease take a moment to review this document to make the c"
},
{
"path": "README-vi.md",
"chars": 80727,
"preview": "# Smart contract best practices\n\n**Notice: this translation was generously provided by a contributor. The maintainers ar"
},
{
"path": "README-zh.md",
"chars": 34998,
"preview": "# 以太坊智能合约 —— 最佳安全开发指南\n\n**Notice: this translation was generously provided by a contributor. The maintainers are not able"
},
{
"path": "README.md",
"chars": 1729,
"preview": "[<img width=\"200\" alt=\"get in touch with Consensys Diligence\" src=\"https://user-images.githubusercontent.com/2865694/568"
},
{
"path": "custom/.icons/fontawesome/LICENSE.txt",
"chars": 1548,
"preview": "Font Awesome Free License\n-------------------------\n\nFont Awesome Free is free, open source, and GPL friendly. You can u"
},
{
"path": "custom/.icons/octicons/LICENSE",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2020 GitHub Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "custom/main.html",
"chars": 332,
"preview": "{% extends \"base.html\" %}\n\n{% block scripts %}\n {{ super() }}\n \n<!-- Google Tag Manager (noscript) -->\n<noscript><ifra"
},
{
"path": "custom/partials/integrations/analytics/custom.html",
"chars": 439,
"preview": "<!-- Google Tag Manager -->\n<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':\n new Date().getTime(),"
},
{
"path": "docs/about/index.md",
"chars": 22,
"preview": "{! CONTRIBUTING.md !}\n"
},
{
"path": "docs/about/license.md",
"chars": 582,
"preview": "Copyright 2016 Smart Contract Best Practices Authors\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); yo"
},
{
"path": "docs/attacks/denial-of-service.md",
"chars": 5698,
"preview": "## DoS with (Unexpected) revert\n\nConsider a simple auction contract:\n\n```sol\n// INSECURE\ncontract Auction {\n address "
},
{
"path": "docs/attacks/deprecated.md",
"chars": 1276,
"preview": "\nThese are attacks which are no longer possible due to changes in the protocol or improvements to\nsolidity. They are rec"
},
{
"path": "docs/attacks/force-feeding.md",
"chars": 3776,
"preview": "!!! tip\n\n Thank you for visiting the Smart Contract Security Best Practices. Please note that this resource is no lon"
},
{
"path": "docs/attacks/frontrunning.md",
"chars": 6547,
"preview": "!!! tip\n\n Thank you for visiting the Smart Contract Security Best Practices. Please note that this resource is no lon"
},
{
"path": "docs/attacks/griefing.md",
"chars": 3220,
"preview": "!!! tip\n\n Thank you for visiting the Smart Contract Security Best Practices. Please note that this resource is no lon"
},
{
"path": "docs/attacks/index.md",
"chars": 2609,
"preview": "!!! tip\n\n Seeking more detailed information on smart contract attacks? The [Smart Contract Security Field Guide](http"
},
{
"path": "docs/attacks/insecure-arithmetic.md",
"chars": 2190,
"preview": "\nConsider a simple token transfer:\n\n```sol\nmapping (address => uint256) public balanceOf;\n\n// INSECURE\nfunction transfer"
},
{
"path": "docs/attacks/more.md",
"chars": 800,
"preview": "The [Smart Contract Weakness Classification Registry](https://smartcontractsecurity.github.io/SWC-registry/)\noffers a co"
},
{
"path": "docs/attacks/oracle-manipulation.md",
"chars": 3013,
"preview": "!!! tip\n\n Thank you for visiting the Smart Contract Security Best Practices. Please note that this resource is no lon"
},
{
"path": "docs/attacks/reentrancy.md",
"chars": 9629,
"preview": "!!! tip\n\n Thank you for visiting the Smart Contract Security Best Practices. Please note that this resource is no lon"
},
{
"path": "docs/attacks/timestamp-dependence.md",
"chars": 387,
"preview": "Be aware that the timestamp of the block can be manipulated by the miner, and all direct and\nindirect uses of the timest"
},
{
"path": "docs/bug-bounty-programs.md",
"chars": 2892,
"preview": "---\nhide:\n - navigation\n---\n\n!!! tip\n\n Looking for comprehensive information on setting up, managing, and operating "
},
{
"path": "docs/development-recommendations/deprecated/constructor-naming.md",
"chars": 312,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/deprecated/division-by-zero.md",
"chars": 520,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/deprecated/functions-and-events.md",
"chars": 942,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/documentation/contact.md",
"chars": 437,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/documentation/general.md",
"chars": 460,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/documentation/history.md",
"chars": 440,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/documentation/known-issues.md",
"chars": 575,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/documentation/procedures.md",
"chars": 733,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/documentation/specification.md",
"chars": 673,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/documentation/status.md",
"chars": 625,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/general/external-calls.md",
"chars": 8524,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/general/force-feeding.md",
"chars": 998,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/general/negative-int.md",
"chars": 1590,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/general/participants.md",
"chars": 1161,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/general/public-data.md",
"chars": 3131,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/index.md",
"chars": 1730,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/precautions/circuit-breakers.md",
"chars": 1323,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/precautions/deployment.md",
"chars": 1420,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/precautions/general.md",
"chars": 1260,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/precautions/rate-limiting.md",
"chars": 1811,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/precautions/safe-haven.md",
"chars": 2089,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/precautions/speed-bumps.md",
"chars": 1908,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/precautions/upgradeability.md",
"chars": 7731,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/solidity-specific/abstract-vs-interfaces.md",
"chars": 1087,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/solidity-specific/assert-require-revert.md",
"chars": 2901,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/solidity-specific/complex-inheritance.md",
"chars": 1912,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/solidity-specific/event-monitoring.md",
"chars": 2628,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/solidity-specific/extcodesize-checks.md",
"chars": 2262,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/solidity-specific/fallback-functions.md",
"chars": 1757,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/solidity-specific/integer-division.md",
"chars": 1110,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/solidity-specific/interface-types.md",
"chars": 1848,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/solidity-specific/locking-pragmas.md",
"chars": 1312,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/solidity-specific/modifiers-as-guards.md",
"chars": 1619,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/solidity-specific/payability.md",
"chars": 802,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/solidity-specific/shadowing.md",
"chars": 1000,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/solidity-specific/timestamp-dependence.md",
"chars": 2965,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/solidity-specific/tx-origin.md",
"chars": 2221,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/solidity-specific/visibility.md",
"chars": 2226,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/token-specific/contract-address.md",
"chars": 1388,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/token-specific/frontrunning.md",
"chars": 842,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/token-specific/standardization.md",
"chars": 690,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/development-recommendations/token-specific/zero-address.md",
"chars": 528,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/general-philosophy/blockchain-properties.md",
"chars": 1043,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/general-philosophy/index.md",
"chars": 1114,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/general-philosophy/prepare-for-failure.md",
"chars": 649,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/general-philosophy/rollout.md",
"chars": 628,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/general-philosophy/simplicity-vs-complexity.md",
"chars": 3180,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/general-philosophy/simplicity.md",
"chars": 693,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/general-philosophy/stay-up-to-date.md",
"chars": 548,
"preview": "!!! tip\n\n For comprehensive insights into secure development practices, consider visiting the [Development Recommenda"
},
{
"path": "docs/index.md",
"chars": 1861,
"preview": "# Ethereum Smart Contract Security Best Practices\n\n!!! tip\n\n Thank you for visiting the Smart Contract Security Best "
},
{
"path": "docs/security-tools/classification.md",
"chars": 301,
"preview": "- [SWC-registry](https://github.com/SmartContractSecurity/SWC-registry/) - SWC definitions and a\n large repository of c"
},
{
"path": "docs/security-tools/disassemblers.md",
"chars": 1305,
"preview": "**Disassemblers**\n\nTools that translate smart contract bytecode into opcodes or EVM assembly.\n\n- [Ethersplay](https://gi"
},
{
"path": "docs/security-tools/index.css",
"chars": 162,
"preview": ".md-typeset__table {\n width: 818px;\n max-width: 100%;\n}\n\n@media only screen and (min-width: 1600px) {\n .md-type"
},
{
"path": "docs/security-tools/index.md",
"chars": 2585,
"preview": "<link href=\"./index.css\" rel=\"stylesheet\"></link>\nThis section is about tools that can detect vulnerabilities or help de"
},
{
"path": "docs/security-tools/linters-and-formatters.md",
"chars": 511,
"preview": "Linters improve code quality by enforcing rules for style and composition, making code easier to\nread and review.\n\n- [Et"
},
{
"path": "docs/security-tools/static-and-dynamic-analysis.md",
"chars": 2543,
"preview": "- [MythX](https://mythx.io) - MythX is a professional-grade cloud service that uses symbolic\n analysis and input fuzzin"
},
{
"path": "docs/security-tools/testing.md",
"chars": 540,
"preview": "- [solidity-coverage](https://github.com/sc-forks/solidity-coverage) - Code coverage for Solidity\n testing.\n- [solidity"
},
{
"path": "docs/security-tools/verification.md",
"chars": 589,
"preview": "- [Scribble](https://consensys.net/diligence/scribble/) - Runtime verification tool for Solidity that enables you to wri"
},
{
"path": "docs/security-tools/visualization.md",
"chars": 1236,
"preview": "- [Solidity Visual Developer](https://marketplace.visualstudio.com/items?itemName=tintinweb.solidity-visual-auditor)\n \\"
},
{
"path": "docs/stylesheets/extra.css",
"chars": 2552,
"preview": "@font-face {\n font-family: 'Graphik';\n src: url('./fonts/Graphik-Bold-Web.woff2') format('woff2'),\n url('./"
},
{
"path": "mkdocs.yml",
"chars": 7299,
"preview": "# Refer to https://github.com/mkdocs/mkdocs/blob/master/mkdocs.yml for a working example\nsite_name: Ethereum Smart Contr"
},
{
"path": "requirements.txt",
"chars": 103,
"preview": "mkdocs\nmkdocs-material\npygments-lexer-solidity\npymdown-extensions\nmarkdown-include\nlinkcheckmd\nrequests"
}
]
About this extraction
This page contains the full source code of the ConsenSysDiligence/smart-contract-best-practices GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 87 files (264.7 KB), approximately 72.0k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.