Skip to content

Commit b301603

Browse files
authored
Merge pull request #874 from ajna-finance/develop
Merge develop
2 parents 19cba1a + 989b5f2 commit b301603

File tree

217 files changed

+15117
-3085
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

217 files changed

+15117
-3085
lines changed

.env.example

+5-16
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ ETHERSCAN_API_KEY=
66
## for deployment ##
77
ALCHEMY_API_KEY=
88

9-
## Infura API key
9+
## Infura API key ##
1010
WEB3_INFURA_PROJECT_ID=
1111

1212
## Ethereum node endpoint ##
@@ -19,21 +19,10 @@ ETH_FROM=
1919
ETH_GAS=15000000
2020

2121
## Solidity Compiler Version ##
22-
SOLC_VERSION=0.8.14
22+
SOLC_VERSION=0.8.18
2323

24-
# AJNA token address for target chain
24+
## AJNA token address for target chain ##
2525
AJNA_TOKEN=0xaadebCF61AA7Da0573b524DE57c67aDa797D46c5
2626

27-
# path to the JSON keystore file for your deployment account
28-
DEPLOY_KEY=
29-
30-
# Default token precisions for (invariant) testing
31-
QUOTE_PRECISION = 18
32-
COLLATERAL_PRECISION = 18
33-
34-
# Default bucket Index for (invariant) testing
35-
BUCKET_INDEX_ERC20 = 2570
36-
BUCKET_INDEX_ERC721 = 850
37-
38-
# Default no of buckets to use for (invariant) testing
39-
NO_OF_BUCKETS = 3
27+
## path to the JSON keystore file for your deployment account ##
28+
DEPLOY_KEY=

.github/workflows/forge-test.yml

+1-7
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,6 @@ env:
88

99
jobs:
1010
check:
11-
env:
12-
QUOTE_PRECISION: 18
13-
COLLATERAL_PRECISION: 18
14-
BUCKET_INDEX_ERC20: 2570
15-
BUCKET_INDEX_ERC721: 850
16-
NO_OF_BUCKETS: 3
1711
strategy:
1812
fail-fast: true
1913

@@ -37,5 +31,5 @@ jobs:
3731

3832
- name: Run tests
3933
run: |
40-
make test-with-gas-report && make test-regression
34+
make test-with-gas-report && make test-regression-all
4135
id: test

.github/workflows/slither.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010

1111
- name: Install and set solc version
1212
run: |
13-
pip install solc-select && solc-select install 0.8.14 && solc-select use 0.8.14
13+
pip install solc-select && solc-select install 0.8.18 && solc-select use 0.8.18
1414
id: solc
1515

1616
- name: Install Slither

.gitignore

+8-1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,11 @@ coverage/
1414
*.info
1515
report/
1616
keystore/
17-
broadcast/
17+
broadcast/
18+
logFile.txt
19+
20+
# Certora
21+
.certora_internal/
22+
.certora_recent_jobs.json
23+
.zip-output-url.txt
24+
*.zip

Makefile

+44-23
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
# include .env file and export its env vars
22
# (-include to ignore error if it does not exist)
3-
-include .env
3+
-include .env && source ./tests/forge/invariants/scenarios/scenario-${SCENARIO}.sh
44

5-
# Default token precisions for invariant testing
6-
QUOTE_PRECISION = 18
7-
COLLATERAL_PRECISION = 18
8-
9-
# Default buckets for invariant testing
10-
BUCKET_INDEX_ERC20 = 2570
11-
BUCKET_INDEX_ERC721 = 850
12-
NO_OF_BUCKETS = 3
5+
CONTRACT_EXCLUDES="RegressionTest|Panic|RealWorld|Trading"
6+
TEST_EXCLUDES="testLoad|invariant|test_regression"
137

148
all: clean install build
159

@@ -22,20 +16,47 @@ install :; git submodule update --init --recursive
2216
# Builds
2317
build :; forge clean && forge build
2418

