├── biddog.png
├── .env.example
├── funding.json
├── .gitignore
├── foundry.toml
├── .gitmodules
├── test
├── mocks
│ ├── ERC20Mock.sol
│ └── AmAmmMock.sol
└── AmAmm.t.sol
├── .github
└── workflows
│ └── test.yml
├── src
├── libraries
│ └── BlockNumberLib.sol
├── interfaces
│ └── IAmAmm.sol
└── AmAmm.sol
├── README.md
└── LICENSE
/biddog.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Bunniapp/biddog/HEAD/biddog.png
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | # Network and account info
2 | RPC_URL_MAINNET=XXX
3 |
4 | PRIVATE_KEY=XXX
5 |
6 | ETHERSCAN_KEY=XXX
7 |
--------------------------------------------------------------------------------
/funding.json:
--------------------------------------------------------------------------------
1 | {
2 | "opRetro": {
3 | "projectId": "0xcc230da6ee8cbcc2d4039a526fbc8aa910d63201e350be2fae0cab9864194974"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiler files
2 | cache/
3 | out/
4 |
5 | # Ignores development broadcast logs
6 | !/broadcast
7 | /broadcast/*/31337/
8 | /broadcast/**/dry-run/
9 |
10 | # Dotenv file
11 | .env
12 |
13 | .vscode
--------------------------------------------------------------------------------
/foundry.toml:
--------------------------------------------------------------------------------
1 | [profile.default]
2 | optimizer_runs = 1000000
3 | remappings = [
4 | "@uniswap/v4-core/=lib/v4-core/",
5 | ]
6 | verbosity = 1
7 | via_ir = true
8 |
9 | # Extreme Fuzzing CI Profile :P
10 | [profile.ci]
11 | fuzz_runs = 100_000
12 | verbosity = 4
13 |
14 | [rpc_endpoints]
15 | mainnet = "${RPC_URL_MAINNET}"
16 |
17 | [etherscan]
18 | mainnet = {key = "${ETHERSCAN_KEY}"}
19 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lib/forge-std"]
2 | path = lib/forge-std
3 | url = https://github.com/foundry-rs/forge-std
4 | [submodule "lib/multicaller"]
5 | path = lib/multicaller
6 | url = https://github.com/vectorized/multicaller
7 | [submodule "lib/solady"]
8 | path = lib/solady
9 | url = https://github.com/vectorized/solady
10 | [submodule "lib/v4-core"]
11 | path = lib/v4-core
12 | url = https://github.com/uniswap/v4-core
13 |
--------------------------------------------------------------------------------
/test/mocks/ERC20Mock.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GPL-3.0
2 |
3 | pragma solidity ^0.8.0;
4 |
5 | import {ERC20} from "solady/tokens/ERC20.sol";
6 |
7 | contract ERC20Mock is ERC20 {
8 | function mint(address to, uint256 amount) public {
9 | _mint(to, amount);
10 | }
11 |
12 | function burn(uint256 amount) public {
13 | _burn(msg.sender, amount);
14 | }
15 |
16 | function name() public pure override returns (string memory) {
17 | return "MockERC20";
18 | }
19 |
20 | function symbol() public pure override returns (string memory) {
21 | return "MOCK-ERC20";
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 |
3 | on: workflow_dispatch
4 |
5 | env:
6 | FOUNDRY_PROFILE: ci
7 |
8 | jobs:
9 | check:
10 | strategy:
11 | fail-fast: true
12 |
13 | name: Foundry project
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v3
17 | with:
18 | submodules: recursive
19 |
20 | - name: Install Foundry
21 | uses: foundry-rs/foundry-toolchain@v1
22 | with:
23 | version: nightly
24 |
25 | - name: Run Forge build
26 | run: |
27 | forge --version
28 | forge build --sizes
29 | id: build
30 |
31 | - name: Run Forge tests
32 | run: |
33 | FOUNDRY_PROFILE=ci forge test -vvv
34 | id: test
35 |
--------------------------------------------------------------------------------
/src/libraries/BlockNumberLib.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: MIT
2 |
3 | pragma solidity ^0.8.19;
4 |
5 | interface ArbSys {
6 | /// @notice Returns the L2 block number.
7 | /// @dev Arbitrum requires using the ArbSys precompile to fetch the L2 block number
8 | /// See https://docs.arbitrum.io/build-decentralized-apps/arbitrum-vs-ethereum/block-numbers-and-time#arbitrum-block-numbers
9 | function arbBlockNumber() external view returns (uint256);
10 | }
11 |
12 | library BlockNumberLib {
13 | uint256 private constant ARBITRUM_ONE_ID = 42161;
14 | ArbSys private constant ARB_SYS = ArbSys(address(100));
15 |
16 | function getBlockNumber() internal view returns (uint256) {
17 | return block.chainid == ARBITRUM_ONE_ID ? ARB_SYS.arbBlockNumber() : block.number;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/test/mocks/AmAmmMock.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.19;
3 |
4 | import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol";
5 | import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
6 |
7 | import "./ERC20Mock.sol";
8 | import "../../src/AmAmm.sol";
9 |
10 | contract AmAmmMock is AmAmm {
11 | using CurrencyLibrary for Currency;
12 |
13 | ERC20Mock public immutable bidToken;
14 | ERC20Mock public immutable feeToken0;
15 | ERC20Mock public immutable feeToken1;
16 |
17 | mapping(PoolId id => bool) public enabled;
18 | mapping(PoolId id => uint128) public minRent;
19 | mapping(PoolId id => uint24) public maxSwapFee;
20 |
21 | constructor(ERC20Mock _bidToken, ERC20Mock _feeToken0, ERC20Mock _feeToken1) {
22 | bidToken = _bidToken;
23 | feeToken0 = _feeToken0;
24 | feeToken1 = _feeToken1;
25 | }
26 |
27 | function setEnabled(PoolId id, bool value) external {
28 | enabled[id] = value;
29 | }
30 |
31 | function setMinRent(PoolId id, uint128 value) external {
32 | minRent[id] = value;
33 | }
34 |
35 | function setMaxSwapFee(PoolId id, uint24 value) external {
36 | maxSwapFee[id] = value;
37 | }
38 |
39 | function giveFeeToken0(PoolId id, uint256 amount) external {
40 | _updateAmAmmWrite(id);
41 | address manager = _topBids[id].manager;
42 | feeToken0.mint(address(this), amount);
43 | _accrueFees(manager, Currency.wrap(address(feeToken0)), amount);
44 | }
45 |
46 | function giveFeeToken1(PoolId id, uint256 amount) external {
47 | _updateAmAmmWrite(id);
48 | address manager = _topBids[id].manager;
49 | feeToken1.mint(address(this), amount);
50 | _accrueFees(manager, Currency.wrap(address(feeToken1)), amount);
51 | }
52 |
53 | function MIN_RENT(PoolId id) internal view override returns (uint128) {
54 | return minRent[id];
55 | }
56 |
57 | /// @dev Returns whether the am-AMM is enabled for a given pool
58 | function _amAmmEnabled(PoolId id) internal view override returns (bool) {
59 | return enabled[id];
60 | }
61 |
62 | /// @dev Validates a bid payload
63 | function _payloadIsValid(PoolId id, bytes6 payload) internal view override returns (bool) {
64 | // first 3 bytes of payload are the swap fee
65 | return uint24(bytes3(payload)) <= maxSwapFee[id];
66 | }
67 |
68 | /// @dev Burns bid tokens from address(this)
69 | function _burnBidToken(PoolId, uint256 amount) internal override {
70 | bidToken.burn(amount);
71 | }
72 |
73 | /// @dev Transfers bid tokens from an address that's not address(this) to address(this)
74 | function _pullBidToken(PoolId, address from, uint256 amount) internal override {
75 | bidToken.transferFrom(from, address(this), amount);
76 | }
77 |
78 | /// @dev Transfers bid tokens from address(this) to an address that's not address(this)
79 | function _pushBidToken(PoolId, address to, uint256 amount) internal override {
80 | bidToken.transfer(to, amount);
81 | }
82 |
83 | /// @dev Transfers accrued fees from address(this)
84 | function _transferFeeToken(Currency currency, address to, uint256 amount) internal override {
85 | currency.transfer(to, amount);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/interfaces/IAmAmm.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 |
3 | pragma solidity ^0.8.0;
4 |
5 | import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol";
6 | import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
7 |
8 | interface IAmAmm {
9 | error AmAmm__BidLocked();
10 | error AmAmm__InvalidBid();
11 | error AmAmm__NotEnabled();
12 | error AmAmm__Unauthorized();
13 | error AmAmm__InvalidDepositAmount();
14 |
15 | event SubmitBid(
16 | PoolId indexed id,
17 | address indexed manager,
18 | uint48 indexed blockIdx,
19 | bytes6 payload,
20 | uint128 rent,
21 | uint128 deposit
22 | );
23 | event DepositIntoTopBid(PoolId indexed id, address indexed manager, uint128 amount);
24 | event WithdrawFromTopBid(PoolId indexed id, address indexed manager, address indexed recipient, uint128 amount);
25 | event DepositIntoNextBid(PoolId indexed id, address indexed manager, uint128 amount);
26 | event WithdrawFromNextBid(PoolId indexed id, address indexed manager, address indexed recipient, uint128 amount);
27 | event ClaimRefund(PoolId indexed id, address indexed manager, address indexed recipient, uint256 refund);
28 | event ClaimFees(Currency indexed currency, address indexed manager, address indexed recipient, uint256 fees);
29 | event SetBidPayload(PoolId indexed id, address indexed manager, bytes6 payload, bool topBid);
30 | event IncreaseBidRent(
31 | PoolId indexed id,
32 | address indexed manager,
33 | uint128 additionalRent,
34 | uint128 updatedDeposit,
35 | bool topBid,
36 | address indexed withdrawRecipient,
37 | uint128 amountDeposited,
38 | uint128 amountWithdrawn
39 | );
40 |
41 | struct Bid {
42 | address manager;
43 | uint48 blockIdx; // block number (minus contract deployment block) when the bid was created / last charged rent
44 | bytes6 payload; // payload specifying what parames the manager wants, e.g. swap fee
45 | uint128 rent; // rent per block
46 | uint128 deposit; // rent deposit amount
47 | }
48 |
49 | /// @notice Places a bid to become the manager of a pool
50 | /// @param id The pool id
51 | /// @param manager The address of the manager
52 | /// @param payload The payload specifying what parameters the manager wants, e.g. swap fee
53 | /// @param rent The rent per block
54 | /// @param deposit The deposit amount, must be a multiple of rent and cover rent for >=K blocks
55 | function bid(PoolId id, address manager, bytes6 payload, uint128 rent, uint128 deposit) external;
56 |
57 | /// @notice Adds deposit to the top/next bid. Only callable by topBids[id].manager or nextBids[id].manager (depending on `isTopBid`).
58 | /// @param id The pool id
59 | /// @param amount The amount to deposit, must be a multiple of rent
60 | /// @param isTopBid True if the top bid manager is depositing, false if the next bid manager is depositing
61 | function depositIntoBid(PoolId id, uint128 amount, bool isTopBid) external;
62 |
63 | /// @notice Withdraws from the deposit of the top/next bid. Only callable by topBids[id].manager or nextBids[id].manager (depending on `isTopBid`). Reverts if D / R < K.
64 | /// @param id The pool id
65 | /// @param amount The amount to withdraw, must be a multiple of rent and leave D / R >= K
66 | /// @param recipient The address of the recipient
67 | /// @param isTopBid True if the top bid manager is withdrawing, false if the next bid manager is withdrawing
68 | function withdrawFromBid(PoolId id, uint128 amount, address recipient, bool isTopBid) external;
69 |
70 | /// @notice Claims the refundable deposit of a pool owed to msg.sender.
71 | /// @param id The pool id
72 | /// @param recipient The address of the manager
73 | /// @return refund The amount of refund claimed
74 | function claimRefund(PoolId id, address recipient) external returns (uint256 refund);
75 |
76 | /// @notice Claims the accrued fees of msg.sender.
77 | /// @param currency The currency of the fees
78 | /// @param recipient The address of the recipient
79 | /// @return fees The amount of fees claimed
80 | function claimFees(Currency currency, address recipient) external returns (uint256 fees);
81 |
82 | /// @notice Increases the rent of a bid. Only callable by the manager of the relevant bid. Reverts if D / R < K after the update.
83 | /// Reverts if updated deposit is not a multiple of the new rent. Noop if additionalRent is 0. Will take/send the difference between the old and new deposits.
84 | /// @param id The pool id
85 | /// @param additionalRent The additional rent to add
86 | /// @param updatedDeposit The updated deposit amount of the bid
87 | /// @param isTopBid True if the top bid manager is increasing the rent and deposit, false if the next bid manager is increasing the rent and deposit
88 | /// @param withdrawRecipient The address to withdraw the difference between the old and new deposits to
89 | /// @return amountDeposited The amount of deposit added, if any
90 | /// @return amountWithdrawn The amount of deposit withdrawn, if any
91 | function increaseBidRent(
92 | PoolId id,
93 | uint128 additionalRent,
94 | uint128 updatedDeposit,
95 | bool isTopBid,
96 | address withdrawRecipient
97 | ) external returns (uint128 amountDeposited, uint128 amountWithdrawn);
98 |
99 | /// @notice Sets the payload of a pool. Only callable by the manager of either the top bid or the next bid.
100 | /// @param id The pool id
101 | /// @param payload The payload specifying e.g. the swap fee
102 | /// @param isTopBid True if the top bid manager is setting the fee, false if the next bid manager is setting the fee
103 | function setBidPayload(PoolId id, bytes6 payload, bool isTopBid) external;
104 |
105 | /// @notice Gets the top/next bid of a pool
106 | /// @param id The pool id
107 | /// @param isTopBid True if the top bid is requested, false if the next bid is requested
108 | function getBid(PoolId id, bool isTopBid) external view returns (Bid memory);
109 |
110 | /// @notice Updates the am-AMM state of a pool and then gets the top/next bid
111 | /// @param id The pool id
112 | /// @param isTopBid True if the top bid is requested, false if the next bid is requested
113 | function getBidWrite(PoolId id, bool isTopBid) external returns (Bid memory);
114 |
115 | /// @notice Gets the refundable deposit of a pool
116 | /// @param manager The address of the manager
117 | /// @param id The pool id
118 | function getRefund(address manager, PoolId id) external view returns (uint256);
119 |
120 | /// @notice Updates the am-AMM state of a pool and then gets the refundable deposit owed to a manager in that pool
121 | /// @param manager The address of the manager
122 | /// @param id The pool id
123 | function getRefundWrite(address manager, PoolId id) external returns (uint256);
124 |
125 | /// @notice Gets the fees accrued by a manager
126 | /// @param manager The address of the manager
127 | /// @param currency The currency of the fees
128 | function getFees(address manager, Currency currency) external view returns (uint256);
129 |
130 | /// @notice Triggers a state machine update for the given pool
131 | /// @param id The pool id
132 | function updateStateMachine(PoolId id) external;
133 | }
134 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BidDog
2 |
3 |
4 |
5 | BidDog is an open-source implementation of am-AMM ([Auction-Managed Automated Market Maker](http://arxiv.org/abs/2403.03367v1)), an add-on to decentralized exchanges that minimizes losses to arbitrage.
6 |
7 | ## Concepts
8 |
9 | - **Block Index (blockIdx)**: A block number minus the block number of the contract deployment.
10 | - **K**: The delay (in blocks) of a bid being submitted and the bidder becoming the manager of a pool. Set to 7200 by default.
11 | - **`MIN_BID_MULTIPLIER`**: Specifies the minimum bid increment. Set to (1 + 10%) by default.
12 | - **Manager**: The manager of a pool pays rent (in the bid token) each block for the privilege of receiving all swap fee revenue and setting the swap fee.
13 | - **Bid**: Each bid in the auction specifies its rent (amount of bid tokens paid per block) and deposit (used to pay rent, >= rent \* K). The bid with the highest rent wins the auction.
14 | - **Bid token**: The token used for bidding in the auction, usually the LP token of a DEX pool. Burnt over time by the manager to pay the rent to the remaining LPs.
15 | - **Fee token**: Token collected as swap fee revenue. There are usually 2 fee tokens for each DEX pool.
16 | - **Refund**: If you currently own the next bid and someone else makes a higher bid, your deposit is refunded to you, which you will need to claim.
17 | - **Payload**: Custom payload attached to a bid, e.g. the desired swap fee. `bytes6` is used to allow implementers to customize how the payload is interpreted.
18 |
19 | ## Developer usage
20 |
21 | Import `biddog/AmAmm.sol` and inherit from `AmAmm`, then implement the following functions based on the specifics of your DEX:
22 |
23 | ```solidity
24 | /// @dev Returns whether the am-AMM is enabled for a given pool
25 | function _amAmmEnabled(PoolId id) internal view virtual returns (bool);
26 |
27 | /// @dev Validates a bid payload, e.g. ensure the swap fee is below a certain threshold
28 | function _payloadIsValid(PoolId id, bytes6 payload) internal view virtual returns (bool);
29 |
30 | /// @dev Burns bid tokens from address(this)
31 | function _burnBidToken(PoolId id, uint256 amount) internal virtual;
32 |
33 | /// @dev Transfers bid tokens from an address that's not address(this) to address(this)
34 | function _pullBidToken(PoolId id, address from, uint256 amount) internal virtual;
35 |
36 | /// @dev Transfers bid tokens from address(this) to an address that's not address(this)
37 | function _pushBidToken(PoolId id, address to, uint256 amount) internal virtual;
38 |
39 | /// @dev Transfers accrued fees from address(this)
40 | function _transferFeeToken(Currency currency, address to, uint256 amount) internal virtual;
41 | ```
42 |
43 | When you need to query the current manager & swap fee value, use:
44 |
45 | ```solidity
46 | /// @dev Charges rent and updates the top and next bids for a given pool
47 | function _updateAmAmmWrite(PoolId id) internal virtual returns (address manager, uint24 swapFee);
48 |
49 | /// @dev View version of _updateAmAmmWrite()
50 | function _updateAmAmm(PoolId id) internal view virtual returns (Bid memory topBid, Bid memory nextBid);
51 | ```
52 |
53 | When your DEX needs to accrue swap fees to a manager, use:
54 |
55 | ```solidity
56 | /// @dev Accrues swap fees to the manager
57 | function _accrueFees(address manager, Currency currency, uint256 amount) internal virtual;
58 | ```
59 |
60 | Optionally, you can override the constants used by am-AMM:
61 |
62 | ```solidity
63 | function K(PoolId) internal view virtual returns (uint40) {
64 | return 7200;
65 | }
66 |
67 | function MIN_BID_MULTIPLIER(PoolId) internal view virtual returns (uint256) {
68 | return 1.1e18;
69 | }
70 |
71 | function MIN_RENT(PoolId) internal view virtual returns (uint128) {
72 | return 0;
73 | }
74 | ```
75 |
76 | ## Manager usage
77 |
78 | ```solidity
79 | /// @notice Places a bid to become the manager of a pool
80 | /// @param id The pool id
81 | /// @param manager The address of the manager
82 | /// @param payload The payload specifying what parameters the manager wants, e.g. swap fee
83 | /// @param rent The rent per block
84 | /// @param deposit The deposit amount, must be a multiple of rent and cover rent for >=K blocks
85 | function bid(PoolId id, address manager, bytes6 payload, uint128 rent, uint128 deposit) external;
86 |
87 | /// @notice Adds deposit to the top bid. Only callable by topBids[id].manager.
88 | /// @param id The pool id
89 | /// @param amount The amount to deposit, must be a multiple of rent
90 | function depositIntoTopBid(PoolId id, uint128 amount) external;
91 |
92 | /// @notice Withdraws from the deposit of the top bid. Only callable by topBids[id].manager. Reverts if D_top / R_top < K.
93 | /// @param id The pool id
94 | /// @param amount The amount to withdraw, must be a multiple of rent and leave D_top / R_top >= K
95 | /// @param recipient The address of the recipient
96 | function withdrawFromTopBid(PoolId id, uint128 amount, address recipient) external;
97 |
98 | /// @notice Adds deposit to the next bid. Only callable by nextBids[id].manager.
99 | /// @param id The pool id
100 | /// @param amount The amount to deposit, must be a multiple of rent
101 | function depositIntoNextBid(PoolId id, uint128 amount) external;
102 |
103 | /// @notice Withdraws from the deposit of the next bid. Only callable by nextBids[id].manager. Reverts if D_next / R_next < K.
104 | /// @param id The pool id
105 | /// @param amount The amount to withdraw, must be a multiple of rent and leave D_next / R_next >= K
106 | /// @param recipient The address of the recipient
107 | function withdrawFromNextBid(PoolId id, uint128 amount, address recipient) external;
108 |
109 | /// @notice Cancels the next bid. Only callable by nextBids[id].manager. Reverts if D_top / R_top < K.
110 | /// @param id The pool id
111 | /// @param recipient The address of the recipient
112 | /// @return refund The amount of refund claimed
113 | function cancelNextBid(PoolId id, address recipient) external returns (uint256 refund);
114 |
115 | /// @notice Claims the refundable deposit of a pool owed to msg.sender.
116 | /// @param id The pool id
117 | /// @param recipient The address of the manager
118 | /// @return refund The amount of refund claimed
119 | function claimRefund(PoolId id, address recipient) external returns (uint256 refund);
120 |
121 | /// @notice Claims the accrued fees of msg.sender.
122 | /// @param currency The currency of the fees
123 | /// @param recipient The address of the recipient
124 | /// @return fees The amount of fees claimed
125 | function claimFees(Currency currency, address recipient) external returns (uint256 fees);
126 |
127 | /// @notice Increases the rent of a bid. Only callable by the manager of the relevant bid. Reverts if D / R < K after the update.
128 | /// Reverts if updated deposit is not a multiple of the new rent. Noop if additionalRent is 0. Will take/send the difference between the old and new deposits.
129 | /// @param id The pool id
130 | /// @param additionalRent The additional rent to add
131 | /// @param updatedDeposit The updated deposit amount of the bid
132 | /// @param topBid True if the top bid manager is increasing the rent and deposit, false if the next bid manager is increasing the rent and deposit
133 | /// @param withdrawRecipient The address to withdraw the difference between the old and new deposits to
134 | /// @return amountDeposited The amount of deposit added, if any
135 | /// @return amountWithdrawn The amount of deposit withdrawn, if any
136 | function increaseBidRent(
137 | PoolId id,
138 | uint128 additionalRent,
139 | uint128 updatedDeposit,
140 | bool topBid,
141 | address withdrawRecipient
142 | ) external returns (uint128 amountDeposited, uint128 amountWithdrawn);
143 |
144 | /// @notice Sets the payload of a pool. Only callable by the manager of either the top bid or the next bid.
145 | /// @param id The pool id
146 | /// @param payload The payload specifying e.g. the swap fee
147 | /// @param topBid True if the top bid manager is setting the fee, false if the next bid manager is setting the fee
148 | function setBidPayload(PoolId id, bytes6 payload, bool topBid) external;
149 | ```
150 |
151 | ## Design
152 |
153 | BidDog was built as a state machine with the following state transitions:
154 |
155 | ```
156 | after
157 | ┌───────────────────────deposit ───────────────────┐
158 | │ depletes │
159 | ▼ │
160 | ┌────────────────────────┐ ┌────────────────────────┐
161 | │ │ │ │
162 | │ State A │ │ State B │
163 | │ Manager: nil │ ┌───────────▶│ Manager: r0 │◀─┐
164 | │ Next: nil │ │ │ Next: nil │ │
165 | │ │ │ │ │ │
166 | └────────────────────────┘ │ └────────────────────────┘ │
167 | │ │ │ │
168 | │ │ │ │
169 | │ │ │ │
170 | │ │ │ │
171 | bid(r) after K bid(r) after K
172 | │ blocks │ blocks
173 | │ │ │ │
174 | │ │ │ │
175 | │ │ after │ │
176 | ├────────────────────────┼──deposit ───────────────┼──────────────┤
177 | │ │ depletes │ │
178 | ▼ │ ▼ │
179 | ┌────────────────────────┐ │ ┌────────────────────────┐ │
180 | │ │ │ │ │ │
181 | │ State C │ │ │ State D │ │
182 | ┌─▶│ Manager: nil │────────────┘ ┌─▶│ Manager: r0 │──┘
183 | │ │ Next: r │ │ │ Next: r │
184 | │ │ │ │ │ │
185 | │ └────────────────────────┘ │ └────────────────────────┘
186 | │ │ │ │
187 | │ │ │ │
188 | └─────bid(r)────┘ └─────bid(r)────┘
189 | ```
190 |
191 | ## Modifications
192 |
193 | Several modifications were made on the original am-AMM design to improve UX.
194 |
195 | - When withdrawing from the deposit of the next bid, we enforce `D_next / R_next >= K` instead of `D_top / R_top + D_next / R_next >= K` to ensure that the deposit of a bid cannot go below `R * K` before the bid becomes active.
196 | - After the top bid's deposit depletes we make sure that the next bid has existed for at least `K` blocks before making it active.
197 |
198 | ## Known issues
199 |
200 | - `blockIdx` is a `uint48`, and when it overflows the `currentBlockIdx >= nextBidStartBlockIdx` checks will have undefined behavior. Even with a block time of 10ms it would take ~89 thousand years for the overflow to occur, so it's unlikely to be a problem in practice.
201 |
202 | ## Installation
203 |
204 | To install with [Foundry](https://github.com/gakonst/foundry):
205 |
206 | ```
207 | forge install bunniapp/biddog
208 | ```
209 |
210 | ## Local development
211 |
212 | This project uses [Foundry](https://github.com/gakonst/foundry) as the development framework.
213 |
214 | ### Dependencies
215 |
216 | ```
217 | forge install
218 | ```
219 |
220 | ### Compilation
221 |
222 | ```
223 | forge build
224 | ```
225 |
226 | ### Testing
227 |
228 | ```
229 | forge test
230 | ```
231 |
--------------------------------------------------------------------------------
/src/AmAmm.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.19;
3 |
4 | import {LibMulticaller} from "multicaller/LibMulticaller.sol";
5 |
6 | import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol";
7 | import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
8 |
9 | import {SafeCastLib} from "solady/utils/SafeCastLib.sol";
10 | import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol";
11 |
12 | import {IAmAmm} from "./interfaces/IAmAmm.sol";
13 | import {BlockNumberLib} from "./libraries/BlockNumberLib.sol";
14 |
15 | /// @title AmAmm
16 | /// @author zefram.eth
17 | /// @notice Implements the auction mechanism from the am-AMM paper (https://arxiv.org/abs/2403.03367)
18 | abstract contract AmAmm is IAmAmm {
19 | /// -----------------------------------------------------------------------
20 | /// Library usage
21 | /// -----------------------------------------------------------------------
22 |
23 | using SafeCastLib for *;
24 | using FixedPointMathLib for *;
25 |
26 | /// -----------------------------------------------------------------------
27 | /// Modifiers
28 | /// -----------------------------------------------------------------------
29 |
30 | modifier onlyAmAmmEnabled(PoolId id) {
31 | _checkAmAmmEnabled(id);
32 | _;
33 | }
34 |
35 | /// -----------------------------------------------------------------------
36 | /// Constants
37 | /// -----------------------------------------------------------------------
38 |
39 | function K(PoolId) internal view virtual returns (uint48) {
40 | return 7200;
41 | }
42 |
43 | function MIN_BID_MULTIPLIER(PoolId) internal view virtual returns (uint256) {
44 | return 1.1e18;
45 | }
46 |
47 | function MIN_RENT(PoolId) internal view virtual returns (uint128) {
48 | return 0;
49 | }
50 |
51 | /// -----------------------------------------------------------------------
52 | /// Immutable args
53 | /// -----------------------------------------------------------------------
54 |
55 | /// @dev The block number at which the contract was deployed
56 | uint256 internal immutable _deploymentBlockNumber;
57 |
58 | /// -----------------------------------------------------------------------
59 | /// Storage variables
60 | /// -----------------------------------------------------------------------
61 |
62 | mapping(PoolId id => Bid) internal _topBids;
63 | mapping(PoolId id => Bid) internal _nextBids;
64 | mapping(PoolId id => uint48) internal _lastUpdatedBlockIdx;
65 | mapping(Currency currency => uint256) internal _totalFees;
66 | mapping(address manager => mapping(PoolId id => uint256)) internal _refunds;
67 | mapping(address manager => mapping(Currency currency => uint256)) internal _fees;
68 |
69 | /// -----------------------------------------------------------------------
70 | /// Constructor
71 | /// -----------------------------------------------------------------------
72 |
73 | constructor() {
74 | _deploymentBlockNumber = BlockNumberLib.getBlockNumber();
75 | }
76 |
77 | /// -----------------------------------------------------------------------
78 | /// Bidder actions
79 | /// -----------------------------------------------------------------------
80 |
81 | /// @inheritdoc IAmAmm
82 | function bid(PoolId id, address manager, bytes6 payload, uint128 rent, uint128 deposit)
83 | public
84 | virtual
85 | override
86 | onlyAmAmmEnabled(id)
87 | {
88 | address msgSender = LibMulticaller.senderOrSigner();
89 |
90 | /// -----------------------------------------------------------------------
91 | /// State updates
92 | /// -----------------------------------------------------------------------
93 |
94 | // update state machine
95 | _updateAmAmmWrite(id);
96 |
97 | // ensure bid is valid
98 | // - manager can't be zero address
99 | // - bid needs to be greater than the next bid by >10%
100 | // - deposit needs to cover the rent for K blocks
101 | // - deposit needs to be a multiple of rent
102 | // - payload needs to be valid
103 | // - rent needs to be at least MIN_RENT
104 | if (
105 | manager == address(0) || rent <= _nextBids[id].rent.mulWad(MIN_BID_MULTIPLIER(id)) || deposit < rent * K(id)
106 | || deposit % rent != 0 || !_payloadIsValid(id, payload) || rent < MIN_RENT(id)
107 | ) {
108 | revert AmAmm__InvalidBid();
109 | }
110 |
111 | // refund deposit of the previous next bid
112 | _refunds[_nextBids[id].manager][id] += _nextBids[id].deposit;
113 |
114 | // update next bid
115 | uint48 blockIdx = uint48(BlockNumberLib.getBlockNumber() - _deploymentBlockNumber);
116 | _nextBids[id] = Bid(manager, blockIdx, payload, rent, deposit);
117 |
118 | /// -----------------------------------------------------------------------
119 | /// External calls
120 | /// -----------------------------------------------------------------------
121 |
122 | // transfer deposit from msg.sender to this contract
123 | _pullBidToken(id, msgSender, deposit);
124 |
125 | emit SubmitBid(id, manager, blockIdx, payload, rent, deposit);
126 | }
127 |
128 | /// @inheritdoc IAmAmm
129 | function depositIntoBid(PoolId id, uint128 amount, bool isTopBid) public virtual override onlyAmAmmEnabled(id) {
130 | address msgSender = LibMulticaller.senderOrSigner();
131 |
132 | /// -----------------------------------------------------------------------
133 | /// State updates
134 | /// -----------------------------------------------------------------------
135 |
136 | // update state machine
137 | _updateAmAmmWrite(id);
138 |
139 | Bid storage bidStorage = isTopBid ? _topBids[id] : _nextBids[id];
140 | Bid memory bidMemory = bidStorage;
141 |
142 | // only the top bid manager can deposit into the top bid
143 | if (msgSender != bidMemory.manager) {
144 | revert AmAmm__Unauthorized();
145 | }
146 |
147 | // ensure amount is a multiple of rent
148 | if (amount % bidMemory.rent != 0) {
149 | revert AmAmm__InvalidDepositAmount();
150 | }
151 |
152 | // add amount to top bid deposit
153 | bidStorage.deposit = bidMemory.deposit + amount;
154 |
155 | /// -----------------------------------------------------------------------
156 | /// External calls
157 | /// -----------------------------------------------------------------------
158 |
159 | // transfer amount from msg.sender to this contract
160 | _pullBidToken(id, msgSender, amount);
161 |
162 | if (isTopBid) {
163 | emit DepositIntoTopBid(id, msgSender, amount);
164 | } else {
165 | emit DepositIntoNextBid(id, msgSender, amount);
166 | }
167 | }
168 |
169 | /// @inheritdoc IAmAmm
170 | function withdrawFromBid(PoolId id, uint128 amount, address recipient, bool isTopBid)
171 | public
172 | virtual
173 | override
174 | onlyAmAmmEnabled(id)
175 | {
176 | address msgSender = LibMulticaller.senderOrSigner();
177 |
178 | /// -----------------------------------------------------------------------
179 | /// State updates
180 | /// -----------------------------------------------------------------------
181 |
182 | // update state machine
183 | _updateAmAmmWrite(id);
184 |
185 | Bid storage bidStorage = isTopBid ? _topBids[id] : _nextBids[id];
186 | Bid memory bidMemory = bidStorage;
187 |
188 | // only the manager of the relevant bid can withdraw from the bid
189 | if (msgSender != bidMemory.manager) {
190 | revert AmAmm__Unauthorized();
191 | }
192 |
193 | // ensure amount is a multiple of rent
194 | if (amount % bidMemory.rent != 0) {
195 | revert AmAmm__InvalidDepositAmount();
196 | }
197 |
198 | // require D / R >= K
199 | if ((bidMemory.deposit - amount) / bidMemory.rent < K(id)) {
200 | revert AmAmm__BidLocked();
201 | }
202 |
203 | // deduct amount from bid deposit
204 | bidStorage.deposit = bidMemory.deposit - amount;
205 |
206 | /// -----------------------------------------------------------------------
207 | /// External calls
208 | /// -----------------------------------------------------------------------
209 |
210 | // transfer amount to recipient
211 | _pushBidToken(id, recipient, amount);
212 |
213 | if (isTopBid) {
214 | emit WithdrawFromTopBid(id, msgSender, recipient, amount);
215 | } else {
216 | emit WithdrawFromNextBid(id, msgSender, recipient, amount);
217 | }
218 | }
219 |
220 | /// @inheritdoc IAmAmm
221 | function claimRefund(PoolId id, address recipient)
222 | public
223 | virtual
224 | override
225 | onlyAmAmmEnabled(id)
226 | returns (uint256 refund)
227 | {
228 | address msgSender = LibMulticaller.senderOrSigner();
229 |
230 | /// -----------------------------------------------------------------------
231 | /// State updates
232 | /// -----------------------------------------------------------------------
233 |
234 | // update state machine
235 | _updateAmAmmWrite(id);
236 |
237 | refund = _refunds[msgSender][id];
238 | if (refund == 0) {
239 | return 0;
240 | }
241 | delete _refunds[msgSender][id];
242 |
243 | /// -----------------------------------------------------------------------
244 | /// External calls
245 | /// -----------------------------------------------------------------------
246 |
247 | // transfer refund to recipient
248 | _pushBidToken(id, recipient, refund);
249 |
250 | emit ClaimRefund(id, msgSender, recipient, refund);
251 | }
252 |
253 | /// @inheritdoc IAmAmm
254 | function claimFees(Currency currency, address recipient) public virtual override returns (uint256 fees) {
255 | address msgSender = LibMulticaller.senderOrSigner();
256 |
257 | /// -----------------------------------------------------------------------
258 | /// State updates
259 | /// -----------------------------------------------------------------------
260 |
261 | // update manager fees
262 | fees = _fees[msgSender][currency];
263 | if (fees == 0) {
264 | return 0;
265 | }
266 | delete _fees[msgSender][currency];
267 |
268 | // update total fees
269 | unchecked {
270 | // safe because _totalFees[currency] is the sum of all managers' fees
271 | _totalFees[currency] -= fees;
272 | }
273 |
274 | /// -----------------------------------------------------------------------
275 | /// External calls
276 | /// -----------------------------------------------------------------------
277 |
278 | // transfer fees to recipient
279 | _transferFeeToken(currency, recipient, fees);
280 |
281 | emit ClaimFees(currency, msgSender, recipient, fees);
282 | }
283 |
284 | /// @inheritdoc IAmAmm
285 | function increaseBidRent(
286 | PoolId id,
287 | uint128 additionalRent,
288 | uint128 updatedDeposit,
289 | bool isTopBid,
290 | address withdrawRecipient
291 | ) public virtual override onlyAmAmmEnabled(id) returns (uint128 amountDeposited, uint128 amountWithdrawn) {
292 | /// -----------------------------------------------------------------------
293 | /// Validation
294 | /// -----------------------------------------------------------------------
295 |
296 | address msgSender = LibMulticaller.senderOrSigner();
297 |
298 | // noop if additionalRent is 0
299 | if (additionalRent == 0) return (0, 0);
300 |
301 | /// -----------------------------------------------------------------------
302 | /// State updates
303 | /// -----------------------------------------------------------------------
304 |
305 | // update state machine
306 | _updateAmAmmWrite(id);
307 |
308 | Bid storage relevantBidStorage = isTopBid ? _topBids[id] : _nextBids[id];
309 | Bid memory relevantBid = relevantBidStorage;
310 |
311 | // must be the manager of the relevant bid
312 | if (msgSender != relevantBid.manager) {
313 | revert AmAmm__Unauthorized();
314 | }
315 |
316 | uint128 newRent = relevantBid.rent + additionalRent;
317 |
318 | // ensure that:
319 | // - updatedDeposit is a multiple of newRent
320 | // - newRent is >= MIN_RENT(id)
321 | if (updatedDeposit % newRent != 0 || newRent < MIN_RENT(id)) {
322 | revert AmAmm__InvalidBid();
323 | }
324 |
325 | // require D / R >= K
326 | if (updatedDeposit / newRent < K(id)) {
327 | revert AmAmm__BidLocked();
328 | }
329 |
330 | // update relevant bid
331 | relevantBidStorage.rent = newRent;
332 | relevantBidStorage.deposit = updatedDeposit;
333 |
334 | /// -----------------------------------------------------------------------
335 | /// External calls
336 | /// -----------------------------------------------------------------------
337 |
338 | unchecked {
339 | // explicitly comparing updatedDeposit and relevantBid.deposit so subtractions are always safe
340 | if (updatedDeposit > relevantBid.deposit) {
341 | // transfer amount from msg.sender to this contract
342 | amountDeposited = updatedDeposit - relevantBid.deposit;
343 | _pullBidToken(id, msgSender, amountDeposited);
344 | } else if (updatedDeposit < relevantBid.deposit) {
345 | // transfer amount from this contract to withdrawRecipient
346 | amountWithdrawn = relevantBid.deposit - updatedDeposit;
347 | _pushBidToken(id, withdrawRecipient, amountWithdrawn);
348 | }
349 | }
350 |
351 | emit IncreaseBidRent(
352 | id, msgSender, additionalRent, updatedDeposit, isTopBid, withdrawRecipient, amountDeposited, amountWithdrawn
353 | );
354 | }
355 |
356 | /// @inheritdoc IAmAmm
357 | function setBidPayload(PoolId id, bytes6 payload, bool isTopBid) public virtual override onlyAmAmmEnabled(id) {
358 | address msgSender = LibMulticaller.senderOrSigner();
359 |
360 | // update state machine
361 | _updateAmAmmWrite(id);
362 |
363 | Bid storage relevantBid = isTopBid ? _topBids[id] : _nextBids[id];
364 |
365 | if (msgSender != relevantBid.manager) {
366 | revert AmAmm__Unauthorized();
367 | }
368 |
369 | if (!_payloadIsValid(id, payload)) {
370 | revert AmAmm__InvalidBid();
371 | }
372 |
373 | relevantBid.payload = payload;
374 |
375 | emit SetBidPayload(id, msgSender, payload, isTopBid);
376 | }
377 |
378 | /// @inheritdoc IAmAmm
379 | function updateStateMachine(PoolId id) external override {
380 | _updateAmAmmWrite(id);
381 | }
382 |
383 | /// -----------------------------------------------------------------------
384 | /// Getters
385 | /// -----------------------------------------------------------------------
386 |
387 | /// @inheritdoc IAmAmm
388 | function getBid(PoolId id, bool isTopBid) external view override returns (Bid memory) {
389 | (Bid memory topBid, Bid memory nextBid) = _updateAmAmmView(id);
390 | return isTopBid ? topBid : nextBid;
391 | }
392 |
393 | /// @inheritdoc IAmAmm
394 | function getBidWrite(PoolId id, bool isTopBid) external override returns (Bid memory) {
395 | _updateAmAmmWrite(id);
396 | return isTopBid ? _topBids[id] : _nextBids[id];
397 | }
398 |
399 | /// @inheritdoc IAmAmm
400 | function getRefund(address manager, PoolId id) external view override returns (uint256) {
401 | return _refunds[manager][id];
402 | }
403 |
404 | /// @inheritdoc IAmAmm
405 | function getRefundWrite(address manager, PoolId id) external override returns (uint256) {
406 | _updateAmAmmWrite(id);
407 | return _refunds[manager][id];
408 | }
409 |
410 | /// @inheritdoc IAmAmm
411 | function getFees(address manager, Currency currency) external view override returns (uint256) {
412 | return _fees[manager][currency];
413 | }
414 |
415 | /// -----------------------------------------------------------------------
416 | /// Virtual functions
417 | /// -----------------------------------------------------------------------
418 |
419 | /// @dev Returns whether the am-AMM is enabled for a given pool
420 | function _amAmmEnabled(PoolId id) internal view virtual returns (bool);
421 |
422 | /// @dev Validates a bid payload, e.g. ensure the swap fee is below a certain threshold
423 | function _payloadIsValid(PoolId id, bytes6 payload) internal view virtual returns (bool);
424 |
425 | /// @dev Burns bid tokens from address(this)
426 | function _burnBidToken(PoolId id, uint256 amount) internal virtual;
427 |
428 | /// @dev Transfers bid tokens from an address that's not address(this) to address(this)
429 | function _pullBidToken(PoolId id, address from, uint256 amount) internal virtual;
430 |
431 | /// @dev Transfers bid tokens from address(this) to an address that's not address(this)
432 | function _pushBidToken(PoolId id, address to, uint256 amount) internal virtual;
433 |
434 | /// @dev Transfers accrued fees from address(this)
435 | function _transferFeeToken(Currency currency, address to, uint256 amount) internal virtual;
436 |
437 | /// @dev Accrues swap fees to the manager
438 | function _accrueFees(address manager, Currency currency, uint256 amount) internal virtual {
439 | _fees[manager][currency] += amount;
440 | _totalFees[currency] += amount;
441 | }
442 |
443 | /// -----------------------------------------------------------------------
444 | /// Internal helpers
445 | /// -----------------------------------------------------------------------
446 |
447 | function _checkAmAmmEnabled(PoolId id) internal view {
448 | if (!_amAmmEnabled(id)) {
449 | revert AmAmm__NotEnabled();
450 | }
451 | }
452 |
453 | /// @dev Charges rent and updates the top and next bids for a given pool
454 | function _updateAmAmmWrite(PoolId id) internal virtual {
455 | uint48 currentBlockIdx = uint48(BlockNumberLib.getBlockNumber() - _deploymentBlockNumber);
456 |
457 | // early return if the pool has already been updated in this block
458 | // condition is also true if no update has occurred for type(uint48).max blocks
459 | // which is extremely unlikely
460 | if (_lastUpdatedBlockIdx[id] == currentBlockIdx) {
461 | return;
462 | }
463 |
464 | Bid memory topBid = _topBids[id];
465 | Bid memory nextBid = _nextBids[id];
466 | bool updatedTopBid;
467 | bool updatedNextBid;
468 | uint256 rentCharged;
469 |
470 | // run state machine
471 | {
472 | bool stepHasUpdatedTopBid;
473 | bool stepHasUpdatedNextBid;
474 | uint256 stepRentCharged;
475 | address stepRefundManager;
476 | uint256 stepRefundAmount;
477 | while (true) {
478 | (
479 | topBid,
480 | nextBid,
481 | stepHasUpdatedTopBid,
482 | stepHasUpdatedNextBid,
483 | stepRentCharged,
484 | stepRefundManager,
485 | stepRefundAmount
486 | ) = _stateTransition(currentBlockIdx, id, topBid, nextBid);
487 |
488 | if (!stepHasUpdatedTopBid && !stepHasUpdatedNextBid) {
489 | break;
490 | }
491 |
492 | updatedTopBid = updatedTopBid || stepHasUpdatedTopBid;
493 | updatedNextBid = updatedNextBid || stepHasUpdatedNextBid;
494 | rentCharged += stepRentCharged;
495 | if (stepRefundManager != address(0)) {
496 | _refunds[stepRefundManager][id] += stepRefundAmount;
497 | }
498 | }
499 | }
500 |
501 | // update top and next bids
502 | if (updatedTopBid) {
503 | _topBids[id] = topBid;
504 | }
505 | if (updatedNextBid) {
506 | _nextBids[id] = nextBid;
507 | }
508 |
509 | // update last updated blockIdx
510 | _lastUpdatedBlockIdx[id] = currentBlockIdx;
511 |
512 | // burn rent charged
513 | if (rentCharged != 0) {
514 | _burnBidToken(id, rentCharged);
515 | }
516 | }
517 |
518 | /// @dev View version of _updateAmAmmWrite()
519 | function _updateAmAmmView(PoolId id) internal view virtual returns (Bid memory topBid, Bid memory nextBid) {
520 | uint48 currentBlockIdx = uint48(BlockNumberLib.getBlockNumber() - _deploymentBlockNumber);
521 |
522 | topBid = _topBids[id];
523 | nextBid = _nextBids[id];
524 |
525 | // early return if the pool has already been updated in this block
526 | // condition is also true if no update has occurred for type(uint48).max blocks
527 | // which is extremely unlikely
528 | if (_lastUpdatedBlockIdx[id] == currentBlockIdx) {
529 | return (topBid, nextBid);
530 | }
531 |
532 | // run state machine
533 | {
534 | bool stepHasUpdatedTopBid;
535 | bool stepHasUpdatedNextBid;
536 | while (true) {
537 | (topBid, nextBid, stepHasUpdatedTopBid, stepHasUpdatedNextBid,,,) =
538 | _stateTransition(currentBlockIdx, id, topBid, nextBid);
539 |
540 | if (!stepHasUpdatedTopBid && !stepHasUpdatedNextBid) {
541 | break;
542 | }
543 | }
544 | }
545 | }
546 |
547 | /// @dev Returns the updated top and next bids after a single state transition
548 | /// State diagram is as follows:
549 | /// after
550 | /// ┌───────────────────────deposit ───────────────────┐
551 | /// │ depletes │
552 | /// ▼ │
553 | /// ┌────────────────────────┐ ┌────────────────────────┐
554 | /// │ │ │ │
555 | /// │ State A │ │ State B │
556 | /// │ Manager: nil │ ┌───────────▶│ Manager: r0 │◀─┐
557 | /// │ Next: nil │ │ │ Next: nil │ │
558 | /// │ │ │ │ │ │
559 | /// └────────────────────────┘ │ └────────────────────────┘ │
560 | /// │ │ │ │
561 | /// │ │ │ │
562 | /// │ │ │ │
563 | /// │ │ │ │
564 | /// bid(r) after K bid(r) after K
565 | /// │ blocks │ blocks
566 | /// │ │ │ │
567 | /// │ │ │ │
568 | /// │ │ after │ │
569 | /// ├────────────────────────┼──deposit ───────────────┼──────────────┤
570 | /// │ │ depletes │ │
571 | /// ▼ │ ▼ │
572 | /// ┌────────────────────────┐ │ ┌────────────────────────┐ │
573 | /// │ │ │ │ │ │
574 | /// │ State C │ │ │ State D │ │
575 | /// ┌─▶│ Manager: nil │────────────┘ ┌─▶│ Manager: r0 │──┘
576 | /// │ │ Next: r │ │ │ Next: r │
577 | /// │ │ │ │ │ │
578 | /// │ └────────────────────────┘ │ └────────────────────────┘
579 | /// │ │ │ │
580 | /// │ │ │ │
581 | /// └─────bid(r)────┘ └─────bid(r)────┘
582 | function _stateTransition(uint48 currentBlockIdx, PoolId id, Bid memory topBid, Bid memory nextBid)
583 | internal
584 | view
585 | virtual
586 | returns (
587 | Bid memory,
588 | Bid memory,
589 | bool updatedTopBid,
590 | bool updatedNextBid,
591 | uint256 rentCharged,
592 | address refundManager,
593 | uint256 refundAmount
594 | )
595 | {
596 | uint48 k = K(id);
597 | if (nextBid.manager == address(0)) {
598 | if (topBid.manager != address(0)) {
599 | // State B
600 | // charge rent from top bid
601 | uint48 blocksPassed;
602 | unchecked {
603 | // unchecked so that if blockIdx ever overflows, we simply wrap around
604 | // could be 0 if no update has occurred for type(uint48).max blocks
605 | // which is extremely unlikely
606 | blocksPassed = currentBlockIdx - topBid.blockIdx;
607 | }
608 | uint256 rentOwed = blocksPassed * topBid.rent;
609 | if (rentOwed >= topBid.deposit) {
610 | // State B -> State A
611 | // the top bid's deposit has been depleted
612 | rentCharged = topBid.deposit;
613 |
614 | topBid = Bid(address(0), 0, 0, 0, 0);
615 |
616 | updatedTopBid = true;
617 | } else if (rentOwed != 0) {
618 | // State B
619 | // charge rent from top bid
620 | rentCharged = rentOwed;
621 |
622 | topBid.deposit -= rentOwed.toUint128();
623 | topBid.blockIdx = currentBlockIdx;
624 |
625 | updatedTopBid = true;
626 | }
627 | }
628 | } else {
629 | if (topBid.manager == address(0)) {
630 | // State C
631 | // check if K blocks have passed since the next bid was submitted
632 | // if so, promote next bid to top bid
633 | uint48 nextBidStartBlockIdx;
634 | unchecked {
635 | // unchecked so that if blockIdx ever overflows, we simply wrap around
636 | nextBidStartBlockIdx = nextBid.blockIdx + k;
637 | }
638 | if (currentBlockIdx >= nextBidStartBlockIdx) {
639 | // State C -> State B
640 | // promote next bid to top bid
641 | topBid = nextBid;
642 | topBid.blockIdx = nextBidStartBlockIdx;
643 | nextBid = Bid(address(0), 0, 0, 0, 0);
644 |
645 | updatedTopBid = true;
646 | updatedNextBid = true;
647 | }
648 | } else {
649 | // State D
650 | // we charge rent from the top bid only until K blocks after the next bid was submitted
651 | // assuming the next bid's rent is greater than the top bid's rent + 10%, otherwise we don't care about
652 | // the next bid
653 | bool nextBidIsBetter = nextBid.rent > topBid.rent.mulWad(MIN_BID_MULTIPLIER(id));
654 | uint48 blocksPassed;
655 | unchecked {
656 | // unchecked so that if blockIdx ever overflows, we simply wrap around
657 | blocksPassed = nextBidIsBetter
658 | ? uint48(
659 | FixedPointMathLib.min(currentBlockIdx - topBid.blockIdx, nextBid.blockIdx + k - topBid.blockIdx)
660 | )
661 | : currentBlockIdx - topBid.blockIdx;
662 | }
663 | uint256 rentOwed = blocksPassed * topBid.rent;
664 | if (rentOwed >= topBid.deposit) {
665 | // State D -> State C
666 | // top bid has insufficient deposit
667 | // clear the top bid and make sure the next bid starts >= the latest processed blockIdx
668 | rentCharged = topBid.deposit;
669 |
670 | topBid = Bid(address(0), 0, 0, 0, 0);
671 | unchecked {
672 | // unchecked so that if blockIdx ever underflows, we simply wrap around
673 | uint48 latestProcessedBlockIdx = nextBidIsBetter
674 | ? uint48(FixedPointMathLib.min(currentBlockIdx, nextBid.blockIdx + k))
675 | : currentBlockIdx;
676 | nextBid.blockIdx = uint48(FixedPointMathLib.max(nextBid.blockIdx, latestProcessedBlockIdx - k));
677 | }
678 |
679 | updatedTopBid = true;
680 | updatedNextBid = true;
681 | } else {
682 | // State D
683 | // top bid has sufficient deposit
684 | // charge rent from top bid
685 | if (rentOwed != 0) {
686 | rentCharged = rentOwed;
687 |
688 | topBid.deposit -= rentOwed.toUint128();
689 | topBid.blockIdx = currentBlockIdx;
690 |
691 | updatedTopBid = true;
692 | }
693 |
694 | // check if K blocks have passed since the next bid was submitted
695 | // and that the next bid's rent is greater than the top bid's rent + 10%
696 | // if so, promote next bid to top bid
697 | uint48 nextBidStartBlockIdx;
698 | unchecked {
699 | // unchecked so that if blockIdx ever overflows, we simply wrap around
700 | nextBidStartBlockIdx = nextBid.blockIdx + k;
701 | }
702 | if (currentBlockIdx >= nextBidStartBlockIdx && nextBidIsBetter) {
703 | // State D -> State B
704 | // refund remaining deposit to top bid manager
705 | (refundManager, refundAmount) = (topBid.manager, topBid.deposit);
706 |
707 | // promote next bid to top bid
708 | topBid = nextBid;
709 | topBid.blockIdx = nextBidStartBlockIdx;
710 | nextBid = Bid(address(0), 0, 0, 0, 0);
711 |
712 | updatedTopBid = true;
713 | updatedNextBid = true;
714 | }
715 | }
716 | }
717 | }
718 |
719 | return (topBid, nextBid, updatedTopBid, updatedNextBid, rentCharged, refundManager, refundAmount);
720 | }
721 | }
722 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 |
633 | Copyright (C)
634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published
637 | by the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------
/test/AmAmm.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0
2 | pragma solidity ^0.8.19;
3 |
4 | import "forge-std/Test.sol";
5 |
6 | import "./mocks/AmAmmMock.sol";
7 | import "./mocks/ERC20Mock.sol";
8 | import "../src/interfaces/IAmAmm.sol";
9 |
10 | contract AmAmmTest is Test {
11 | PoolId constant POOL_0 = PoolId.wrap(bytes32(0));
12 |
13 | uint128 internal constant K = 7200; // 7200 blocks
14 | uint256 internal constant MIN_BID_MULTIPLIER = 1.1e18; // 10%
15 |
16 | AmAmmMock amAmm;
17 | uint256 internal deployBlockNumber;
18 |
19 | function setUp() external {
20 | deployBlockNumber = vm.getBlockNumber();
21 | amAmm = new AmAmmMock(new ERC20Mock(), new ERC20Mock(), new ERC20Mock());
22 | amAmm.bidToken().approve(address(amAmm), type(uint256).max);
23 | amAmm.setEnabled(POOL_0, true);
24 | amAmm.setMaxSwapFee(POOL_0, 0.1e6);
25 | }
26 |
27 | function _swapFeeToPayload(uint24 swapFee) internal pure returns (bytes6) {
28 | return bytes6(bytes3(swapFee));
29 | }
30 |
31 | function test_stateTransition_AC() external {
32 | // mint bid tokens
33 | amAmm.bidToken().mint(address(this), K * 1e18);
34 |
35 | // make bid
36 | amAmm.bid({
37 | id: POOL_0,
38 | manager: address(this),
39 | payload: _swapFeeToPayload(0.01e6),
40 | rent: 1e18,
41 | deposit: K * 1e18
42 | });
43 |
44 | // verify state
45 | IAmAmm.Bid memory bid = amAmm.getBid(POOL_0, false);
46 | assertEq(amAmm.bidToken().balanceOf(address(this)), 0, "didn't take bid tokens");
47 | assertEq(amAmm.bidToken().balanceOf(address(amAmm)), K * 1e18, "didn't give bid tokens");
48 | assertEq(bid.manager, address(this), "manager incorrect");
49 | assertEq(bid.payload, _swapFeeToPayload(0.01e6), "swapFee incorrect");
50 | assertEq(bid.rent, 1e18, "rent incorrect");
51 | assertEq(bid.deposit, K * 1e18, "deposit incorrect");
52 | assertEq(bid.blockIdx, _getBlockIdx(), "blockIdx incorrect");
53 | }
54 |
55 | function test_stateTransition_CC() external {
56 | // mint bid tokens
57 | amAmm.bidToken().mint(address(this), K * 1e18 + K * 1.2e18);
58 |
59 | // make first bid
60 | amAmm.bid({
61 | id: POOL_0,
62 | manager: address(this),
63 | payload: _swapFeeToPayload(0.01e6),
64 | rent: 1e18,
65 | deposit: K * 1e18
66 | });
67 |
68 | // make second bid
69 | amAmm.bid({
70 | id: POOL_0,
71 | manager: address(this),
72 | payload: _swapFeeToPayload(0.01e6),
73 | rent: 1.2e18,
74 | deposit: K * 1.2e18
75 | });
76 |
77 | // verify state
78 | IAmAmm.Bid memory bid = amAmm.getBid(POOL_0, false);
79 | assertEq(amAmm.bidToken().balanceOf(address(this)), 0, "didn't take bid tokens");
80 | assertEq(amAmm.bidToken().balanceOf(address(amAmm)), K * 1e18 + K * 1.2e18, "didn't give bid tokens");
81 | assertEq(amAmm.getRefund(address(this), POOL_0), K * 1e18, "didn't refund first bid");
82 | assertEq(bid.manager, address(this), "manager incorrect");
83 | assertEq(bid.payload, _swapFeeToPayload(0.01e6), "swapFee incorrect");
84 | assertEq(bid.rent, 1.2e18, "rent incorrect");
85 | assertEq(bid.deposit, K * 1.2e18, "deposit incorrect");
86 | assertEq(bid.blockIdx, _getBlockIdx(), "blockIdx incorrect");
87 | }
88 |
89 | function test_stateTransition_CB() external {
90 | // mint bid tokens
91 | amAmm.bidToken().mint(address(this), K * 1e18);
92 |
93 | // make bid
94 | amAmm.bid({
95 | id: POOL_0,
96 | manager: address(this),
97 | payload: _swapFeeToPayload(0.01e6),
98 | rent: 1e18,
99 | deposit: K * 1e18
100 | });
101 |
102 | // wait K blocks
103 | skipBlocks(K);
104 |
105 | // verify state
106 | IAmAmm.Bid memory bid = amAmm.getBidWrite(POOL_0, true);
107 | assertEq(bid.manager, address(this), "manager incorrect");
108 | assertEq(bid.payload, _swapFeeToPayload(0.01e6), "swapFee incorrect");
109 | assertEq(bid.rent, 1e18, "rent incorrect");
110 | assertEq(bid.deposit, K * 1e18, "deposit incorrect");
111 | assertEq(bid.blockIdx, _getBlockIdx(), "blockIdx incorrect");
112 | }
113 |
114 | function test_stateTransition_BB() external {
115 | // mint bid tokens
116 | amAmm.bidToken().mint(address(this), K * 1e18);
117 |
118 | // make bid
119 | amAmm.bid({
120 | id: POOL_0,
121 | manager: address(this),
122 | payload: _swapFeeToPayload(0.01e6),
123 | rent: 1e18,
124 | deposit: K * 1e18
125 | });
126 |
127 | // wait K + 3 blocks
128 | skipBlocks(K + 3);
129 |
130 | // verify state
131 | IAmAmm.Bid memory bid = amAmm.getBidWrite(POOL_0, true);
132 | assertEq(bid.manager, address(this), "manager incorrect");
133 | assertEq(bid.payload, _swapFeeToPayload(0.01e6), "swapFee incorrect");
134 | assertEq(bid.rent, 1e18, "rent incorrect");
135 | assertEq(bid.deposit, (K - 3) * 1e18, "deposit incorrect");
136 | assertEq(bid.blockIdx, _getBlockIdx(), "blockIdx incorrect");
137 | assertEq(amAmm.bidToken().balanceOf(address(amAmm)), bid.deposit, "didn't burn rent");
138 | }
139 |
140 | function test_stateTransition_BA() external {
141 | // mint bid tokens
142 | amAmm.bidToken().mint(address(this), K * 1e18);
143 |
144 | // make bid
145 | amAmm.bid({
146 | id: POOL_0,
147 | manager: address(this),
148 | payload: _swapFeeToPayload(0.01e6),
149 | rent: 1e18,
150 | deposit: K * 1e18
151 | });
152 |
153 | // wait 2K blocks
154 | skipBlocks(2 * K);
155 |
156 | // verify state
157 | IAmAmm.Bid memory bid = amAmm.getBidWrite(POOL_0, true);
158 | assertEq(bid.manager, address(0), "manager incorrect");
159 | assertEq(bid.payload, _swapFeeToPayload(0), "swapFee incorrect");
160 | assertEq(bid.rent, 0, "rent incorrect");
161 | assertEq(bid.deposit, 0, "deposit incorrect");
162 | assertEq(bid.blockIdx, 0, "blockIdx incorrect");
163 | assertEq(amAmm.bidToken().balanceOf(address(amAmm)), bid.deposit, "didn't burn rent");
164 | }
165 |
166 | function test_stateTransition_BD() external {
167 | // mint bid tokens
168 | amAmm.bidToken().mint(address(this), K * 1e18);
169 |
170 | // make bid
171 | amAmm.bid({
172 | id: POOL_0,
173 | manager: address(this),
174 | payload: _swapFeeToPayload(0.01e6),
175 | rent: 1e18,
176 | deposit: K * 1e18
177 | });
178 |
179 | // wait K blocks
180 | skipBlocks(K);
181 |
182 | // mint bid tokens
183 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
184 |
185 | // make bid
186 | amAmm.bid({
187 | id: POOL_0,
188 | manager: address(this),
189 | payload: _swapFeeToPayload(0.01e6),
190 | rent: 2e18,
191 | deposit: 2 * K * 1e18
192 | });
193 |
194 | // verify top bid state
195 | IAmAmm.Bid memory bid = amAmm.getBid(POOL_0, true);
196 | assertEq(bid.manager, address(this), "manager incorrect");
197 | assertEq(bid.payload, _swapFeeToPayload(0.01e6), "swapFee incorrect");
198 | assertEq(bid.rent, 1e18, "rent incorrect");
199 | assertEq(bid.deposit, K * 1e18, "deposit incorrect");
200 | assertEq(bid.blockIdx, _getBlockIdx(), "blockIdx incorrect");
201 |
202 | // verify next bid state
203 | bid = amAmm.getBid(POOL_0, false);
204 | assertEq(bid.manager, address(this), "manager incorrect");
205 | assertEq(bid.payload, _swapFeeToPayload(0.01e6), "swapFee incorrect");
206 | assertEq(bid.rent, 2e18, "rent incorrect");
207 | assertEq(bid.deposit, 2 * K * 1e18, "deposit incorrect");
208 | assertEq(bid.blockIdx, _getBlockIdx(), "blockIdx incorrect");
209 | }
210 |
211 | function test_stateTransition_DD() external {
212 | // mint bid tokens
213 | amAmm.bidToken().mint(address(this), K * 1e18);
214 |
215 | // make bid
216 | amAmm.bid({
217 | id: POOL_0,
218 | manager: address(this),
219 | payload: _swapFeeToPayload(0.01e6),
220 | rent: 1e18,
221 | deposit: K * 1e18
222 | });
223 |
224 | // wait K blocks
225 | skipBlocks(K);
226 |
227 | // mint bid tokens
228 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
229 |
230 | // make bid
231 | amAmm.bid({
232 | id: POOL_0,
233 | manager: address(this),
234 | payload: _swapFeeToPayload(0.01e6),
235 | rent: 2e18,
236 | deposit: 2 * K * 1e18
237 | });
238 |
239 | // mint bid tokens
240 | amAmm.bidToken().mint(address(this), 3 * K * 1e18);
241 |
242 | // make higher bid
243 | amAmm.bid({
244 | id: POOL_0,
245 | manager: address(this),
246 | payload: _swapFeeToPayload(0.01e6),
247 | rent: 3e18,
248 | deposit: 3 * K * 1e18
249 | });
250 |
251 | // wait 3 blocks
252 | skipBlocks(3);
253 |
254 | // verify top bid state
255 | IAmAmm.Bid memory bid = amAmm.getBidWrite(POOL_0, true);
256 | assertEq(bid.manager, address(this), "top bid manager incorrect");
257 | assertEq(bid.payload, _swapFeeToPayload(0.01e6), "top bid swapFee incorrect");
258 | assertEq(bid.rent, 1e18, "top bid rent incorrect");
259 | assertEq(bid.deposit, (K - 3) * 1e18, "top bid deposit incorrect");
260 | assertEq(bid.blockIdx, _getBlockIdx(), "top bid blockIdx incorrect");
261 |
262 | // verify next bid state
263 | bid = amAmm.getBid(POOL_0, false);
264 | assertEq(bid.manager, address(this), "next bid manager incorrect");
265 | assertEq(bid.payload, _swapFeeToPayload(0.01e6), "next bid swapFee incorrect");
266 | assertEq(bid.rent, 3e18, "next bid rent incorrect");
267 | assertEq(bid.deposit, 3 * K * 1e18, "next bid deposit incorrect");
268 | assertEq(bid.blockIdx, _getBlockIdx() - 3, "next bid blockIdx incorrect");
269 |
270 | // verify bid token balance
271 | assertEq(amAmm.bidToken().balanceOf(address(amAmm)), (6 * K - 3) * 1e18, "bid token balance incorrect");
272 | }
273 |
274 | function test_stateTransition_DD_lowNextBidRent() external {
275 | // mint bid tokens
276 | amAmm.bidToken().mint(address(this), 10 * K * 1e18);
277 |
278 | // make bid
279 | amAmm.bid({
280 | id: POOL_0,
281 | manager: address(this),
282 | payload: _swapFeeToPayload(0.01e6),
283 | rent: 1e18,
284 | deposit: 10 * K * 1e18
285 | });
286 |
287 | // wait K blocks
288 | skipBlocks(K);
289 |
290 | // mint bid tokens
291 | amAmm.bidToken().mint(address(this), K * 1e18);
292 |
293 | // make lower bid
294 | uint48 nextBidBlockIdx = _getBlockIdx();
295 | amAmm.bid({
296 | id: POOL_0,
297 | manager: address(this),
298 | payload: _swapFeeToPayload(0.02e6),
299 | rent: 0.5e18,
300 | deposit: K * 1e18
301 | });
302 |
303 | // wait 2K blocks
304 | // because the bid is lower than the top bid (plus minimum increment), it should be ignored
305 | skipBlocks(2 * K);
306 |
307 | // verify top bid state
308 | IAmAmm.Bid memory bid = amAmm.getBidWrite(POOL_0, true);
309 | assertEq(bid.manager, address(this), "top bid manager incorrect");
310 | assertEq(bid.payload, _swapFeeToPayload(0.01e6), "top bid swapFee incorrect");
311 | assertEq(bid.rent, 1e18, "top bid rent incorrect");
312 | assertEq(bid.deposit, 8 * K * 1e18, "top bid deposit incorrect");
313 | assertEq(bid.blockIdx, _getBlockIdx(), "top bid blockIdx incorrect");
314 |
315 | // verify next bid state
316 | bid = amAmm.getBid(POOL_0, false);
317 | assertEq(bid.manager, address(this), "next bid manager incorrect");
318 | assertEq(bid.payload, _swapFeeToPayload(0.02e6), "next bid swapFee incorrect");
319 | assertEq(bid.rent, 0.5e18, "next bid rent incorrect");
320 | assertEq(bid.deposit, K * 1e18, "next bid deposit incorrect");
321 | assertEq(bid.blockIdx, nextBidBlockIdx, "next bid blockIdx incorrect");
322 |
323 | // verify bid token balance
324 | assertEq(amAmm.bidToken().balanceOf(address(amAmm)), 9 * K * 1e18, "bid token balance incorrect");
325 | }
326 |
327 | function test_stateTransition_DB_afterKBlocks() external {
328 | // mint bid tokens
329 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
330 |
331 | // make bid
332 | amAmm.bid({
333 | id: POOL_0,
334 | manager: address(this),
335 | payload: _swapFeeToPayload(0.01e6),
336 | rent: 1e18,
337 | deposit: 2 * K * 1e18
338 | });
339 |
340 | // wait K blocks
341 | skipBlocks(K);
342 |
343 | // mint bid tokens
344 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
345 |
346 | // make bid
347 | amAmm.bid({
348 | id: POOL_0,
349 | manager: address(this),
350 | payload: _swapFeeToPayload(0.05e6),
351 | rent: 2e18,
352 | deposit: 2 * K * 1e18
353 | });
354 |
355 | // wait K blocks
356 | skipBlocks(K);
357 |
358 | // verify top bid state
359 | IAmAmm.Bid memory bid = amAmm.getBidWrite(POOL_0, true);
360 | assertEq(bid.manager, address(this), "top bid manager incorrect");
361 | assertEq(bid.payload, _swapFeeToPayload(0.05e6), "top bid swapFee incorrect");
362 | assertEq(bid.rent, 2e18, "top bid rent incorrect");
363 | assertEq(bid.deposit, 2 * K * 1e18, "top bid deposit incorrect");
364 | assertEq(bid.blockIdx, _getBlockIdx(), "top bid blockIdx incorrect");
365 |
366 | // verify next bid state
367 | bid = amAmm.getBid(POOL_0, false);
368 | assertEq(bid.manager, address(0), "next bid manager incorrect");
369 | assertEq(bid.payload, _swapFeeToPayload(0), "next bid swapFee incorrect");
370 | assertEq(bid.rent, 0, "next bid rent incorrect");
371 | assertEq(bid.deposit, 0, "next bid deposit incorrect");
372 | assertEq(bid.blockIdx, 0, "next bid blockIdx incorrect");
373 |
374 | // verify bid token balance
375 | assertEq(amAmm.bidToken().balanceOf(address(amAmm)), 3 * K * 1e18, "bid token balance incorrect");
376 |
377 | // verify refund
378 | assertEq(amAmm.getRefund(address(this), POOL_0), K * 1e18, "refund incorrect");
379 | }
380 |
381 | function test_stateTransition_DC() external {
382 | // mint bid tokens
383 | amAmm.bidToken().mint(address(this), K * 1e18);
384 |
385 | // make bid
386 | amAmm.bid({
387 | id: POOL_0,
388 | manager: address(this),
389 | payload: _swapFeeToPayload(0.01e6),
390 | rent: 1e18,
391 | deposit: K * 1e18
392 | });
393 |
394 | // wait 2 * K - 3 blocks
395 | skipBlocks((2 * K - 3));
396 |
397 | // mint bid tokens
398 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
399 |
400 | // make next bid
401 | uint48 nextBidBlockIdx = _getBlockIdx();
402 | amAmm.bid({
403 | id: POOL_0,
404 | manager: address(this),
405 | payload: _swapFeeToPayload(0.05e6),
406 | rent: 2e18,
407 | deposit: 2 * K * 1e18
408 | });
409 |
410 | // wait 3 blocks
411 | skipBlocks(3);
412 |
413 | // verify top bid state
414 | IAmAmm.Bid memory bid = amAmm.getBidWrite(POOL_0, true);
415 | assertEq(bid.manager, address(0), "top bid manager incorrect");
416 | assertEq(bid.payload, 0, "top bid swapFee incorrect");
417 | assertEq(bid.rent, 0, "top bid rent incorrect");
418 | assertEq(bid.deposit, 0, "top bid deposit incorrect");
419 | assertEq(bid.blockIdx, 0, "top bid blockIdx incorrect");
420 |
421 | // verify next bid state
422 | bid = amAmm.getBid(POOL_0, false);
423 | assertEq(bid.manager, address(this), "next bid manager incorrect");
424 | assertEq(bid.payload, _swapFeeToPayload(0.05e6), "next bid swapFee incorrect");
425 | assertEq(bid.rent, 2e18, "next bid rent incorrect");
426 | assertEq(bid.deposit, 2 * K * 1e18, "next bid deposit incorrect");
427 | assertEq(bid.blockIdx, nextBidBlockIdx, "next bid blockIdx incorrect");
428 |
429 | // verify bid token balance
430 | assertEq(amAmm.bidToken().balanceOf(address(amAmm)), 2 * K * 1e18, "bid token balance incorrect");
431 | }
432 |
433 | function test_stateTransition_DC_lowNextBidRent() external {
434 | // mint bid tokens
435 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
436 |
437 | // make bid
438 | amAmm.bid({
439 | id: POOL_0,
440 | manager: address(this),
441 | payload: _swapFeeToPayload(0.01e6),
442 | rent: 1e18,
443 | deposit: 2 * K * 1e18
444 | });
445 |
446 | // wait K blocks
447 | // top bid will last 2K blocks from now
448 | skipBlocks(K);
449 |
450 | // mint bid tokens
451 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
452 |
453 | // make lower bid
454 | uint48 nextBidBlockIdx = _getBlockIdx();
455 | amAmm.bid({
456 | id: POOL_0,
457 | manager: address(this),
458 | payload: _swapFeeToPayload(0.05e6),
459 | rent: 0.5e18,
460 | deposit: 2 * K * 1e18
461 | });
462 |
463 | // wait K blocks
464 | // top bid should last another K blocks and next bid doesn't activate
465 | // since the rent is lower than the top bid
466 | skipBlocks(K);
467 |
468 | // verify top bid state
469 | IAmAmm.Bid memory bid = amAmm.getBidWrite(POOL_0, true);
470 | assertEq(bid.manager, address(this), "top bid manager incorrect");
471 | assertEq(bid.payload, _swapFeeToPayload(0.01e6), "top bid swapFee incorrect");
472 | assertEq(bid.rent, 1e18, "top bid rent incorrect");
473 | assertEq(bid.deposit, K * 1e18, "top bid deposit incorrect");
474 | assertEq(bid.blockIdx, _getBlockIdx(), "top bid blockIdx incorrect");
475 |
476 | // verify next bid state
477 | bid = amAmm.getBid(POOL_0, false);
478 | assertEq(bid.manager, address(this), "next bid manager incorrect");
479 | assertEq(bid.payload, _swapFeeToPayload(0.05e6), "next bid swapFee incorrect");
480 | assertEq(bid.rent, 0.5e18, "next bid rent incorrect");
481 | assertEq(bid.deposit, 2 * K * 1e18, "next bid deposit incorrect");
482 | assertEq(bid.blockIdx, nextBidBlockIdx, "next bid blockIdx incorrect");
483 |
484 | // verify bid token balance
485 | assertEq(amAmm.bidToken().balanceOf(address(amAmm)), 3 * K * 1e18, "bid token balance incorrect");
486 |
487 | // wait K blocks
488 | // top bid's deposit is now depleted so next bid should activate
489 | skipBlocks(K);
490 |
491 | // verify top bid state
492 | bid = amAmm.getBidWrite(POOL_0, true);
493 | assertEq(bid.manager, address(this), "later top bid manager incorrect");
494 | assertEq(bid.payload, _swapFeeToPayload(0.05e6), "later top bid swapFee incorrect");
495 | assertEq(bid.rent, 0.5e18, "later top bid rent incorrect");
496 | assertEq(bid.deposit, 2 * K * 1e18, "later top bid deposit incorrect");
497 | assertEq(bid.blockIdx, _getBlockIdx(), "later top bid blockIdx incorrect");
498 |
499 | // verify next bid state
500 | bid = amAmm.getBid(POOL_0, false);
501 | assertEq(bid.manager, address(0), "later next bid manager incorrect");
502 | assertEq(bid.payload, _swapFeeToPayload(0), "later next bid swapFee incorrect");
503 | assertEq(bid.rent, 0, "later next bid rent incorrect");
504 | assertEq(bid.deposit, 0, "later next bid deposit incorrect");
505 | assertEq(bid.blockIdx, 0, "later next bid blockIdx incorrect");
506 | }
507 |
508 | function test_stateTransition_DBA_afterKBlocks() external {
509 | // mint bid tokens
510 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
511 |
512 | // make bid
513 | amAmm.bid({
514 | id: POOL_0,
515 | manager: address(this),
516 | payload: _swapFeeToPayload(0.01e6),
517 | rent: 1e18,
518 | deposit: 2 * K * 1e18
519 | });
520 |
521 | // wait K blocks
522 | skipBlocks(K);
523 |
524 | // mint bid tokens
525 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
526 |
527 | // make bid
528 | amAmm.bid({
529 | id: POOL_0,
530 | manager: address(this),
531 | payload: _swapFeeToPayload(0.05e6),
532 | rent: 2e18,
533 | deposit: 2 * K * 1e18
534 | });
535 |
536 | // wait 2K blocks
537 | skipBlocks(2 * K);
538 |
539 | // verify top bid state
540 | IAmAmm.Bid memory bid = amAmm.getBidWrite(POOL_0, true);
541 | assertEq(bid.manager, address(0), "top bid manager incorrect");
542 | assertEq(bid.payload, _swapFeeToPayload(0), "top bid swapFee incorrect");
543 | assertEq(bid.rent, 0, "top bid rent incorrect");
544 | assertEq(bid.deposit, 0, "top bid deposit incorrect");
545 | assertEq(bid.blockIdx, 0, "top bid blockIdx incorrect");
546 |
547 | // verify next bid state
548 | bid = amAmm.getBid(POOL_0, false);
549 | assertEq(bid.manager, address(0), "next bid manager incorrect");
550 | assertEq(bid.payload, _swapFeeToPayload(0), "next bid swapFee incorrect");
551 | assertEq(bid.rent, 0, "next bid rent incorrect");
552 | assertEq(bid.deposit, 0, "next bid deposit incorrect");
553 | assertEq(bid.blockIdx, 0, "next bid blockIdx incorrect");
554 |
555 | // verify bid token balance
556 | assertEq(amAmm.bidToken().balanceOf(address(amAmm)), K * 1e18, "bid token balance incorrect");
557 |
558 | // verify refund
559 | assertEq(amAmm.getRefund(address(this), POOL_0), K * 1e18, "refund incorrect");
560 | }
561 |
562 | function test_stateTransition_DCBA() external {
563 | // mint bid tokens
564 | amAmm.bidToken().mint(address(this), K * 1e18);
565 |
566 | // make bid
567 | amAmm.bid({
568 | id: POOL_0,
569 | manager: address(this),
570 | payload: _swapFeeToPayload(0.01e6),
571 | rent: 1e18,
572 | deposit: K * 1e18
573 | });
574 |
575 | // wait 2 * K - 3 blocks
576 | skipBlocks((2 * K - 3));
577 |
578 | // mint bid tokens
579 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
580 |
581 | // make bid
582 | amAmm.bid({
583 | id: POOL_0,
584 | manager: address(this),
585 | payload: _swapFeeToPayload(0.05e6),
586 | rent: 2e18,
587 | deposit: 2 * K * 1e18
588 | });
589 |
590 | // wait 2 * K blocks
591 | skipBlocks((2 * K));
592 |
593 | // verify top bid state
594 | IAmAmm.Bid memory bid = amAmm.getBidWrite(POOL_0, true);
595 | assertEq(bid.manager, address(0), "top bid manager incorrect");
596 | assertEq(bid.payload, _swapFeeToPayload(0), "top bid swapFee incorrect");
597 | assertEq(bid.rent, 0, "top bid rent incorrect");
598 | assertEq(bid.deposit, 0, "top bid deposit incorrect");
599 | assertEq(bid.blockIdx, 0, "top bid blockIdx incorrect");
600 |
601 | // verify next bid state
602 | bid = amAmm.getBid(POOL_0, false);
603 | assertEq(bid.manager, address(0), "next bid manager incorrect");
604 | assertEq(bid.payload, _swapFeeToPayload(0), "next bid swapFee incorrect");
605 | assertEq(bid.rent, 0, "next bid rent incorrect");
606 | assertEq(bid.deposit, 0, "next bid deposit incorrect");
607 | assertEq(bid.blockIdx, 0, "next bid blockIdx incorrect");
608 |
609 | // verify bid token balance
610 | assertEq(amAmm.bidToken().balanceOf(address(amAmm)), 0, "bid token balance incorrect");
611 | }
612 |
613 | function test_bid_fail_notEnabled() external {
614 | amAmm.setEnabled(POOL_0, false);
615 | amAmm.bidToken().mint(address(this), K * 1e18);
616 | vm.expectRevert(IAmAmm.AmAmm__NotEnabled.selector);
617 | amAmm.bid({
618 | id: POOL_0,
619 | manager: address(this),
620 | payload: _swapFeeToPayload(0.01e6),
621 | rent: 1e18,
622 | deposit: K * 1e18
623 | });
624 | }
625 |
626 | function test_bid_fail_invalidBid() external {
627 | // start in state D
628 | amAmm.bidToken().mint(address(this), K * 1e18);
629 | amAmm.bid({
630 | id: POOL_0,
631 | manager: address(this),
632 | payload: _swapFeeToPayload(0.01e6),
633 | rent: 1e18,
634 | deposit: K * 1e18
635 | });
636 | skipBlocks(K);
637 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
638 | amAmm.bid({
639 | id: POOL_0,
640 | manager: address(this),
641 | payload: _swapFeeToPayload(0.01e6),
642 | rent: 2e18,
643 | deposit: 2 * K * 1e18
644 | });
645 |
646 | amAmm.bidToken().mint(address(this), 3 * K * 1e18);
647 |
648 | // manager can't be zero address
649 | vm.expectRevert(IAmAmm.AmAmm__InvalidBid.selector);
650 | amAmm.bid({
651 | id: POOL_0,
652 | manager: address(0),
653 | payload: _swapFeeToPayload(0.01e6),
654 | rent: 3e18,
655 | deposit: 3 * K * 1e18
656 | });
657 |
658 | // bid needs to be greater than top bid and next bid by >10%
659 | vm.expectRevert(IAmAmm.AmAmm__InvalidBid.selector);
660 | amAmm.bid({
661 | id: POOL_0,
662 | manager: address(this),
663 | payload: _swapFeeToPayload(0.01e6),
664 | rent: 1e18,
665 | deposit: 3 * K * 1e18
666 | });
667 | vm.expectRevert(IAmAmm.AmAmm__InvalidBid.selector);
668 | amAmm.bid({
669 | id: POOL_0,
670 | manager: address(this),
671 | payload: _swapFeeToPayload(0.01e6),
672 | rent: 1.1e18,
673 | deposit: 3 * K * 1e18
674 | });
675 | vm.expectRevert(IAmAmm.AmAmm__InvalidBid.selector);
676 | amAmm.bid({
677 | id: POOL_0,
678 | manager: address(this),
679 | payload: _swapFeeToPayload(0.01e6),
680 | rent: 2.2e18,
681 | deposit: 3 * K * 1e18
682 | });
683 |
684 | // deposit needs to cover the rent for K hours
685 | vm.expectRevert(IAmAmm.AmAmm__InvalidBid.selector);
686 | amAmm.bid({
687 | id: POOL_0,
688 | manager: address(this),
689 | payload: _swapFeeToPayload(0.01e6),
690 | rent: 3e18,
691 | deposit: 2 * K * 1e18
692 | });
693 |
694 | // deposit needs to be a multiple of rent
695 | vm.expectRevert(IAmAmm.AmAmm__InvalidBid.selector);
696 | amAmm.bid({
697 | id: POOL_0,
698 | manager: address(this),
699 | payload: _swapFeeToPayload(0.01e6),
700 | rent: 3e18,
701 | deposit: 3 * K * 1e18 + 1
702 | });
703 |
704 | // swap fee needs to be <= _maxSwapFee(id)
705 | vm.expectRevert(IAmAmm.AmAmm__InvalidBid.selector);
706 | amAmm.bid({
707 | id: POOL_0,
708 | manager: address(this),
709 | payload: _swapFeeToPayload(0.5e6),
710 | rent: 3e18,
711 | deposit: 3 * K * 1e18
712 | });
713 |
714 | // rent needs to be >= MIN_RENT(id)
715 | amAmm.setMinRent(POOL_0, 1e18);
716 | vm.expectRevert(IAmAmm.AmAmm__InvalidBid.selector);
717 | amAmm.bid({
718 | id: POOL_0,
719 | manager: address(this),
720 | payload: _swapFeeToPayload(0.01e6),
721 | rent: 0.5e18,
722 | deposit: 3 * K * 1e18
723 | });
724 | }
725 |
726 | function test_depositIntoTopBid() external {
727 | // start in state B
728 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
729 | amAmm.bid({
730 | id: POOL_0,
731 | manager: address(this),
732 | payload: _swapFeeToPayload(0.01e6),
733 | rent: 1e18,
734 | deposit: 2 * K * 1e18
735 | });
736 | skipBlocks(K);
737 |
738 | amAmm.bidToken().mint(address(this), K * 1e18);
739 | amAmm.depositIntoBid(POOL_0, K * 1e18, true);
740 |
741 | // verify state
742 | IAmAmm.Bid memory bid = amAmm.getBid(POOL_0, true);
743 | assertEq(bid.manager, address(this), "manager incorrect");
744 | assertEq(bid.payload, _swapFeeToPayload(0.01e6), "swapFee incorrect");
745 | assertEq(bid.rent, 1e18, "rent incorrect");
746 | assertEq(bid.deposit, 3 * K * 1e18, "deposit incorrect");
747 | assertEq(bid.blockIdx, _getBlockIdx(), "blockIdx incorrect");
748 |
749 | // verify token balances
750 | assertEq(amAmm.bidToken().balanceOf(address(this)), 0, "manager balance incorrect");
751 | assertEq(amAmm.bidToken().balanceOf(address(amAmm)), 3 * K * 1e18, "contract balance incorrect");
752 | }
753 |
754 | function test_depositIntoTopBid_fail_notEnabled() external {
755 | // start in state B
756 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
757 | amAmm.bid({
758 | id: POOL_0,
759 | manager: address(this),
760 | payload: _swapFeeToPayload(0.01e6),
761 | rent: 1e18,
762 | deposit: 2 * K * 1e18
763 | });
764 | skipBlocks(K);
765 |
766 | amAmm.bidToken().mint(address(this), K * 1e18);
767 | amAmm.setEnabled(POOL_0, false);
768 | vm.expectRevert(IAmAmm.AmAmm__NotEnabled.selector);
769 | amAmm.depositIntoBid(POOL_0, K * 1e18, true);
770 | }
771 |
772 | function test_depositIntoTopBid_fail_unauthorized() external {
773 | // start in state B
774 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
775 | amAmm.bid({
776 | id: POOL_0,
777 | manager: address(this),
778 | payload: _swapFeeToPayload(0.01e6),
779 | rent: 1e18,
780 | deposit: 2 * K * 1e18
781 | });
782 | skipBlocks(K);
783 |
784 | amAmm.bidToken().mint(address(this), K * 1e18);
785 | vm.startPrank(address(0x42));
786 | vm.expectRevert(IAmAmm.AmAmm__Unauthorized.selector);
787 | amAmm.depositIntoBid(POOL_0, K * 1e18, true);
788 | vm.stopPrank();
789 | }
790 |
791 | function test_depositIntoTopBid_fail_invalidDepositAmount() external {
792 | // start in state B
793 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
794 | amAmm.bid({
795 | id: POOL_0,
796 | manager: address(this),
797 | payload: _swapFeeToPayload(0.01e6),
798 | rent: 1e18,
799 | deposit: 2 * K * 1e18
800 | });
801 | skipBlocks(K);
802 |
803 | amAmm.bidToken().mint(address(this), K * 1e18);
804 | vm.expectRevert(IAmAmm.AmAmm__InvalidDepositAmount.selector);
805 | amAmm.depositIntoBid(POOL_0, K * 1e18 - 1, true);
806 | }
807 |
808 | function test_withdrawFromTopBid() external {
809 | // start in state B
810 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
811 | amAmm.bid({
812 | id: POOL_0,
813 | manager: address(this),
814 | payload: _swapFeeToPayload(0.01e6),
815 | rent: 1e18,
816 | deposit: 2 * K * 1e18
817 | });
818 | skipBlocks(K);
819 |
820 | address recipient = address(0x42);
821 | amAmm.withdrawFromBid(POOL_0, K * 1e18, recipient, true);
822 |
823 | // verify state
824 | IAmAmm.Bid memory bid = amAmm.getBid(POOL_0, true);
825 | assertEq(bid.manager, address(this), "manager incorrect");
826 | assertEq(bid.payload, _swapFeeToPayload(0.01e6), "swapFee incorrect");
827 | assertEq(bid.rent, 1e18, "rent incorrect");
828 | assertEq(bid.deposit, K * 1e18, "deposit incorrect");
829 | assertEq(bid.blockIdx, _getBlockIdx(), "blockIdx incorrect");
830 |
831 | // verify token balances
832 | assertEq(amAmm.bidToken().balanceOf(recipient), K * 1e18, "recipient balance incorrect");
833 | }
834 |
835 | function test_withdrawFromTopBid_fail_notEnabled() external {
836 | // start in state B
837 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
838 | amAmm.bid({
839 | id: POOL_0,
840 | manager: address(this),
841 | payload: _swapFeeToPayload(0.01e6),
842 | rent: 1e18,
843 | deposit: 2 * K * 1e18
844 | });
845 | skipBlocks(K);
846 |
847 | amAmm.setEnabled(POOL_0, false);
848 | vm.expectRevert(IAmAmm.AmAmm__NotEnabled.selector);
849 | amAmm.withdrawFromBid(POOL_0, K * 1e18, address(this), true);
850 | }
851 |
852 | function test_withdrawFromTopBid_fail_unauthorized() external {
853 | // start in state B
854 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
855 | amAmm.bid({
856 | id: POOL_0,
857 | manager: address(this),
858 | payload: _swapFeeToPayload(0.01e6),
859 | rent: 1e18,
860 | deposit: 2 * K * 1e18
861 | });
862 | skipBlocks(K);
863 |
864 | address recipient = address(0x42);
865 | vm.startPrank(recipient);
866 | vm.expectRevert(IAmAmm.AmAmm__Unauthorized.selector);
867 | amAmm.withdrawFromBid(POOL_0, K * 1e18, recipient, true);
868 | vm.stopPrank();
869 | }
870 |
871 | function test_withdrawFromTopBid_fail_invalidDepositAmount() external {
872 | // start in state B
873 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
874 | amAmm.bid({
875 | id: POOL_0,
876 | manager: address(this),
877 | payload: _swapFeeToPayload(0.01e6),
878 | rent: 1e18,
879 | deposit: 2 * K * 1e18
880 | });
881 | skipBlocks(K);
882 |
883 | vm.expectRevert(IAmAmm.AmAmm__InvalidDepositAmount.selector);
884 | amAmm.withdrawFromBid(POOL_0, K * 1e18 - 1, address(this), true);
885 | }
886 |
887 | function test_withdrawFromTopBid_bidLocked() external {
888 | // start in state B
889 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
890 | amAmm.bid({
891 | id: POOL_0,
892 | manager: address(this),
893 | payload: _swapFeeToPayload(0.01e6),
894 | rent: 1e18,
895 | deposit: 2 * K * 1e18
896 | });
897 | skipBlocks(K);
898 |
899 | vm.expectRevert(IAmAmm.AmAmm__BidLocked.selector);
900 | amAmm.withdrawFromBid(POOL_0, 2 * K * 1e18, address(this), true);
901 | }
902 |
903 | function test_depositIntoNextBid() external {
904 | // start in state C
905 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
906 | amAmm.bid({
907 | id: POOL_0,
908 | manager: address(this),
909 | payload: _swapFeeToPayload(0.01e6),
910 | rent: 1e18,
911 | deposit: 2 * K * 1e18
912 | });
913 |
914 | amAmm.bidToken().mint(address(this), K * 1e18);
915 | amAmm.depositIntoBid(POOL_0, K * 1e18, false);
916 |
917 | // verify state
918 | IAmAmm.Bid memory bid = amAmm.getBid(POOL_0, false);
919 | assertEq(bid.manager, address(this), "manager incorrect");
920 | assertEq(bid.payload, _swapFeeToPayload(0.01e6), "swapFee incorrect");
921 | assertEq(bid.rent, 1e18, "rent incorrect");
922 | assertEq(bid.deposit, 3 * K * 1e18, "deposit incorrect");
923 | assertEq(bid.blockIdx, _getBlockIdx(), "blockIdx incorrect");
924 |
925 | // verify token balances
926 | assertEq(amAmm.bidToken().balanceOf(address(this)), 0, "manager balance incorrect");
927 | assertEq(amAmm.bidToken().balanceOf(address(amAmm)), 3 * K * 1e18, "contract balance incorrect");
928 | }
929 |
930 | function test_depositIntoNextBid_fail_notEnabled() external {
931 | // start in state C
932 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
933 | amAmm.bid({
934 | id: POOL_0,
935 | manager: address(this),
936 | payload: _swapFeeToPayload(0.01e6),
937 | rent: 1e18,
938 | deposit: 2 * K * 1e18
939 | });
940 |
941 | amAmm.bidToken().mint(address(this), K * 1e18);
942 | amAmm.setEnabled(POOL_0, false);
943 | vm.expectRevert(IAmAmm.AmAmm__NotEnabled.selector);
944 | amAmm.depositIntoBid(POOL_0, K * 1e18, false);
945 | }
946 |
947 | function test_depositIntoNextBid_fail_unauthorized() external {
948 | // start in state C
949 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
950 | amAmm.bid({
951 | id: POOL_0,
952 | manager: address(this),
953 | payload: _swapFeeToPayload(0.01e6),
954 | rent: 1e18,
955 | deposit: 2 * K * 1e18
956 | });
957 |
958 | amAmm.bidToken().mint(address(this), K * 1e18);
959 | vm.startPrank(address(0x42));
960 | vm.expectRevert(IAmAmm.AmAmm__Unauthorized.selector);
961 | amAmm.depositIntoBid(POOL_0, K * 1e18, false);
962 | vm.stopPrank();
963 | }
964 |
965 | function test_depositIntoNextBid_fail_invalidDepositAmount() external {
966 | // start in state C
967 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
968 | amAmm.bid({
969 | id: POOL_0,
970 | manager: address(this),
971 | payload: _swapFeeToPayload(0.01e6),
972 | rent: 1e18,
973 | deposit: 2 * K * 1e18
974 | });
975 |
976 | amAmm.bidToken().mint(address(this), K * 1e18);
977 | vm.expectRevert(IAmAmm.AmAmm__InvalidDepositAmount.selector);
978 | amAmm.depositIntoBid(POOL_0, K * 1e18 - 1, false);
979 | }
980 |
981 | function test_withdrawFromNextBid() external {
982 | // start in state C
983 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
984 | amAmm.bid({
985 | id: POOL_0,
986 | manager: address(this),
987 | payload: _swapFeeToPayload(0.01e6),
988 | rent: 1e18,
989 | deposit: 2 * K * 1e18
990 | });
991 |
992 | address recipient = address(0x42);
993 | amAmm.withdrawFromBid(POOL_0, K * 1e18, recipient, false);
994 |
995 | // verify state
996 | IAmAmm.Bid memory bid = amAmm.getBid(POOL_0, false);
997 | assertEq(bid.manager, address(this), "manager incorrect");
998 | assertEq(bid.payload, _swapFeeToPayload(0.01e6), "swapFee incorrect");
999 | assertEq(bid.rent, 1e18, "rent incorrect");
1000 | assertEq(bid.deposit, K * 1e18, "deposit incorrect");
1001 | assertEq(bid.blockIdx, _getBlockIdx(), "blockIdx incorrect");
1002 |
1003 | // verify token balances
1004 | assertEq(amAmm.bidToken().balanceOf(recipient), K * 1e18, "recipient balance incorrect");
1005 | }
1006 |
1007 | function test_withdrawFromNextBid_fail_notEnabled() external {
1008 | // start in state C
1009 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
1010 | amAmm.bid({
1011 | id: POOL_0,
1012 | manager: address(this),
1013 | payload: _swapFeeToPayload(0.01e6),
1014 | rent: 1e18,
1015 | deposit: 2 * K * 1e18
1016 | });
1017 |
1018 | amAmm.setEnabled(POOL_0, false);
1019 | vm.expectRevert(IAmAmm.AmAmm__NotEnabled.selector);
1020 | amAmm.withdrawFromBid(POOL_0, K * 1e18, address(this), false);
1021 | }
1022 |
1023 | function test_withdrawFromNextBid_fail_unauthorized() external {
1024 | // start in state C
1025 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
1026 | amAmm.bid({
1027 | id: POOL_0,
1028 | manager: address(this),
1029 | payload: _swapFeeToPayload(0.01e6),
1030 | rent: 1e18,
1031 | deposit: 2 * K * 1e18
1032 | });
1033 |
1034 | address recipient = address(0x42);
1035 | vm.startPrank(recipient);
1036 | vm.expectRevert(IAmAmm.AmAmm__Unauthorized.selector);
1037 | amAmm.withdrawFromBid(POOL_0, K * 1e18, recipient, false);
1038 | vm.stopPrank();
1039 | }
1040 |
1041 | function test_withdrawFromNextBid_fail_invalidDepositAmount() external {
1042 | // start in state C
1043 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
1044 | amAmm.bid({
1045 | id: POOL_0,
1046 | manager: address(this),
1047 | payload: _swapFeeToPayload(0.01e6),
1048 | rent: 1e18,
1049 | deposit: 2 * K * 1e18
1050 | });
1051 |
1052 | vm.expectRevert(IAmAmm.AmAmm__InvalidDepositAmount.selector);
1053 | amAmm.withdrawFromBid(POOL_0, K * 1e18 - 1, address(this), false);
1054 | }
1055 |
1056 | function test_withdrawFromNextBid_fail_bidLocked() external {
1057 | // start in state C
1058 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
1059 | amAmm.bid({
1060 | id: POOL_0,
1061 | manager: address(this),
1062 | payload: _swapFeeToPayload(0.01e6),
1063 | rent: 1e18,
1064 | deposit: 2 * K * 1e18
1065 | });
1066 |
1067 | vm.expectRevert(IAmAmm.AmAmm__BidLocked.selector);
1068 | amAmm.withdrawFromBid(POOL_0, 2 * K * 1e18, address(this), false);
1069 | }
1070 |
1071 | function test_claimRefund() external {
1072 | // start in state D
1073 | amAmm.bidToken().mint(address(this), K * 1e18);
1074 | amAmm.bid({
1075 | id: POOL_0,
1076 | manager: address(this),
1077 | payload: _swapFeeToPayload(0.01e6),
1078 | rent: 1e18,
1079 | deposit: K * 1e18
1080 | });
1081 | skipBlocks(K);
1082 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
1083 | amAmm.bid({
1084 | id: POOL_0,
1085 | manager: address(this),
1086 | payload: _swapFeeToPayload(0.01e6),
1087 | rent: 2e18,
1088 | deposit: 2 * K * 1e18
1089 | });
1090 |
1091 | // make higher bid
1092 | amAmm.bidToken().mint(address(this), 3 * K * 1e18);
1093 | amAmm.bid({
1094 | id: POOL_0,
1095 | manager: address(this),
1096 | payload: _swapFeeToPayload(0.01e6),
1097 | rent: 3e18,
1098 | deposit: 3 * K * 1e18
1099 | });
1100 |
1101 | assertEq(amAmm.getRefund(address(this), POOL_0), 2 * K * 1e18, "get refund incorrect");
1102 |
1103 | // claim refund
1104 | address recipient = address(0x42);
1105 | uint256 refundAmount = amAmm.claimRefund(POOL_0, recipient);
1106 |
1107 | assertEq(refundAmount, 2 * K * 1e18, "refund amount incorrect");
1108 | assertEq(amAmm.bidToken().balanceOf(recipient), 2 * K * 1e18, "recipient balance incorrect");
1109 | }
1110 |
1111 | function test_claimRefund_fail_notEnabled() external {
1112 | // start in state D
1113 | amAmm.bidToken().mint(address(this), K * 1e18);
1114 | amAmm.bid({
1115 | id: POOL_0,
1116 | manager: address(this),
1117 | payload: _swapFeeToPayload(0.01e6),
1118 | rent: 1e18,
1119 | deposit: K * 1e18
1120 | });
1121 | skipBlocks(K);
1122 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
1123 | amAmm.bid({
1124 | id: POOL_0,
1125 | manager: address(this),
1126 | payload: _swapFeeToPayload(0.01e6),
1127 | rent: 2e18,
1128 | deposit: 2 * K * 1e18
1129 | });
1130 |
1131 | // make higher bid
1132 | amAmm.bidToken().mint(address(this), 3 * K * 1e18);
1133 | amAmm.bid({
1134 | id: POOL_0,
1135 | manager: address(this),
1136 | payload: _swapFeeToPayload(0.01e6),
1137 | rent: 3e18,
1138 | deposit: 3 * K * 1e18
1139 | });
1140 |
1141 | amAmm.setEnabled(POOL_0, false);
1142 | vm.expectRevert(IAmAmm.AmAmm__NotEnabled.selector);
1143 | amAmm.claimRefund(POOL_0, address(this));
1144 | }
1145 |
1146 | function test_claimFees() external {
1147 | // start in state D
1148 | amAmm.bidToken().mint(address(this), K * 1e18);
1149 | amAmm.bid({
1150 | id: POOL_0,
1151 | manager: address(this),
1152 | payload: _swapFeeToPayload(0.01e6),
1153 | rent: 1e18,
1154 | deposit: K * 1e18
1155 | });
1156 | skipBlocks(K);
1157 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
1158 | amAmm.bid({
1159 | id: POOL_0,
1160 | manager: address(0x69),
1161 | payload: _swapFeeToPayload(0.01e6),
1162 | rent: 2e18,
1163 | deposit: 2 * K * 1e18
1164 | });
1165 |
1166 | // give fees
1167 | amAmm.giveFeeToken0(POOL_0, 1 ether);
1168 | amAmm.giveFeeToken1(POOL_0, 2 ether);
1169 |
1170 | // claim fees
1171 | address recipient = address(0x42);
1172 | uint256 feeAmount0 = amAmm.claimFees(Currency.wrap(address(amAmm.feeToken0())), recipient);
1173 | uint256 feeAmount1 = amAmm.claimFees(Currency.wrap(address(amAmm.feeToken1())), recipient);
1174 |
1175 | // check results
1176 | assertEq(feeAmount0, 1 ether, "feeAmount0 incorrect");
1177 | assertEq(feeAmount1, 2 ether, "feeAmount1 incorrect");
1178 | assertEq(amAmm.feeToken0().balanceOf(recipient), 1 ether, "recipient balance0 incorrect");
1179 | assertEq(amAmm.feeToken1().balanceOf(recipient), 2 ether, "recipient balance1 incorrect");
1180 | }
1181 |
1182 | function test_increaseBidRent_topBid_addDeposit() external {
1183 | uint128 additionalRent = 1e18;
1184 | uint128 additionalDeposit = 2e18;
1185 |
1186 | // start in state B
1187 | amAmm.bidToken().mint(address(this), 2 * K * 1e18 + additionalDeposit);
1188 | amAmm.bid({
1189 | id: POOL_0,
1190 | manager: address(this),
1191 | payload: _swapFeeToPayload(0.01e6),
1192 | rent: 1e18,
1193 | deposit: 2 * K * 1e18
1194 | });
1195 | skipBlocks(K);
1196 |
1197 | uint256 beforeUserBalance = amAmm.bidToken().balanceOf(address(this));
1198 | uint256 beforeAmAmmBalance = amAmm.bidToken().balanceOf(address(amAmm));
1199 | (uint128 amountDeposited, uint128 amountWithdrawn) =
1200 | amAmm.increaseBidRent(POOL_0, additionalRent, 2 * K * 1e18 + additionalDeposit, true, address(this));
1201 |
1202 | // verify state
1203 | IAmAmm.Bid memory bid = amAmm.getBid(POOL_0, true);
1204 | assertEq(bid.manager, address(this), "manager incorrect");
1205 | assertEq(bid.payload, _swapFeeToPayload(0.01e6), "swapFee incorrect");
1206 | assertEq(bid.rent, 1e18 + additionalRent, "rent incorrect");
1207 | assertEq(bid.deposit, 2 * K * 1e18 + additionalDeposit, "deposit incorrect");
1208 | assertEq(bid.blockIdx, _getBlockIdx(), "blockIdx incorrect");
1209 |
1210 | // verify balances
1211 | assertEq(
1212 | amAmm.bidToken().balanceOf(address(this)), beforeUserBalance - additionalDeposit, "user balance incorrect"
1213 | );
1214 | assertEq(
1215 | amAmm.bidToken().balanceOf(address(amAmm)),
1216 | beforeAmAmmBalance + additionalDeposit,
1217 | "amAmm balance incorrect"
1218 | );
1219 | assertEq(amountDeposited, additionalDeposit, "amountDeposited incorrect");
1220 | assertEq(amountWithdrawn, 0, "amountWithdrawn incorrect");
1221 | }
1222 |
1223 | function test_increaseBidRent_topBid_withdrawDeposit() external {
1224 | uint128 additionalRent = 1e18;
1225 | uint128 withdrawAmount = K * 1e18;
1226 |
1227 | // start in state B
1228 | amAmm.bidToken().mint(address(this), 3 * K * 1e18);
1229 | amAmm.bid({
1230 | id: POOL_0,
1231 | manager: address(this),
1232 | payload: _swapFeeToPayload(0.01e6),
1233 | rent: 1e18,
1234 | deposit: 3 * K * 1e18
1235 | });
1236 | skipBlocks(K);
1237 |
1238 | uint256 beforeUserBalance = amAmm.bidToken().balanceOf(address(this));
1239 | uint256 beforeAmAmmBalance = amAmm.bidToken().balanceOf(address(amAmm));
1240 | (uint128 amountDeposited, uint128 amountWithdrawn) =
1241 | amAmm.increaseBidRent(POOL_0, additionalRent, 3 * K * 1e18 - withdrawAmount, true, address(this));
1242 |
1243 | // verify state
1244 | IAmAmm.Bid memory bid = amAmm.getBid(POOL_0, true);
1245 | assertEq(bid.manager, address(this), "manager incorrect");
1246 | assertEq(bid.payload, _swapFeeToPayload(0.01e6), "swapFee incorrect");
1247 | assertEq(bid.rent, 1e18 + additionalRent, "rent incorrect");
1248 | assertEq(bid.deposit, 3 * K * 1e18 - withdrawAmount, "deposit incorrect");
1249 | assertEq(bid.blockIdx, _getBlockIdx(), "blockIdx incorrect");
1250 |
1251 | // verify balances
1252 | assertEq(
1253 | amAmm.bidToken().balanceOf(address(this)), beforeUserBalance + withdrawAmount, "user balance incorrect"
1254 | );
1255 | assertEq(
1256 | amAmm.bidToken().balanceOf(address(amAmm)), beforeAmAmmBalance - withdrawAmount, "amAmm balance incorrect"
1257 | );
1258 | assertEq(amountDeposited, 0, "amountDeposited incorrect");
1259 | assertEq(amountWithdrawn, withdrawAmount, "amountWithdrawn incorrect");
1260 | }
1261 |
1262 | function test_increaseBidRent_nextBid_addDeposit() external {
1263 | uint128 additionalRent = 1e18;
1264 | uint128 additionalDeposit = K * 1e18;
1265 |
1266 | // start in state D
1267 | amAmm.bidToken().mint(address(this), K * 1e18);
1268 | amAmm.bid({
1269 | id: POOL_0,
1270 | manager: address(this),
1271 | payload: _swapFeeToPayload(0.01e6),
1272 | rent: 1e18,
1273 | deposit: K * 1e18
1274 | });
1275 | skipBlocks(K);
1276 | amAmm.bidToken().mint(address(this), 2 * K * 1e18 + additionalDeposit);
1277 | amAmm.bid({
1278 | id: POOL_0,
1279 | manager: address(this),
1280 | payload: _swapFeeToPayload(0.02e6),
1281 | rent: 2e18,
1282 | deposit: 2 * K * 1e18
1283 | });
1284 |
1285 | uint256 beforeUserBalance = amAmm.bidToken().balanceOf(address(this));
1286 | uint256 beforeAmAmmBalance = amAmm.bidToken().balanceOf(address(amAmm));
1287 | amAmm.increaseBidRent(POOL_0, additionalRent, 2 * K * 1e18 + additionalDeposit, false, address(this));
1288 |
1289 | // verify state
1290 | IAmAmm.Bid memory bid = amAmm.getBid(POOL_0, false);
1291 | assertEq(bid.manager, address(this), "manager incorrect");
1292 | assertEq(bid.payload, _swapFeeToPayload(0.02e6), "swapFee incorrect");
1293 | assertEq(bid.rent, 2e18 + additionalRent, "rent incorrect");
1294 | assertEq(bid.deposit, 2 * K * 1e18 + additionalDeposit, "deposit incorrect");
1295 | assertEq(bid.blockIdx, _getBlockIdx(), "blockIdx incorrect");
1296 |
1297 | // verify balances
1298 | assertEq(
1299 | amAmm.bidToken().balanceOf(address(this)), beforeUserBalance - additionalDeposit, "user balance incorrect"
1300 | );
1301 | assertEq(
1302 | amAmm.bidToken().balanceOf(address(amAmm)),
1303 | beforeAmAmmBalance + additionalDeposit,
1304 | "amAmm balance incorrect"
1305 | );
1306 | }
1307 |
1308 | function test_increaseBidRent_nextBid_withdrawDeposit() external {
1309 | uint128 additionalRent = 1e18;
1310 | uint128 withdrawAmount = K * 1e18;
1311 |
1312 | // start in state D
1313 | amAmm.bidToken().mint(address(this), K * 1e18);
1314 | amAmm.bid({
1315 | id: POOL_0,
1316 | manager: address(this),
1317 | payload: _swapFeeToPayload(0.01e6),
1318 | rent: 1e18,
1319 | deposit: K * 1e18
1320 | });
1321 | skipBlocks(K);
1322 | amAmm.bidToken().mint(address(this), 4 * K * 1e18);
1323 | amAmm.bid({
1324 | id: POOL_0,
1325 | manager: address(this),
1326 | payload: _swapFeeToPayload(0.02e6),
1327 | rent: 2e18,
1328 | deposit: 4 * K * 1e18
1329 | });
1330 |
1331 | uint256 beforeUserBalance = amAmm.bidToken().balanceOf(address(this));
1332 | uint256 beforeAmAmmBalance = amAmm.bidToken().balanceOf(address(amAmm));
1333 | (uint128 amountDeposited, uint128 amountWithdrawn) =
1334 | amAmm.increaseBidRent(POOL_0, additionalRent, 4 * K * 1e18 - withdrawAmount, false, address(this));
1335 |
1336 | // verify state
1337 | IAmAmm.Bid memory bid = amAmm.getBid(POOL_0, false);
1338 | assertEq(bid.manager, address(this), "manager incorrect");
1339 | assertEq(bid.payload, _swapFeeToPayload(0.02e6), "swapFee incorrect");
1340 | assertEq(bid.rent, 2e18 + additionalRent, "rent incorrect");
1341 | assertEq(bid.deposit, 4 * K * 1e18 - withdrawAmount, "deposit incorrect");
1342 | assertEq(bid.blockIdx, _getBlockIdx(), "blockIdx incorrect");
1343 |
1344 | // verify balances
1345 | assertEq(
1346 | amAmm.bidToken().balanceOf(address(this)), beforeUserBalance + withdrawAmount, "user balance incorrect"
1347 | );
1348 | assertEq(
1349 | amAmm.bidToken().balanceOf(address(amAmm)), beforeAmAmmBalance - withdrawAmount, "amAmm balance incorrect"
1350 | );
1351 | assertEq(amountDeposited, 0, "amountDeposited incorrect");
1352 | assertEq(amountWithdrawn, withdrawAmount, "amountWithdrawn incorrect");
1353 | }
1354 |
1355 | function test_increaseBidRent_fail_notEnabled() external {
1356 | // start in state B
1357 | amAmm.bidToken().mint(address(this), 2 * K * 1e18 + 1e18);
1358 | amAmm.bid({
1359 | id: POOL_0,
1360 | manager: address(this),
1361 | payload: _swapFeeToPayload(0.01e6),
1362 | rent: 1e18,
1363 | deposit: 2 * K * 1e18
1364 | });
1365 | skipBlocks(K);
1366 |
1367 | amAmm.setEnabled(POOL_0, false);
1368 | vm.expectRevert(IAmAmm.AmAmm__NotEnabled.selector);
1369 | amAmm.increaseBidRent(POOL_0, 1e18, 1e18, true, address(this));
1370 | }
1371 |
1372 | function test_increaseBidRent_fail_unauthorized() external {
1373 | // start in state B
1374 | amAmm.bidToken().mint(address(this), 2 * K * 1e18 + 1e18);
1375 | amAmm.bid({
1376 | id: POOL_0,
1377 | manager: address(this),
1378 | payload: _swapFeeToPayload(0.01e6),
1379 | rent: 1e18,
1380 | deposit: 2 * K * 1e18
1381 | });
1382 | skipBlocks(K);
1383 |
1384 | address eve = address(0x42);
1385 | vm.startPrank(eve);
1386 | vm.expectRevert(IAmAmm.AmAmm__Unauthorized.selector);
1387 | amAmm.increaseBidRent(POOL_0, 1e18, 1e18, true, address(this));
1388 | vm.stopPrank();
1389 | }
1390 |
1391 | function test_increaseBidRent_fail_invalidBid() external {
1392 | // start in state B
1393 | amAmm.bidToken().mint(address(this), 2 * K * 1e18 + 1);
1394 | amAmm.bid({
1395 | id: POOL_0,
1396 | manager: address(this),
1397 | payload: _swapFeeToPayload(0.01e6),
1398 | rent: 1e18,
1399 | deposit: 2 * K * 1e18
1400 | });
1401 | skipBlocks(K);
1402 |
1403 | // updated deposit not a multiple of rent
1404 | vm.expectRevert(IAmAmm.AmAmm__InvalidBid.selector);
1405 | amAmm.increaseBidRent(POOL_0, 1e18, 1, true, address(this));
1406 |
1407 | // rent too low
1408 | amAmm.setMinRent(POOL_0, 10e18);
1409 | vm.expectRevert(IAmAmm.AmAmm__InvalidBid.selector);
1410 | amAmm.increaseBidRent(POOL_0, 1e18, 2 * K * 1e18, true, address(this));
1411 | }
1412 |
1413 | function test_increaseBidRent_fail_bidLocked() external {
1414 | // start in state B
1415 | amAmm.bidToken().mint(address(this), 2 * K * 1e18 + 1e18);
1416 | amAmm.bid({
1417 | id: POOL_0,
1418 | manager: address(this),
1419 | payload: _swapFeeToPayload(0.01e6),
1420 | rent: 1e18,
1421 | deposit: 2 * K * 1e18
1422 | });
1423 | skipBlocks(K);
1424 |
1425 | vm.expectRevert(IAmAmm.AmAmm__BidLocked.selector);
1426 | amAmm.increaseBidRent(POOL_0, K * 1e18 - 1e18, 2 * K * 1e18, true, address(this));
1427 | }
1428 |
1429 | function test_setBidPayload_topBid() external {
1430 | // start in state B
1431 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
1432 | amAmm.bid({
1433 | id: POOL_0,
1434 | manager: address(this),
1435 | payload: _swapFeeToPayload(0.01e6),
1436 | rent: 1e18,
1437 | deposit: 2 * K * 1e18
1438 | });
1439 | skipBlocks(K);
1440 |
1441 | amAmm.setBidPayload(POOL_0, _swapFeeToPayload(0.02e6), true);
1442 |
1443 | // verify state
1444 | IAmAmm.Bid memory bid = amAmm.getBid(POOL_0, true);
1445 | assertEq(bid.manager, address(this), "manager incorrect");
1446 | assertEq(bid.payload, _swapFeeToPayload(0.02e6), "swapFee incorrect");
1447 | assertEq(bid.rent, 1e18, "rent incorrect");
1448 | assertEq(bid.deposit, 2 * K * 1e18, "deposit incorrect");
1449 | assertEq(bid.blockIdx, _getBlockIdx(), "blockIdx incorrect");
1450 | }
1451 |
1452 | function test_setBidPayload_nextBid() external {
1453 | // start in state D
1454 | amAmm.bidToken().mint(address(this), K * 1e18);
1455 | amAmm.bid({
1456 | id: POOL_0,
1457 | manager: address(this),
1458 | payload: _swapFeeToPayload(0.01e6),
1459 | rent: 1e18,
1460 | deposit: K * 1e18
1461 | });
1462 | skipBlocks(K);
1463 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
1464 | amAmm.bid({
1465 | id: POOL_0,
1466 | manager: address(this),
1467 | payload: _swapFeeToPayload(0.01e6),
1468 | rent: 2e18,
1469 | deposit: 2 * K * 1e18
1470 | });
1471 |
1472 | amAmm.setBidPayload(POOL_0, _swapFeeToPayload(0.02e6), false);
1473 |
1474 | // verify state
1475 | IAmAmm.Bid memory bid = amAmm.getBid(POOL_0, false);
1476 | assertEq(bid.manager, address(this), "manager incorrect");
1477 | assertEq(bid.payload, _swapFeeToPayload(0.02e6), "swapFee incorrect");
1478 | assertEq(bid.rent, 2e18, "rent incorrect");
1479 | assertEq(bid.deposit, 2 * K * 1e18, "deposit incorrect");
1480 | assertEq(bid.blockIdx, _getBlockIdx(), "blockIdx incorrect");
1481 | }
1482 |
1483 | function test_setBidPayload_fail_notEnabled() external {
1484 | // start in state B
1485 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
1486 | amAmm.bid({
1487 | id: POOL_0,
1488 | manager: address(this),
1489 | payload: _swapFeeToPayload(0.01e6),
1490 | rent: 1e18,
1491 | deposit: 2 * K * 1e18
1492 | });
1493 | skipBlocks(K);
1494 |
1495 | amAmm.setEnabled(POOL_0, false);
1496 | vm.expectRevert(IAmAmm.AmAmm__NotEnabled.selector);
1497 | amAmm.setBidPayload(POOL_0, _swapFeeToPayload(0.02e6), true);
1498 | }
1499 |
1500 | function test_setBidPayload_fail_unauthorized() external {
1501 | // start in state B
1502 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
1503 | amAmm.bid({
1504 | id: POOL_0,
1505 | manager: address(this),
1506 | payload: _swapFeeToPayload(0.01e6),
1507 | rent: 1e18,
1508 | deposit: 2 * K * 1e18
1509 | });
1510 | skipBlocks(K);
1511 |
1512 | address eve = address(0x42);
1513 | vm.startPrank(eve);
1514 | vm.expectRevert(IAmAmm.AmAmm__Unauthorized.selector);
1515 | amAmm.setBidPayload(POOL_0, _swapFeeToPayload(0.02e6), true);
1516 | vm.stopPrank();
1517 | }
1518 |
1519 | function test_setBidPayload_fail_invalidSwapFee() external {
1520 | // start in state B
1521 | amAmm.bidToken().mint(address(this), 2 * K * 1e18);
1522 | amAmm.bid({
1523 | id: POOL_0,
1524 | manager: address(this),
1525 | payload: _swapFeeToPayload(0.01e6),
1526 | rent: 1e18,
1527 | deposit: 2 * K * 1e18
1528 | });
1529 | skipBlocks(K);
1530 |
1531 | vm.expectRevert(IAmAmm.AmAmm__InvalidBid.selector);
1532 | amAmm.setBidPayload(POOL_0, _swapFeeToPayload(0.5e6), true);
1533 | }
1534 |
1535 | function _getBlockIdx() internal view returns (uint48) {
1536 | return uint48(vm.getBlockNumber() - deployBlockNumber);
1537 | }
1538 |
1539 | function skipBlocks(uint256 numBlocks) internal {
1540 | vm.roll(vm.getBlockNumber() + numBlocks);
1541 | }
1542 | }
1543 |
--------------------------------------------------------------------------------