Skip to content

Commit

Permalink
chore: add tests to burn short in account (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
swissarmytowel authored Oct 5, 2023
1 parent edc641c commit f80c8c0
Show file tree
Hide file tree
Showing 6 changed files with 518 additions and 204 deletions.
394 changes: 194 additions & 200 deletions .gas-snapshot

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion src/settled-cash/AccountCashEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ abstract contract AccountCashEngine is BaseEngine {
* @param _data bytes data to decode
*/
function _burnOptionFromAccount(address _subAccount, bytes calldata _data) internal virtual {
// decode parameters
// decode parameters
(uint256 tokenId, address from, uint64 amount) = abi.decode(_data, (uint256, address, uint64));

Expand Down
2 changes: 0 additions & 2 deletions src/settled-cash/CrossMarginCashEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ import {AccountUtil} from "../libraries/AccountUtil.sol";
import {CrossMarginCashMath} from "./CrossMarginCashMath.sol";
import {CrossMarginCashLib} from "./CrossMarginCashLib.sol";



// Cross margin types
import "./types.sol";
import "../config/errors.sol";
Expand Down
3 changes: 2 additions & 1 deletion src/settled-physical/AccountPhysicalEngine.sol
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ abstract contract AccountPhysicalEngine is BaseEngine {
* @param _data bytes data to decode
*/
function _burnOptionFromAccount(address _subAccount, bytes calldata _data) internal virtual {
// decode parameters
// decode parameters
(uint256 tokenId, address from, uint64 amount) = abi.decode(_data, (uint256, address, uint64));

Expand All @@ -111,6 +110,8 @@ abstract contract AccountPhysicalEngine is BaseEngine {
// update the account in state
_decreaseLongInAccount(from, tokenId, amount);

emit PhysicalOptionTokenBurned(from, tokenId, amount);

// update the account in state
_decreaseShortInAccount(_subAccount, tokenId, amount);

Expand Down
161 changes: 161 additions & 0 deletions test/integrations-cash/BurnToken.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ contract TestBurnOption_CMC is CrossMarginCashFixture {
uint256 public amount = 1 * UNIT;
uint256 public tokenId;

event CashOptionTokenBurned(address subAccount, uint256 tokenId, uint256 amount);

function setUp() public {
weth.mint(address(this), depositAmount);
weth.approve(address(engine), type(uint256).max);
Expand All @@ -43,6 +45,9 @@ contract TestBurnOption_CMC is CrossMarginCashFixture {
ActionArgs[] memory actions = new ActionArgs[](1);
actions[0] = createBurnAction(tokenId, address(this), amount);

vm.expectEmit(true, true, true, true);
emit CashOptionTokenBurned(address(this), tokenId, amount);

// action
engine.execute(address(this), actions);
(Position[] memory shorts,,) = engine.marginAccounts(address(this));
Expand Down Expand Up @@ -105,3 +110,159 @@ contract TestBurnOption_CMC is CrossMarginCashFixture {
engine.execute(address(this), actions);
}
}

// solhint-disable-next-line contract-name-camelcase
contract TestBurnOptionFromAccount_CMC is CrossMarginCashFixture {
uint256 public depositAmount = 1 ether;
uint256 public amount = 1 * UNIT;
uint256 public tokenId;

event CashOptionTokenBurned(address subAccount, uint256 tokenId, uint256 amount);

function setUp() public {
weth.mint(address(this), 1 * 1e18);
weth.approve(address(engine), type(uint256).max);

weth.mint(alice, 1 * 1e18);

vm.startPrank(alice);
weth.approve(address(engine), type(uint256).max);
engine.setAccountAccess(address(this), type(uint256).max);
vm.stopPrank();

oracle.setSpotPrice(address(weth), 1900 * UNIT);

tokenId = getTokenId(TokenType.CALL, pidEthCollat, block.timestamp + 1 days, 4000 * 1e6, 0);

ActionArgs[] memory actions = new ActionArgs[](2);
actions[0] = createAddCollateralAction(wethId, alice, depositAmount);
actions[1] = createMintIntoAccountAction(tokenId, address(this), amount);
engine.execute(alice, actions);

option.setApprovalForAll(address(engine), true);
}

function testBurnFromAccount() public {
Position[] memory shorts;
Position[] memory longs;

(shorts, longs,) = engine.marginAccounts(address(this));

assertEq(shorts.length, 0);
assertEq(longs.length, 1);
assertEq(longs[0].tokenId, tokenId);

(shorts, longs,) = engine.marginAccounts(alice);

assertEq(shorts.length, 1);
assertEq(shorts[0].tokenId, tokenId);
assertEq(longs.length, 0);

ActionArgs[] memory actions = new ActionArgs[](1);
actions[0] = ActionArgs({action: ActionType.BurnShortInAccount, data: abi.encode(tokenId, address(this), uint64(amount))});

// decreases longs
vm.expectEmit(true, true, true, true);
emit CashOptionTokenBurned(address(this), tokenId, amount);

// decreases shorts
vm.expectEmit(true, true, true, true);
emit CashOptionTokenBurned(alice, tokenId, amount);

engine.execute(alice, actions);

(shorts, longs,) = engine.marginAccounts(address(this));

assertEq(shorts.length, 0);
assertEq(longs.length, 0);

(shorts, longs,) = engine.marginAccounts(alice);

assertEq(shorts.length, 0);
assertEq(longs.length, 0);
}

function testCanBurnFromSubAccount() public {
address subAccount = address(uint160(address(this)) ^ uint160(1));

ActionArgs[] memory actions = new ActionArgs[](1);
actions[0] = createAddCollateralAction(wethId, address(this), depositAmount);
engine.execute(subAccount, actions);

actions[0] = createTransferShortAction(tokenId, subAccount, amount);
engine.execute(alice, actions);

Position[] memory shorts;
Position[] memory longs;

(shorts, longs,) = engine.marginAccounts(address(this));

assertEq(shorts.length, 0);
assertEq(longs.length, 1);
assertEq(longs[0].tokenId, tokenId);

(shorts, longs,) = engine.marginAccounts(subAccount);

assertEq(shorts.length, 1);
assertEq(shorts[0].tokenId, tokenId);
assertEq(longs.length, 0);

(shorts, longs,) = engine.marginAccounts(alice);

assertEq(shorts.length, 0);
assertEq(longs.length, 0);

actions[0] = ActionArgs({action: ActionType.BurnShortInAccount, data: abi.encode(tokenId, address(this), uint64(amount))});

// decreases longs
vm.expectEmit(true, true, true, true);
emit CashOptionTokenBurned(address(this), tokenId, amount);

// decreases shorts
vm.expectEmit(true, true, true, true);
emit CashOptionTokenBurned(subAccount, tokenId, amount);

engine.execute(subAccount, actions);
}

function testCannotBurnFromEmptySubAccount() public {
address subAccount = address(uint160(address(this)) ^ uint160(1));

ActionArgs[] memory actions = new ActionArgs[](1);
actions[0] = ActionArgs({action: ActionType.BurnShortInAccount, data: abi.encode(tokenId, address(this), uint64(amount))});

vm.expectRevert(CM_InvalidToken.selector);
engine.execute(subAccount, actions);
}

function testCannotBurnFromWithWrongTokenId() public {
uint256 badTokenId = getTokenId(TokenType.CALL, pidUsdcCollat, block.timestamp + 1 days, 4000 * 1e6, 0);

ActionArgs[] memory actions = new ActionArgs[](1);
actions[0] =
ActionArgs({action: ActionType.BurnShortInAccount, data: abi.encode(badTokenId, address(this), uint64(amount))});

vm.expectRevert(CM_InvalidToken.selector);
engine.execute(alice, actions);
}

function testCannotBurnWhenOptionTokenBalanceIsLow() public {
vm.prank(address(engine));
option.safeTransferFrom(address(engine), address(this), tokenId, 1, "");

ActionArgs[] memory actions = new ActionArgs[](1);
actions[0] = ActionArgs({action: ActionType.BurnShortInAccount, data: abi.encode(tokenId, address(this), uint64(amount))});

vm.expectRevert(stdError.arithmeticError);
engine.execute(alice, actions);
}

function testCannotBurnFromUnAuthorizedAccount() public {
ActionArgs[] memory actions = new ActionArgs[](1);
actions[0] = ActionArgs({action: ActionType.BurnShortInAccount, data: abi.encode(tokenId, alice, uint64(amount))});

// expect error
vm.expectRevert(BM_InvalidFromAddress.selector);
engine.execute(address(this), actions);
}
}
161 changes: 161 additions & 0 deletions test/integrations-physical/BurnToken.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ contract TestBurnOption_CMP is CrossMarginPhysicalFixture {
uint256 public amount = 1 * UNIT;
uint256 public tokenId;

event PhysicalOptionTokenBurned(address subAccount, uint256 tokenId, uint256 amount);

function setUp() public {
weth.mint(address(this), depositAmount);
weth.approve(address(engine), type(uint256).max);
Expand All @@ -42,6 +44,9 @@ contract TestBurnOption_CMP is CrossMarginPhysicalFixture {
ActionArgs[] memory actions = new ActionArgs[](1);
actions[0] = createBurnAction(tokenId, address(this), amount);

vm.expectEmit(true, true, true, true);
emit PhysicalOptionTokenBurned(address(this), tokenId, amount);

// action
engine.execute(address(this), actions);
(Position[] memory shorts,,) = engine.marginAccounts(address(this));
Expand Down Expand Up @@ -104,3 +109,159 @@ contract TestBurnOption_CMP is CrossMarginPhysicalFixture {
engine.execute(address(this), actions);
}
}

// solhint-disable-next-line contract-name-camelcase
contract TestBurnOptionFromAccount_CMP is CrossMarginPhysicalFixture {
uint256 public depositAmount = 1 ether;
uint256 public amount = 1 * UNIT;
uint256 public tokenId;

event PhysicalOptionTokenBurned(address subAccount, uint256 tokenId, uint256 amount);

function setUp() public {
weth.mint(address(this), 1 * 1e18);
weth.approve(address(engine), type(uint256).max);

weth.mint(alice, 1 * 1e18);

vm.startPrank(alice);
weth.approve(address(engine), type(uint256).max);
engine.setAccountAccess(address(this), type(uint256).max);
vm.stopPrank();

oracle.setSpotPrice(address(weth), 1900 * UNIT);

tokenId = getTokenId(TokenType.CALL, pidEthCollat, block.timestamp + 1 days, 4000 * 1e6, 30 minutes);

ActionArgs[] memory actions = new ActionArgs[](2);
actions[0] = createAddCollateralAction(wethId, alice, depositAmount);
actions[1] = createMintIntoAccountAction(tokenId, address(this), amount);
engine.execute(alice, actions);

option.setApprovalForAll(address(engine), true);
}

function testBurnFromAccount() public {
Position[] memory shorts;
Position[] memory longs;

(shorts, longs,) = engine.marginAccounts(address(this));

assertEq(shorts.length, 0);
assertEq(longs.length, 1);
assertEq(longs[0].tokenId, tokenId);

(shorts, longs,) = engine.marginAccounts(alice);

assertEq(shorts.length, 1);
assertEq(shorts[0].tokenId, tokenId);
assertEq(longs.length, 0);

ActionArgs[] memory actions = new ActionArgs[](1);
actions[0] = ActionArgs({action: ActionType.BurnShortInAccount, data: abi.encode(tokenId, address(this), uint64(amount))});

// decreases longs
vm.expectEmit(true, true, true, true);
emit PhysicalOptionTokenBurned(address(this), tokenId, amount);

// decreases shorts
vm.expectEmit(true, true, true, true);
emit PhysicalOptionTokenBurned(alice, tokenId, amount);

engine.execute(alice, actions);

(shorts, longs,) = engine.marginAccounts(address(this));

assertEq(shorts.length, 0);
assertEq(longs.length, 0);

(shorts, longs,) = engine.marginAccounts(alice);

assertEq(shorts.length, 0);
assertEq(longs.length, 0);
}

function testCanBurnFromSubAccount() public {
address subAccount = address(uint160(address(this)) ^ uint160(1));

ActionArgs[] memory actions = new ActionArgs[](1);
actions[0] = createAddCollateralAction(wethId, address(this), depositAmount);
engine.execute(subAccount, actions);

actions[0] = createTransferShortAction(tokenId, subAccount, amount);
engine.execute(alice, actions);

Position[] memory shorts;
Position[] memory longs;

(shorts, longs,) = engine.marginAccounts(address(this));

assertEq(shorts.length, 0);
assertEq(longs.length, 1);
assertEq(longs[0].tokenId, tokenId);

(shorts, longs,) = engine.marginAccounts(subAccount);

assertEq(shorts.length, 1);
assertEq(shorts[0].tokenId, tokenId);
assertEq(longs.length, 0);

(shorts, longs,) = engine.marginAccounts(alice);

assertEq(shorts.length, 0);
assertEq(longs.length, 0);

actions[0] = ActionArgs({action: ActionType.BurnShortInAccount, data: abi.encode(tokenId, address(this), uint64(amount))});

// decreases longs
vm.expectEmit(true, true, true, true);
emit PhysicalOptionTokenBurned(address(this), tokenId, amount);

// decreases shorts
vm.expectEmit(true, true, true, true);
emit PhysicalOptionTokenBurned(subAccount, tokenId, amount);

engine.execute(subAccount, actions);
}

function testCannotBurnFromEmptySubAccount() public {
address subAccount = address(uint160(address(this)) ^ uint160(1));

ActionArgs[] memory actions = new ActionArgs[](1);
actions[0] = ActionArgs({action: ActionType.BurnShortInAccount, data: abi.encode(tokenId, address(this), uint64(amount))});

vm.expectRevert(CM_InvalidToken.selector);
engine.execute(subAccount, actions);
}

function testCannotBurnFromWithWrongTokenId() public {
uint256 badTokenId = getTokenId(TokenType.CALL, pidUsdcCollat, block.timestamp + 1 days, 4000 * 1e6, 30 minutes);

ActionArgs[] memory actions = new ActionArgs[](1);
actions[0] =
ActionArgs({action: ActionType.BurnShortInAccount, data: abi.encode(badTokenId, address(this), uint64(amount))});

vm.expectRevert(CM_InvalidToken.selector);
engine.execute(alice, actions);
}

function testCannotBurnWhenOptionTokenBalanceIsLow() public {
vm.prank(address(engine));
option.safeTransferFrom(address(engine), address(this), tokenId, 1, "");

ActionArgs[] memory actions = new ActionArgs[](1);
actions[0] = ActionArgs({action: ActionType.BurnShortInAccount, data: abi.encode(tokenId, address(this), uint64(amount))});

vm.expectRevert(stdError.arithmeticError);
engine.execute(alice, actions);
}

function testCannotBurnFromUnAuthorizedAccount() public {
ActionArgs[] memory actions = new ActionArgs[](1);
actions[0] = ActionArgs({action: ActionType.BurnShortInAccount, data: abi.encode(tokenId, alice, uint64(amount))});

// expect error
vm.expectRevert(BM_InvalidFromAddress.selector);
engine.execute(address(this), actions);
}
}

0 comments on commit f80c8c0

Please sign in to comment.