25-
# Tests
26-
test :; forge test --no-match-test "testLoad|invariant|test_regression" # --ffi # enable if you need the `ffi` cheat code on HEVM
27-
test-with-gas-report :; forge test --no-match-test "testLoad|invariant|test_regression" --gas-report # --ffi # enable if you need the `ffi` cheat code on HEVM
28-
test-load :; forge test --match-test testLoad --gas-report
29-
test-invariant :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} BUCKET_INDEX_ERC20=${BUCKET_INDEX_ERC20} BUCKET_INDEX_ERC721=${BUCKET_INDEX_ERC721} NO_OF_BUCKETS=${NO_OF_BUCKETS} forge t --mt invariant --nmc RegressionTest
30-
test-invariant-erc20 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} BUCKET_INDEX_ERC20=${BUCKET_INDEX_ERC20} NO_OF_BUCKETS=${NO_OF_BUCKETS} forge t --mt invariant --nmc RegressionTest --mc ERC20
31-
test-invariant-erc721 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} BUCKET_INDEX_ERC721=${BUCKET_INDEX_ERC721} NO_OF_BUCKETS=${NO_OF_BUCKETS} forge t --mt invariant --nmc RegressionTest --mc ERC721
32-
test-regression :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} BUCKET_INDEX_ERC20=${BUCKET_INDEX_ERC20} BUCKET_INDEX_ERC721=${BUCKET_INDEX_ERC721} NO_OF_BUCKETS=${NO_OF_BUCKETS} forge t --mt test_regression
33-
test-regression-erc20 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} COLLATERAL_PRECISION=${COLLATERAL_PRECISION} BUCKET_INDEX_ERC20=${BUCKET_INDEX_ERC20} NO_OF_BUCKETS=${NO_OF_BUCKETS} forge t --mt test_regression --mc ERC20
34-
test-regression-erc721 :; eval QUOTE_PRECISION=${QUOTE_PRECISION} BUCKET_INDEX_ERC721=${BUCKET_INDEX_ERC721} NO_OF_BUCKETS=${NO_OF_BUCKETS} forge t --mt test_regression --mc ERC721
35-
coverage :; forge coverage --no-match-test "testLoad|invariant"
36-
test-invariant-erc20-precision :; ./tests/forge/invariants/test-invariant-erc20-precision.sh
37-
test-invariant-erc20-buckets :; ./tests/forge/invariants/test-invariant-erc20-buckets.sh
38-
test-invariant-erc721-buckets :; ./tests/forge/invariants/test-invariant-erc721-buckets.sh
19+
# Unit Tests
20+
test :; forge test --no-match-test ${TEST_EXCLUDES} --nmc ${CONTRACT_EXCLUDES} # --ffi # enable if you need the `ffi` cheat code on HEVM
21+
test-with-gas-report :; forge test --no-match-test ${TEST_EXCLUDES} --nmc ${CONTRACT_EXCLUDES} --gas-report # --ffi # enable if you need the `ffi` cheat code on HEVM
22+
23+
# Gas Load Tests
24+
test-load :; forge test --match-test testLoad --gas-report
25+
26+
# Invariant Tests
27+
test-invariant-all :; forge t --mt invariant --nmc ${CONTRACT_EXCLUDES}
28+
test-invariant-erc20 :; forge t --mt invariant --nmc ${CONTRACT_EXCLUDES} --mc ERC20
29+
test-invariant-erc721 :; forge t --mt invariant --nmc ${CONTRACT_EXCLUDES} --mc ERC721
30+
test-invariant :; forge t --mt ${MT} --nmc RegressionTest
31+
test-invariant-erc20-precision :; ./tests/forge/invariants/test-invariant-erc20-precision.sh
32+
test-invariant-erc721-precision :; ./tests/forge/invariants/test-invariant-erc721-precision.sh
33+
test-invariant-erc20-buckets :; ./tests/forge/invariants/test-invariant-erc20-buckets.sh
34+
test-invariant-erc721-buckets :; ./tests/forge/invariants/test-invariant-erc721-buckets.sh
35+
36+
# Real-world simulation scenarios
37+
test-rw-simulation-erc20 :; FOUNDRY_INVARIANT_SHRINK_SEQUENCE=false RUST_LOG=forge=info,foundry_evm=info,ethers=info forge t --mt invariant_all_erc20 --mc RealWorldScenario
38+
test-rw-simulation-erc721 :; FOUNDRY_INVARIANT_SHRINK_SEQUENCE=false RUST_LOG=forge=info,foundry_evm=info,ethers=info forge t --mt invariant_all_erc721 --mc RealWorldScenario
39+
40+
# Liquidations load test scenarios
41+
test-liquidations-load-erc20 :; FOUNDRY_INVARIANT_SHRINK_SEQUENCE=false RUST_LOG=forge=info,foundry_evm=info,ethers=info forge t --mt invariant_all_erc20 --mc PanicExitERC20
42+
test-liquidations-load-erc721 :; FOUNDRY_INVARIANT_SHRINK_SEQUENCE=false RUST_LOG=forge=info,foundry_evm=info,ethers=info forge t --mt invariant_all_erc721 --mc PanicExitERC721
43+
44+
# Swap tokens load test scenarios
45+
test-swap-load-erc20 :; FOUNDRY_INVARIANT_SHRINK_SEQUENCE=false RUST_LOG=forge=info,foundry_evm=info,ethers=info forge t --mt invariant_all_erc20 --mc TradingERC20
46+
47+
# Regression Tests
48+
test-regression-all : test-regression-erc20 test-regression-erc721 test-regression-prototech
49+
test-regression-erc20 :; forge t --mt test_regression --mc ERC20 --nmc "RealWorldRegression|Prototech"
50+
test-regression-erc721 :; forge t --mt test_regression --mc ERC721 --nmc "RealWorldRegression|Prototech"
51+
test-regression-prototech :; forge t --mt test_regression --mc Prototech
52+
test-regression-rw :; forge t --mt test_regression --mc RealWorldRegression
53+
test-regression :; forge t --mt ${MT}
54+
55+
# Coverage
56+
coverage :; forge coverage --no-match-test "testLoad|invariant"
57+
58+
# Certora
59+
certora-erc721-permit :; $(if $(CERTORAKEY),, @echo "set certora key"; exit 1;) PATH=~/.solc-select/artifacts/solc-0.8.14:~/.solc-select/artifacts:${PATH} certoraRun --solc_map PermitERC721Harness=solc-0.8.14,Auxiliar=solc-0.8.14,SignerMock=solc-0.8.14 --optimize_map PermitERC721Harness=500,Auxiliar=0,SignerMock=0 --rule_sanity basic certora/harness/PermitERC721Harness.sol certora/Auxiliar.sol certora/SignerMock.sol --verify PermitERC721Harness:certora/PermitERC721.spec --multi_assert_check $(if $(short), --short_output,)
3960

