Skip to content

Commit 49caffa

Browse files
authored
feat: Add token pool for GHO based on CCIP 1.5.1 (#20)
* feat: setup deps * feat: migrate GHO token pools to v1.5.1 * fix: ci * feat: certora formal verification * chore: rm whitespace, ref comment * doc: rm unneeded remanant * feat: upd currentBridged on liquidity transfers * fix: certora add invariant to withdraw, provide, and transfer liq * feat: add transferLiquidity on burnMintTokenPool * doc: upd transferLiquidity for burnMint * feat: rm token decimal check in constructor * feat: setCurrentBridgedAmount, revert bridgeAmount changes on {transfer,provide,withdraw}Liquidity * chore: fix doc * feat: mintAndTransferLiquidity * chore: reorder imports to minmize diff * feat: `directMint` & `directBurn` to migrate facilitators * chore: expand on test scenario * chore: move revert to base test such that it can be inherited * test: use setter over vm.store * test: revert `InsufficientLiquidity` on `transferLiquidity` * test: rm unneeded mockCall * test: migrateLiquidity on remote pool * doc: update comments * chore: cleanup test * new: introduce __gap on UpgradeableTokenPool * doc: update doc & diffs for storage gap * chore: upd directMint test * doc: upd diff and doc for __gap * doc: fix base diff doc
1 parent 6f54175 commit 49caffa

33 files changed

+6216
-4
lines changed

.github/workflows/certora.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: certora
2+
3+
on: push
4+
5+
jobs:
6+
verify:
7+
runs-on: ubuntu-latest
8+
9+
steps:
10+
- uses: actions/checkout@v2
11+
with:
12+
submodules: recursive
13+
14+
- name: Install python
15+
uses: actions/setup-python@v2
16+
with: { python-version: 3.9 }
17+
18+
- name: Install java
19+
uses: actions/setup-java@v1
20+
with: { java-version: '11', java-package: jre }
21+
22+
- name: Install certora cli
23+
run: pip install certora-cli==7.20.3
24+
25+
- name: Install solc
26+
run: |
27+
wget https://github.com/ethereum/solidity/releases/download/v0.8.24/solc-static-linux
28+
chmod +x solc-static-linux
29+
sudo mv solc-static-linux /usr/local/bin/solc8.24
30+
31+
- name: Verify rule ${{ matrix.rule }}
32+
run: |
33+
echo "key length" ${#CERTORAKEY}
34+
certoraRun certora/confs/${{ matrix.rule }}
35+
env:
36+
CERTORAKEY: ${{ secrets.CERTORAKEY }}
37+
38+
strategy:
39+
fail-fast: false
40+
max-parallel: 16
41+
matrix:
42+
rule:
43+
- ccip.conf

.github/workflows/tests.yml

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: solidity
2+
3+
on: push
4+
5+
env:
6+
FOUNDRY_PROFILE: ci
7+
8+
jobs:
9+
tests:
10+
strategy:
11+
fail-fast: false
12+
matrix:
13+
product: [ccip]
14+
name: Foundry Tests ${{ matrix.product }}
15+
# See https://github.com/foundry-rs/foundry/issues/3827
16+
runs-on: ubuntu-22.04
17+
18+
# The if statements for steps after checkout repo is workaround for
19+
# passing required check for PRs that don't have filtered changes.
20+
steps:
21+
- name: Checkout the repo
22+
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
23+
with:
24+
submodules: recursive
25+
26+
# Only needed because we use the NPM versions of packages
27+
# and not native Foundry. This is to make sure the dependencies
28+
# stay in sync.
29+
- name: Setup NodeJS
30+
uses: ./.github/actions/setup-nodejs
31+
32+
- name: Install Foundry
33+
uses: foundry-rs/foundry-toolchain@v1
34+
with:
35+
# Has to match the `make foundry` version.
36+
version: nightly-2cb875799419c907cc3709e586ece2559e6b340e
37+
38+
- name: Run Forge build
39+
run: |
40+
forge --version
41+
forge build
42+
id: build
43+
working-directory: contracts
44+
env:
45+
FOUNDRY_PROFILE: ${{ matrix.product }}
46+
47+
- name: Run Forge tests
48+
run: |
49+
forge test -vvv
50+
id: test
51+
working-directory: contracts
52+
env:
53+
FOUNDRY_PROFILE: ${{ matrix.product }}
54+
55+
- name: Run Forge snapshot
56+
if: ${{ !contains(fromJson('["vrf"]'), matrix.product) && !contains(fromJson('["automation"]'), matrix.product) && needs.changes.outputs.changes == 'true' }}
57+
run: |
58+
forge snapshot --nmt "testFuzz_\w{1,}?" --check gas-snapshots/${{ matrix.product }}.gas-snapshot
59+
id: snapshot
60+
working-directory: contracts
61+
env:
62+
FOUNDRY_PROFILE: ${{ matrix.product }}

.gitmodules

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[submodule "contracts/foundry-lib/gho-core"]
2+
path = contracts/foundry-lib/gho-core
3+
url = https://github.com/aave/gho-core
4+
[submodule "contracts/foundry-lib/solidity-utils"]
5+
path = contracts/foundry-lib/solidity-utils
6+
url = https://github.com/bgd-labs/solidity-utils

certora/Makefile

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
default: help
2+
3+
PATCH = applyHarness.patch
4+
CONTRACTS_DIR = ../contracts
5+
MUNGED_DIR = munged
6+
7+
help:
8+
@echo "usage:"
9+
@echo " make clean: remove all generated files (those ignored by git)"
10+
@echo " make $(MUNGED_DIR): create $(MUNGED_DIR) directory by applying the patch file to $(CONTRACTS_DIR)"
11+
@echo " make record: record a new patch file capturing the differences between $(CONTRACTS_DIR) and $(MUNGED_DIR)"
12+
13+
munged: $(wildcard $(CONTRACTS_DIR)/*.sol) $(PATCH)
14+
rm -rf $@
15+
cp -r $(CONTRACTS_DIR) $@
16+
patch -p0 -d $@ < $(PATCH)
17+
18+
record:
19+
diff -ruN $(CONTRACTS_DIR) $(MUNGED_DIR) | sed 's+\.\./contracts/++g' | sed 's+munged/++g' > $(PATCH)
20+
21+
clean:
22+
git clean -fdX
23+
touch $(PATCH)
24+

certora/confs/ccip.conf

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"files": [
3+
"contracts/src/v0.8/ccip/pools/GHO/UpgradeableLockReleaseTokenPool.sol",
4+
"certora/harness/SimpleERC20.sol"
5+
],
6+
"packages": [
7+
"solidity-utils/=contracts/foundry-lib/solidity-utils/src/"
8+
],
9+
"link": [
10+
"UpgradeableLockReleaseTokenPool:i_token=SimpleERC20"
11+
],
12+
"optimistic_loop": true,
13+
"optimistic_hashing": true,
14+
"process": "emv",
15+
"prover_args": ["-depth 10","-mediumTimeout 700"],
16+
"smt_timeout": "600",
17+
"solc": "solc8.24",
18+
"verify": "UpgradeableLockReleaseTokenPool:certora/specs/ccip.spec",
19+
"rule_sanity": "basic",
20+
"msg": "CCIP"
21+
}
22+

certora/harness/SimpleERC20.sol

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// SPDX-License-Identifier: agpl-3.0
2+
pragma solidity ^0.8.0;
3+
4+
import {IERC20} from "../../contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol";
5+
6+
/**
7+
A simple ERC implementation used as the underlying_asset for the verification process.
8+
*/
9+
contract SimpleERC20 is IERC20 {
10+
uint256 t;
11+
mapping(address => uint256) b;
12+
mapping(address => mapping(address => uint256)) a;
13+
14+
function add(uint a, uint b) internal pure returns (uint256) {
15+
uint c = a + b;
16+
require(c >= a);
17+
return c;
18+
}
19+
20+
function sub(uint a, uint b) internal pure returns (uint256) {
21+
require(a >= b);
22+
return a - b;
23+
}
24+
25+
function totalSupply() external view override returns (uint256) {
26+
return t;
27+
}
28+
29+
function balanceOf(address account) external view override returns (uint256) {
30+
return b[account];
31+
}
32+
33+
function transfer(address recipient, uint256 amount) external override returns (bool) {
34+
b[msg.sender] = sub(b[msg.sender], amount);
35+
b[recipient] = add(b[recipient], amount);
36+
return true;
37+
}
38+
39+
function allowance(address owner, address spender) external view override returns (uint256) {
40+
return a[owner][spender];
41+
}
42+
43+
function approve(address spender, uint256 amount) external override returns (bool) {
44+
a[msg.sender][spender] = amount;
45+
return true;
46+
}
47+
48+
function transferFrom(
49+
address sender,
50+
address recipient,
51+
uint256 amount
52+
) external override returns (bool) {
53+
b[sender] = sub(b[sender], amount);
54+
b[recipient] = add(b[recipient], amount);
55+
a[sender][msg.sender] = sub(a[sender][msg.sender], amount);
56+
return true;
57+
}
58+
}

certora/munged/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*
2+
!.gitignore

certora/specs/ccip.spec

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
This is a Specification File for Smart Contract Verification with the Certora Prover.
3+
Contract name: UpgradeableLockReleaseTokenPool
4+
*/
5+
6+
using SimpleERC20 as erc20;
7+
8+
methods {
9+
function getCurrentBridgedAmount() external returns (uint256) envfree;
10+
function getBridgeLimit() external returns (uint256) envfree;
11+
function owner() external returns (address) envfree;
12+
}
13+
14+
15+
rule sanity {
16+
env e;
17+
calldataarg arg;
18+
method f;
19+
f(e, arg);
20+
satisfy true;
21+
}
22+
23+
24+
25+
/* ==============================================================================
26+
invariant: currentBridge_LEQ_bridgeLimit.
27+
Description: The value of s_currentBridged is LEQ than the value of s_bridgeLimit.
28+
Note: this may be violated if one calls to setBridgeLimit(newBridgeLimit) with
29+
newBridgeLimit < s_currentBridged.
30+
============================================================================*/
31+
invariant currentBridge_LEQ_bridgeLimit()
32+
getCurrentBridgedAmount() <= getBridgeLimit()
33+
filtered { f ->
34+
!f.isView &&
35+
f.selector != sig:setBridgeLimit(uint256).selector &&
36+
f.selector != sig:setCurrentBridgedAmount(uint256).selector}
37+
{
38+
preserved initialize(address owner, address[] allowlist, address router, uint256 bridgeLimit) with (env e2) {
39+
require getCurrentBridgedAmount()==0;
40+
}
41+
}
42+
43+
44+
/* ==============================================================================
45+
rule: withdrawLiquidity_correctness
46+
description: The rule checks that the balance of the contract is as expected.
47+
============================================================================*/
48+
rule withdrawLiquidity_correctness(env e) {
49+
uint256 amount;
50+
51+
require e.msg.sender != currentContract;
52+
uint256 bal_before = erc20.balanceOf(e, currentContract);
53+
withdrawLiquidity(e, amount);
54+
uint256 bal_after = erc20.balanceOf(e, currentContract);
55+
56+
assert (to_mathint(bal_after) == bal_before - amount);
57+
}
58+
59+
60+
/* ==============================================================================
61+
rule: provideLiquidity_correctness
62+
description: The rule checks that the balance of the contract is as expected.
63+
============================================================================*/
64+
rule provideLiquidity_correctness(env e) {
65+
uint256 amount;
66+
67+
require e.msg.sender != currentContract;
68+
uint256 bal_before = erc20.balanceOf(e, currentContract);
69+
provideLiquidity(e, amount);
70+
uint256 bal_after = erc20.balanceOf(e, currentContract);
71+
72+
assert (to_mathint(bal_after) == bal_before + amount);
73+
}
74+
75+
definition filterSetter(method f) returns bool = f.selector != sig:setCurrentBridgedAmount(uint256).selector;
76+
77+
/* ==============================================================================
78+
rule: only_lockOrBurn_can_increase_currentBridged
79+
============================================================================*/
80+
rule only_lockOrBurn_can_increase_currentBridged(env e, method f)
81+
filtered { f -> filterSetter(f) }
82+
{
83+
calldataarg args;
84+
85+
uint256 curr_bridge_before = getCurrentBridgedAmount();
86+
f (e,args);
87+
uint256 curr_bridge_after = getCurrentBridgedAmount();
88+
89+
assert
90+
curr_bridge_after > curr_bridge_before =>
91+
f.selector==sig:lockOrBurn(Pool.LockOrBurnInV1).selector;
92+
}
93+
94+
95+
/* ==============================================================================
96+
rule: only_releaseOrMint_currentBridged
97+
============================================================================*/
98+
rule only_releaseOrMint_currentBridged(env e, method f)
99+
filtered { f -> filterSetter(f) }
100+
{
101+
calldataarg args;
102+
103+
uint256 curr_bridge_before = getCurrentBridgedAmount();
104+
f (e,args);
105+
uint256 curr_bridge_after = getCurrentBridgedAmount();
106+
107+
assert
108+
curr_bridge_after < curr_bridge_before =>
109+
f.selector==sig:releaseOrMint(Pool.ReleaseOrMintInV1).selector;
110+
}
111+
112+
113+
/* ==============================================================================
114+
rule: only_bridgeLimitAdmin_or_owner_can_call_setBridgeLimit
115+
============================================================================*/
116+
rule only_bridgeLimitAdmin_or_owner_can_call_setBridgeLimit(env e) {
117+
uint256 newBridgeLimit;
118+
119+
setBridgeLimit(e, newBridgeLimit);
120+
121+
assert e.msg.sender==getBridgeLimitAdmin(e) || e.msg.sender==owner();
122+
}
123+
124+
/* ==============================================================================
125+
rule: only_owner_can_call_setCurrentBridgedAmount
126+
============================================================================*/
127+
rule only_owner_can_call_setCurrentBridgedAmount(env e) {
128+
uint256 newBridgedAmount;
129+
130+
setCurrentBridgedAmount(e, newBridgedAmount);
131+
132+
assert e.msg.sender==owner();
133+
}

contracts/foundry-lib/gho-core

Submodule gho-core added at a8d05e6
Submodule solidity-utils added at 9d4d041

0 commit comments

Comments
 (0)