4061
# Generate Gas Snapshots
4162
snapshot :; forge clean && forge snapshot

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The Ajna protocol is a non-custodial, peer-to-peer, permissionless lending, borr
1010
- The following types of tokens are incompatible with Ajna, and no countermeasures exist to explicitly prevent creating a pool with such tokens, actors should use them at their own risk:
1111
- NFT and fungible tokens which charge a fee on transfer.
1212
- Fungible tokens whose balance rebases.
13-
- Fungible tokens with more than 18 decimals or 0 decimals.
13+
- Fungible tokens with more than 18 decimals or 0 decimals, whose `decimals()` function does not return a constant value, or which do not implement the optional [decimals()](https://eips.ethereum.org/EIPS/eip-20#decimals) function.
1414
- Borrowers cannot draw debt from a pool in the same block as when the pool was created.
1515
- With the exception of quantized prices, pool inputs and most accumulators are not explicitly limited. The pool will stop functioning when the bounds of a `uint256` need to be exceeded to process a request.
1616

@@ -130,9 +130,9 @@ bash ./check-code-coverage.sh
130130
```bash
131131
pip install slither-analyzer
132132
```
133-
- Make sure the default `solc` version is set to the same version as contracts (currently 0.8.14). This can be done by installing and using `solc-select`:
133+
- Make sure the default `solc` version is set to the same version as contracts (currently 0.8.18). This can be done by installing and using `solc-select`:
134134
```bash
135-
pip install solc-select && solc-select install 0.8.14 && solc-select use 0.8.14
135+
pip install solc-select && solc-select install 0.8.18 && solc-select use 0.8.18
136136
```
137137
- Run `analyze`
138138

brownie-config.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ autofetch_sources: True
1414
# path remapping to support imports from GitHub/NPM
1515
compiler:
1616
solc:
17-
version: 0.8.14
17+
version: 0.8.18
1818
optimizer:
1919
enabled: true
20-
runs: 500
20+
runs: 0
2121
remappings:
2222
- "@ds-math=lib/ds-math/src/"
2323
- "@openzeppelin/contracts=lib/openzeppelin-contracts/contracts"

certora/Auxiliar.sol

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
pragma solidity 0.8.14;
2+
3+
contract Auxiliar {
4+
function computeDigest(
5+
bytes32 domain_separator,
6+
bytes32 permit_typehash,
7+
address spender,
8+
uint256 tokenId,
9+
uint256 nonce,
10+
uint256 deadline
11+
) public pure returns (bytes32 digest){
12+
digest =
13+
keccak256(
14+
abi.encodePacked(
15+
"\x19\x01",
16+
domain_separator,
17+
keccak256(
18+
abi.encode(
19+
permit_typehash,
20+
spender,
21+
tokenId,
22+
nonce,
23+
deadline
24+
))
25+
));
26+
}
27+
28+
function call_ecrecover(
29+
bytes32 digest,
30+
uint8 v,
31+
bytes32 r,
32+
bytes32 s
33+
) public pure returns (address signer) {
34+
signer = ecrecover(digest, v, r, s);
35+
}
36+
37+
function signatureToVRS(bytes memory signature) public returns (uint8 v, bytes32 r, bytes32 s) {
38+
if (signature.length == 65) {
39+
assembly {
40+
r := mload(add(signature, 0x20))
41+
s := mload(add(signature, 0x40))
42+
v := byte(0, mload(add(signature, 0x60)))
43+
}
44+
}
45+
}
46+
47+
function isContract(address owner) public returns (bool) {
48+
return owner.code.length > 0;
49+
}
50+
}

certora/PermitERC721.spec

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// PermitERC721.spec
2+
3+
using Auxiliar as aux
4+
using SignerMock as signer
5+
6+
methods {
7+
getApproved(uint256) returns (address) envfree
8+
ownerOf(uint256) returns (address) envfree
9+
nonces(uint256) returns (uint96) envfree
10+
DOMAIN_SEPARATOR() returns (bytes32) envfree
11+
PERMIT_TYPEHASH() returns (bytes32) envfree
12+
aux.call_ecrecover(bytes32, uint8, bytes32, bytes32) returns (address) envfree
13+
aux.computeDigest(bytes32, bytes32, address, uint256, uint256, uint256) returns (bytes32) envfree
14+
aux.signatureToVRS(bytes) returns (uint8, bytes32, bytes32) envfree
15+
aux.isContract(address) returns (bool) envfree
16+
isValidSignature(bytes32, bytes) returns (bytes4) => DISPATCHER(true)
17+
}
18+
19+
// Verify that allowance behaves correctly on permit
20+
rule permit(address spender, address tokenId, uint256 deadline, bytes signature) {
21+
env e;
22+
23+
uint8 v; bytes32 r; bytes32 s;
24+
v, r, s = aux.signatureToVRS(signature);
25+
26+
permit(e, spender, tokenId, deadline, v, r, s);
27+
28+
assert(getApproved(tokenId) == spender, "assert1 failed");
29+
}
30+
31+
// Verify revert rules on permit
32+
rule permit_revert(address spender, uint256 tokenId, uint256 deadline, bytes signature) {
33+
env e;
34+
35+
uint8 v; bytes32 r; bytes32 s;
36+
v, r, s = aux.signatureToVRS(signature);
37+
38+
uint256 tokenIdNonce = nonces(tokenId);
39+
address owner = ownerOf(tokenId);
40+
41+
bytes32 digest = aux.computeDigest(
42+
DOMAIN_SEPARATOR(),
43+
PERMIT_TYPEHASH(),
44+
spender,
45+
tokenId,
46+
tokenIdNonce,
47+
deadline
48+
);
49+
50+
address ownerRecover = aux.call_ecrecover(digest, v, r, s);
51+
bytes32 returnedSig = signer.isValidSignature(e, digest, signature);
52+
bool isContract = aux.isContract(owner);
53+
54+
permit@withrevert(e, spender, tokenId, deadline, v, r, s);
55+
56+
bool revert1 = e.msg.value > 0;
57+
bool revert2 = e.block.timestamp > deadline;
58+
bool revert3 = owner == 0;
59+
bool revert4 = owner == spender;
60+
bool revert5 = isContract && returnedSig != 0x1626ba7e00000000000000000000000000000000000000000000000000000000;
61+
bool revert6 = !isContract && ownerRecover == 0;
62+
bool revert7 = !isContract && ownerRecover != owner;
63+
bool revert8 = tokenIdNonce == max_uint96;
64+
65+
assert(revert1 => lastReverted, "revert1 failed");
66+
assert(revert2 => lastReverted, "revert2 failed");
67+
assert(revert3 => lastReverted, "revert3 failed");
68+
assert(revert4 => lastReverted, "revert4 failed");
69+
assert(revert5 => lastReverted, "revert5 failed");
70+
assert(revert6 => lastReverted, "revert6 failed");
71+
assert(revert7 => lastReverted, "revert7 failed");
72+
assert(revert8 => lastReverted, "revert8 failed");
73+
74+
assert(lastReverted => revert1 || revert2 || revert3 ||
75+
revert4 || revert5 || revert6 ||
76+
revert7 || revert8, "Revert rules are not covering all the cases");
77+
}

certora/SignerMock.sol

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// SPDX-License-Identifier: AGPL-3.0-or-later
2+
3+
pragma solidity ^0.8.14;
4+
5+
contract SignerMock {
6+
bytes32 sig;
7+
8+
function isValidSignature(bytes32, bytes memory) external view returns (bytes32) {
9+
return sig;
10+
}
11+
}
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
pragma solidity 0.8.14;
2+
3+
import { PermitERC721 } from '../../src/base/PermitERC721.sol';
4+
5+
contract PermitERC721Harness is PermitERC721 {
6+
7+
// overrides internal nonces
8+
mapping(uint256 => uint96) public nonces;
9+
10+
constructor() PermitERC721("Ajna Positions NFT-V1", "AJNA-V1-POS", "1") public {}
11+
12+
// PostionManager.sol
13+
function _getAndIncrementNonce(
14+
uint256 tokenId_
15+
) internal override returns (uint256) {
16+
return uint256(nonces[tokenId_]++);
17+
}
18+
}

docs/Functions.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,9 @@
150150
- increment auctions count accumulator
151151
- increment auctions.totalBondEscrowed accumulator
152152
- updates auction queue state
153-
- _updateKicker():
153+
- _updateEscrowedBonds():
154154
- update locked and claimable kicker accumulators
155+
- update global escrow accumulator
155156
- Loans.remove():
156157
- delete borrower from indices => borrower address mapping
157158
- remove loan from loans array

foundry.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ block_number = 16_295_000
1818
fork_block_number = 16_295_000
1919
rpc_storage_caching = { chains = ["mainnet"], endpoints = "all" }
2020
optimizer = true
21-
optimizer_runs = 500
21+
optimizer_runs = 0
22+
fs_permissions = [{ access = "read-write", path = "./"}]
2223

2324
[fuzz]
2425
runs = 300

lib/forge-std

script/deploy.s.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: MIT
2-
pragma solidity 0.8.14;
2+
pragma solidity 0.8.18;
33

44
import { Script } from "forge-std/Script.sol";
55
import "forge-std/console.sol";

0 commit comments

Comments
 (0)