From 0f582d768e62822ad527e22a64ced53aef299534 Mon Sep 17 00:00:00 2001 From: Michael M <91594326+MichaelMorami@users.noreply.github.com> Date: Wed, 25 May 2022 11:33:31 +0300 Subject: [PATCH 01/58] certora folder with complexit check + git ignore --- .gitignore | 8 ++- certora/harness/DummyERC20A.sol | 5 ++ certora/harness/DummyERC20B.sol | 5 ++ certora/harness/DummyERC20Impl.sol | 57 ++++++++++++++++ certora/scripts/run.sh | 7 ++ certora/specs/complexity.spec | 103 +++++++++++++++++++++++++++++ certora/specs/erc20.spec | 12 ++++ resource_errors.json | 3 + 8 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 certora/harness/DummyERC20A.sol create mode 100644 certora/harness/DummyERC20B.sol create mode 100644 certora/harness/DummyERC20Impl.sol create mode 100755 certora/scripts/run.sh create mode 100644 certora/specs/complexity.spec create mode 100644 certora/specs/erc20.spec create mode 100644 resource_errors.json diff --git a/.gitignore b/.gitignore index 74a4d0c..a2edf0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ cache/ out/ -.idea \ No newline at end of file +.idea + +# certora +.certora* +.certora*.json +**.last_conf* +certora-logs diff --git a/certora/harness/DummyERC20A.sol b/certora/harness/DummyERC20A.sol new file mode 100644 index 0000000..188b926 --- /dev/null +++ b/certora/harness/DummyERC20A.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.0; +import "./DummyERC20Impl.sol"; + +contract DummyERC20A is DummyERC20Impl {} \ No newline at end of file diff --git a/certora/harness/DummyERC20B.sol b/certora/harness/DummyERC20B.sol new file mode 100644 index 0000000..0f97f1e --- /dev/null +++ b/certora/harness/DummyERC20B.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.0; +import "./DummyERC20Impl.sol"; + +contract DummyERC20B is DummyERC20Impl {} \ No newline at end of file diff --git a/certora/harness/DummyERC20Impl.sol b/certora/harness/DummyERC20Impl.sol new file mode 100644 index 0000000..42e7f23 --- /dev/null +++ b/certora/harness/DummyERC20Impl.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.0; + +// with mint +contract DummyERC20Impl { + uint256 t; + mapping (address => uint256) b; + mapping (address => mapping (address => uint256)) a; + + string public name; + string public symbol; + uint public decimals; + + function myAddress() public returns (address) { + return address(this); + } + + function add(uint a, uint b) internal pure returns (uint256) { + uint c = a +b; + require (c >= a); + return c; + } + function sub(uint a, uint b) internal pure returns (uint256) { + require (a>=b); + return a-b; + } + + function totalSupply() external view returns (uint256) { + return t; + } + function balanceOf(address account) external view returns (uint256) { + return b[account]; + } + function transfer(address recipient, uint256 amount) external returns (bool) { + b[msg.sender] = sub(b[msg.sender], amount); + b[recipient] = add(b[recipient], amount); + return true; + } + function allowance(address owner, address spender) external view returns (uint256) { + return a[owner][spender]; + } + function approve(address spender, uint256 amount) external returns (bool) { + a[msg.sender][spender] = amount; + return true; + } + + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool) { + b[sender] = sub(b[sender], amount); + b[recipient] = add(b[recipient], amount); + a[sender][msg.sender] = sub(a[sender][msg.sender], amount); + return true; + } +} \ No newline at end of file diff --git a/certora/scripts/run.sh b/certora/scripts/run.sh new file mode 100755 index 0000000..03cffd3 --- /dev/null +++ b/certora/scripts/run.sh @@ -0,0 +1,7 @@ +certoraRun src/AaveTokenV3.sol:AaveTokenV3 \ + --verify AaveTokenV3:certora/specs/complexity.spec \ + --solc solc8.13 \ + --optimistic_loop \ + --staging \ + --msg "AaveTokenV3 complexity check" + \ No newline at end of file diff --git a/certora/specs/complexity.spec b/certora/specs/complexity.spec new file mode 100644 index 0000000..0645e07 --- /dev/null +++ b/certora/specs/complexity.spec @@ -0,0 +1,103 @@ +import "erc20.spec" + +rule sanity(method f) +{ + env e; + calldataarg args; + f(e,args); + assert false; +} + + +/* +This rule find which functions never reverts. + +*/ + + +rule noRevert(method f) +description "$f has reverting paths" +{ + env e; + calldataarg arg; + require e.msg.value == 0; + f@withrevert(e, arg); + assert !lastReverted, "${f.selector} can revert"; +} + + +rule alwaysRevert(method f) +description "$f has reverting paths" +{ + env e; + calldataarg arg; + f@withrevert(e, arg); + assert lastReverted, "${f.selector} succeeds"; +} + + +/* +This rule find which functions that can be called, may fail due to someone else calling a function right before. + +This is n expensive rule - might fail on the demo site on big contracts +*/ + +rule simpleFrontRunning(method f, address privileged) filtered { f-> !f.isView } +description "$f can no longer be called after it had been called by someone else" +{ + env e1; + calldataarg arg; + require e1.msg.sender == privileged; + + storage initialStorage = lastStorage; + f(e1, arg); + bool firstSucceeded = !lastReverted; + + env e2; + calldataarg arg2; + require e2.msg.sender != e1.msg.sender; + f(e2, arg2) at initialStorage; + f@withrevert(e1, arg); + bool succeeded = !lastReverted; + + assert succeeded, "${f.selector} can be not be called if was called by someone else"; +} + + +/* +This rule find which functions are privileged. +A function is privileged if there is only one address that can call it. + +The rules finds this by finding which functions can be called by two different users. + +*/ + + +rule privilegedOperation(method f, address privileged) +description "$f can be called by more than one user without reverting" +{ + env e1; + calldataarg arg; + require e1.msg.sender == privileged; + + storage initialStorage = lastStorage; + f@withrevert(e1, arg); // privileged succeeds executing candidate privileged operation. + bool firstSucceeded = !lastReverted; + + env e2; + calldataarg arg2; + require e2.msg.sender != privileged; + f@withrevert(e2, arg2) at initialStorage; // unprivileged + bool secondSucceeded = !lastReverted; + + assert !(firstSucceeded && secondSucceeded), "${f.selector} can be called by both ${e1.msg.sender} and ${e2.msg.sender}, so it is not privileged"; +} + +rule whoChangedBalanceOf(method f, address u) { + env eB; + env eF; + calldataarg args; + uint256 before = balanceOf(eB, u); + f(eF,args); + assert balanceOf(eB, u) == before, "balanceOf changed"; +} \ No newline at end of file diff --git a/certora/specs/erc20.spec b/certora/specs/erc20.spec new file mode 100644 index 0000000..b12fec1 --- /dev/null +++ b/certora/specs/erc20.spec @@ -0,0 +1,12 @@ +// erc20 methods +methods { + name() returns (string) => DISPATCHER(true) + symbol() returns (string) => DISPATCHER(true) + decimals() returns (string) => DISPATCHER(true) + totalSupply() returns (uint256) => DISPATCHER(true) + balanceOf(address) returns (uint256) => DISPATCHER(true) + allowance(address,address) returns (uint) => DISPATCHER(true) + approve(address,uint256) returns (bool) => DISPATCHER(true) + transfer(address,uint256) returns (bool) => DISPATCHER(true) + transferFrom(address,address,uint256) returns (bool) => DISPATCHER(true) +} diff --git a/resource_errors.json b/resource_errors.json new file mode 100644 index 0000000..d9bd792 --- /dev/null +++ b/resource_errors.json @@ -0,0 +1,3 @@ +{ + "topics": [] +} \ No newline at end of file From 1e657e31df78a0d49b9337c01abcc038b5d25b36 Mon Sep 17 00:00:00 2001 From: Michael M <91594326+MichaelMorami@users.noreply.github.com> Date: Wed, 25 May 2022 11:33:31 +0300 Subject: [PATCH 02/58] certora folder with complexit check + git ignore --- .gitignore | 8 ++- certora/harness/DummyERC20A.sol | 5 ++ certora/harness/DummyERC20B.sol | 5 ++ certora/harness/DummyERC20Impl.sol | 57 ++++++++++++++++ certora/scripts/run.sh | 7 ++ certora/specs/complexity.spec | 103 +++++++++++++++++++++++++++++ certora/specs/erc20.spec | 12 ++++ resource_errors.json | 3 + 8 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 certora/harness/DummyERC20A.sol create mode 100644 certora/harness/DummyERC20B.sol create mode 100644 certora/harness/DummyERC20Impl.sol create mode 100755 certora/scripts/run.sh create mode 100644 certora/specs/complexity.spec create mode 100644 certora/specs/erc20.spec create mode 100644 resource_errors.json diff --git a/.gitignore b/.gitignore index 74a4d0c..a2edf0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,9 @@ cache/ out/ -.idea \ No newline at end of file +.idea + +# certora +.certora* +.certora*.json +**.last_conf* +certora-logs diff --git a/certora/harness/DummyERC20A.sol b/certora/harness/DummyERC20A.sol new file mode 100644 index 0000000..188b926 --- /dev/null +++ b/certora/harness/DummyERC20A.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.0; +import "./DummyERC20Impl.sol"; + +contract DummyERC20A is DummyERC20Impl {} \ No newline at end of file diff --git a/certora/harness/DummyERC20B.sol b/certora/harness/DummyERC20B.sol new file mode 100644 index 0000000..0f97f1e --- /dev/null +++ b/certora/harness/DummyERC20B.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.0; +import "./DummyERC20Impl.sol"; + +contract DummyERC20B is DummyERC20Impl {} \ No newline at end of file diff --git a/certora/harness/DummyERC20Impl.sol b/certora/harness/DummyERC20Impl.sol new file mode 100644 index 0000000..42e7f23 --- /dev/null +++ b/certora/harness/DummyERC20Impl.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.0; + +// with mint +contract DummyERC20Impl { + uint256 t; + mapping (address => uint256) b; + mapping (address => mapping (address => uint256)) a; + + string public name; + string public symbol; + uint public decimals; + + function myAddress() public returns (address) { + return address(this); + } + + function add(uint a, uint b) internal pure returns (uint256) { + uint c = a +b; + require (c >= a); + return c; + } + function sub(uint a, uint b) internal pure returns (uint256) { + require (a>=b); + return a-b; + } + + function totalSupply() external view returns (uint256) { + return t; + } + function balanceOf(address account) external view returns (uint256) { + return b[account]; + } + function transfer(address recipient, uint256 amount) external returns (bool) { + b[msg.sender] = sub(b[msg.sender], amount); + b[recipient] = add(b[recipient], amount); + return true; + } + function allowance(address owner, address spender) external view returns (uint256) { + return a[owner][spender]; + } + function approve(address spender, uint256 amount) external returns (bool) { + a[msg.sender][spender] = amount; + return true; + } + + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool) { + b[sender] = sub(b[sender], amount); + b[recipient] = add(b[recipient], amount); + a[sender][msg.sender] = sub(a[sender][msg.sender], amount); + return true; + } +} \ No newline at end of file diff --git a/certora/scripts/run.sh b/certora/scripts/run.sh new file mode 100755 index 0000000..03cffd3 --- /dev/null +++ b/certora/scripts/run.sh @@ -0,0 +1,7 @@ +certoraRun src/AaveTokenV3.sol:AaveTokenV3 \ + --verify AaveTokenV3:certora/specs/complexity.spec \ + --solc solc8.13 \ + --optimistic_loop \ + --staging \ + --msg "AaveTokenV3 complexity check" + \ No newline at end of file diff --git a/certora/specs/complexity.spec b/certora/specs/complexity.spec new file mode 100644 index 0000000..0645e07 --- /dev/null +++ b/certora/specs/complexity.spec @@ -0,0 +1,103 @@ +import "erc20.spec" + +rule sanity(method f) +{ + env e; + calldataarg args; + f(e,args); + assert false; +} + + +/* +This rule find which functions never reverts. + +*/ + + +rule noRevert(method f) +description "$f has reverting paths" +{ + env e; + calldataarg arg; + require e.msg.value == 0; + f@withrevert(e, arg); + assert !lastReverted, "${f.selector} can revert"; +} + + +rule alwaysRevert(method f) +description "$f has reverting paths" +{ + env e; + calldataarg arg; + f@withrevert(e, arg); + assert lastReverted, "${f.selector} succeeds"; +} + + +/* +This rule find which functions that can be called, may fail due to someone else calling a function right before. + +This is n expensive rule - might fail on the demo site on big contracts +*/ + +rule simpleFrontRunning(method f, address privileged) filtered { f-> !f.isView } +description "$f can no longer be called after it had been called by someone else" +{ + env e1; + calldataarg arg; + require e1.msg.sender == privileged; + + storage initialStorage = lastStorage; + f(e1, arg); + bool firstSucceeded = !lastReverted; + + env e2; + calldataarg arg2; + require e2.msg.sender != e1.msg.sender; + f(e2, arg2) at initialStorage; + f@withrevert(e1, arg); + bool succeeded = !lastReverted; + + assert succeeded, "${f.selector} can be not be called if was called by someone else"; +} + + +/* +This rule find which functions are privileged. +A function is privileged if there is only one address that can call it. + +The rules finds this by finding which functions can be called by two different users. + +*/ + + +rule privilegedOperation(method f, address privileged) +description "$f can be called by more than one user without reverting" +{ + env e1; + calldataarg arg; + require e1.msg.sender == privileged; + + storage initialStorage = lastStorage; + f@withrevert(e1, arg); // privileged succeeds executing candidate privileged operation. + bool firstSucceeded = !lastReverted; + + env e2; + calldataarg arg2; + require e2.msg.sender != privileged; + f@withrevert(e2, arg2) at initialStorage; // unprivileged + bool secondSucceeded = !lastReverted; + + assert !(firstSucceeded && secondSucceeded), "${f.selector} can be called by both ${e1.msg.sender} and ${e2.msg.sender}, so it is not privileged"; +} + +rule whoChangedBalanceOf(method f, address u) { + env eB; + env eF; + calldataarg args; + uint256 before = balanceOf(eB, u); + f(eF,args); + assert balanceOf(eB, u) == before, "balanceOf changed"; +} \ No newline at end of file diff --git a/certora/specs/erc20.spec b/certora/specs/erc20.spec new file mode 100644 index 0000000..b12fec1 --- /dev/null +++ b/certora/specs/erc20.spec @@ -0,0 +1,12 @@ +// erc20 methods +methods { + name() returns (string) => DISPATCHER(true) + symbol() returns (string) => DISPATCHER(true) + decimals() returns (string) => DISPATCHER(true) + totalSupply() returns (uint256) => DISPATCHER(true) + balanceOf(address) returns (uint256) => DISPATCHER(true) + allowance(address,address) returns (uint) => DISPATCHER(true) + approve(address,uint256) returns (bool) => DISPATCHER(true) + transfer(address,uint256) returns (bool) => DISPATCHER(true) + transferFrom(address,address,uint256) returns (bool) => DISPATCHER(true) +} diff --git a/resource_errors.json b/resource_errors.json new file mode 100644 index 0000000..d9bd792 --- /dev/null +++ b/resource_errors.json @@ -0,0 +1,3 @@ +{ + "topics": [] +} \ No newline at end of file From c36fab43d6b21ff2dbd0c0e6f59cad91a9e7e193 Mon Sep 17 00:00:00 2001 From: Michael M <91594326+MichaelMorami@users.noreply.github.com> Date: Wed, 25 May 2022 11:44:51 +0300 Subject: [PATCH 03/58] removing unnecessary files and updating contract --- certora/harness/DummyERC20A.sol | 5 -- certora/harness/DummyERC20B.sol | 5 -- certora/harness/DummyERC20Impl.sol | 57 -------------------- certora/scripts/{run.sh => runComplexity.sh} | 0 certora/specs/erc20.spec | 12 ----- 5 files changed, 79 deletions(-) delete mode 100644 certora/harness/DummyERC20A.sol delete mode 100644 certora/harness/DummyERC20B.sol delete mode 100644 certora/harness/DummyERC20Impl.sol rename certora/scripts/{run.sh => runComplexity.sh} (100%) delete mode 100644 certora/specs/erc20.spec diff --git a/certora/harness/DummyERC20A.sol b/certora/harness/DummyERC20A.sol deleted file mode 100644 index 188b926..0000000 --- a/certora/harness/DummyERC20A.sol +++ /dev/null @@ -1,5 +0,0 @@ -// SPDX-License-Identifier: agpl-3.0 -pragma solidity ^0.8.0; -import "./DummyERC20Impl.sol"; - -contract DummyERC20A is DummyERC20Impl {} \ No newline at end of file diff --git a/certora/harness/DummyERC20B.sol b/certora/harness/DummyERC20B.sol deleted file mode 100644 index 0f97f1e..0000000 --- a/certora/harness/DummyERC20B.sol +++ /dev/null @@ -1,5 +0,0 @@ -// SPDX-License-Identifier: agpl-3.0 -pragma solidity ^0.8.0; -import "./DummyERC20Impl.sol"; - -contract DummyERC20B is DummyERC20Impl {} \ No newline at end of file diff --git a/certora/harness/DummyERC20Impl.sol b/certora/harness/DummyERC20Impl.sol deleted file mode 100644 index 42e7f23..0000000 --- a/certora/harness/DummyERC20Impl.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: agpl-3.0 -pragma solidity ^0.8.0; - -// with mint -contract DummyERC20Impl { - uint256 t; - mapping (address => uint256) b; - mapping (address => mapping (address => uint256)) a; - - string public name; - string public symbol; - uint public decimals; - - function myAddress() public returns (address) { - return address(this); - } - - function add(uint a, uint b) internal pure returns (uint256) { - uint c = a +b; - require (c >= a); - return c; - } - function sub(uint a, uint b) internal pure returns (uint256) { - require (a>=b); - return a-b; - } - - function totalSupply() external view returns (uint256) { - return t; - } - function balanceOf(address account) external view returns (uint256) { - return b[account]; - } - function transfer(address recipient, uint256 amount) external returns (bool) { - b[msg.sender] = sub(b[msg.sender], amount); - b[recipient] = add(b[recipient], amount); - return true; - } - function allowance(address owner, address spender) external view returns (uint256) { - return a[owner][spender]; - } - function approve(address spender, uint256 amount) external returns (bool) { - a[msg.sender][spender] = amount; - return true; - } - - function transferFrom( - address sender, - address recipient, - uint256 amount - ) external returns (bool) { - b[sender] = sub(b[sender], amount); - b[recipient] = add(b[recipient], amount); - a[sender][msg.sender] = sub(a[sender][msg.sender], amount); - return true; - } -} \ No newline at end of file diff --git a/certora/scripts/run.sh b/certora/scripts/runComplexity.sh similarity index 100% rename from certora/scripts/run.sh rename to certora/scripts/runComplexity.sh diff --git a/certora/specs/erc20.spec b/certora/specs/erc20.spec deleted file mode 100644 index b12fec1..0000000 --- a/certora/specs/erc20.spec +++ /dev/null @@ -1,12 +0,0 @@ -// erc20 methods -methods { - name() returns (string) => DISPATCHER(true) - symbol() returns (string) => DISPATCHER(true) - decimals() returns (string) => DISPATCHER(true) - totalSupply() returns (uint256) => DISPATCHER(true) - balanceOf(address) returns (uint256) => DISPATCHER(true) - allowance(address,address) returns (uint) => DISPATCHER(true) - approve(address,uint256) returns (bool) => DISPATCHER(true) - transfer(address,uint256) returns (bool) => DISPATCHER(true) - transferFrom(address,address,uint256) returns (bool) => DISPATCHER(true) -} From 5bffdd862f4fafe0090f4dca1c291591afcfeb4a Mon Sep 17 00:00:00 2001 From: Michael M <91594326+MichaelMorami@users.noreply.github.com> Date: Wed, 25 May 2022 11:45:53 +0300 Subject: [PATCH 04/58] removing unnecessary files --- certora/harness/DummyERC20A.sol | 5 --- certora/harness/DummyERC20B.sol | 5 --- certora/harness/DummyERC20Impl.sol | 57 ------------------------------ certora/scripts/run.sh | 7 ---- certora/specs/erc20.spec | 12 ------- 5 files changed, 86 deletions(-) delete mode 100644 certora/harness/DummyERC20A.sol delete mode 100644 certora/harness/DummyERC20B.sol delete mode 100644 certora/harness/DummyERC20Impl.sol delete mode 100755 certora/scripts/run.sh delete mode 100644 certora/specs/erc20.spec diff --git a/certora/harness/DummyERC20A.sol b/certora/harness/DummyERC20A.sol deleted file mode 100644 index 188b926..0000000 --- a/certora/harness/DummyERC20A.sol +++ /dev/null @@ -1,5 +0,0 @@ -// SPDX-License-Identifier: agpl-3.0 -pragma solidity ^0.8.0; -import "./DummyERC20Impl.sol"; - -contract DummyERC20A is DummyERC20Impl {} \ No newline at end of file diff --git a/certora/harness/DummyERC20B.sol b/certora/harness/DummyERC20B.sol deleted file mode 100644 index 0f97f1e..0000000 --- a/certora/harness/DummyERC20B.sol +++ /dev/null @@ -1,5 +0,0 @@ -// SPDX-License-Identifier: agpl-3.0 -pragma solidity ^0.8.0; -import "./DummyERC20Impl.sol"; - -contract DummyERC20B is DummyERC20Impl {} \ No newline at end of file diff --git a/certora/harness/DummyERC20Impl.sol b/certora/harness/DummyERC20Impl.sol deleted file mode 100644 index 42e7f23..0000000 --- a/certora/harness/DummyERC20Impl.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: agpl-3.0 -pragma solidity ^0.8.0; - -// with mint -contract DummyERC20Impl { - uint256 t; - mapping (address => uint256) b; - mapping (address => mapping (address => uint256)) a; - - string public name; - string public symbol; - uint public decimals; - - function myAddress() public returns (address) { - return address(this); - } - - function add(uint a, uint b) internal pure returns (uint256) { - uint c = a +b; - require (c >= a); - return c; - } - function sub(uint a, uint b) internal pure returns (uint256) { - require (a>=b); - return a-b; - } - - function totalSupply() external view returns (uint256) { - return t; - } - function balanceOf(address account) external view returns (uint256) { - return b[account]; - } - function transfer(address recipient, uint256 amount) external returns (bool) { - b[msg.sender] = sub(b[msg.sender], amount); - b[recipient] = add(b[recipient], amount); - return true; - } - function allowance(address owner, address spender) external view returns (uint256) { - return a[owner][spender]; - } - function approve(address spender, uint256 amount) external returns (bool) { - a[msg.sender][spender] = amount; - return true; - } - - function transferFrom( - address sender, - address recipient, - uint256 amount - ) external returns (bool) { - b[sender] = sub(b[sender], amount); - b[recipient] = add(b[recipient], amount); - a[sender][msg.sender] = sub(a[sender][msg.sender], amount); - return true; - } -} \ No newline at end of file diff --git a/certora/scripts/run.sh b/certora/scripts/run.sh deleted file mode 100755 index 03cffd3..0000000 --- a/certora/scripts/run.sh +++ /dev/null @@ -1,7 +0,0 @@ -certoraRun src/AaveTokenV3.sol:AaveTokenV3 \ - --verify AaveTokenV3:certora/specs/complexity.spec \ - --solc solc8.13 \ - --optimistic_loop \ - --staging \ - --msg "AaveTokenV3 complexity check" - \ No newline at end of file diff --git a/certora/specs/erc20.spec b/certora/specs/erc20.spec deleted file mode 100644 index b12fec1..0000000 --- a/certora/specs/erc20.spec +++ /dev/null @@ -1,12 +0,0 @@ -// erc20 methods -methods { - name() returns (string) => DISPATCHER(true) - symbol() returns (string) => DISPATCHER(true) - decimals() returns (string) => DISPATCHER(true) - totalSupply() returns (uint256) => DISPATCHER(true) - balanceOf(address) returns (uint256) => DISPATCHER(true) - allowance(address,address) returns (uint) => DISPATCHER(true) - approve(address,uint256) returns (bool) => DISPATCHER(true) - transfer(address,uint256) returns (bool) => DISPATCHER(true) - transferFrom(address,address,uint256) returns (bool) => DISPATCHER(true) -} From 12f99312223406d8b2b1af3912b2dad7b50c60c1 Mon Sep 17 00:00:00 2001 From: Michael M <91594326+MichaelMorami@users.noreply.github.com> Date: Mon, 20 Jun 2022 11:43:22 +0300 Subject: [PATCH 05/58] starting setup --- certora/harness/AaveTokenV3Harness.sol | 9 +++++++++ certora/scripts/verifyBgdSpec.sh | 13 +++++++++++++ certora/specs/bgdSpec.spec | 20 ++++++++++++++++++++ properties.md | 1 - 4 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 certora/harness/AaveTokenV3Harness.sol create mode 100755 certora/scripts/verifyBgdSpec.sh create mode 100644 certora/specs/bgdSpec.spec diff --git a/certora/harness/AaveTokenV3Harness.sol b/certora/harness/AaveTokenV3Harness.sol new file mode 100644 index 0000000..6ad679c --- /dev/null +++ b/certora/harness/AaveTokenV3Harness.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {AaveTokenV3} from '../../src/AaveTokenV3.sol'; + +contract AaveTokenV3Harness is AaveTokenV3 { + +} \ No newline at end of file diff --git a/certora/scripts/verifyBgdSpec.sh b/certora/scripts/verifyBgdSpec.sh new file mode 100755 index 0000000..59e58e5 --- /dev/null +++ b/certora/scripts/verifyBgdSpec.sh @@ -0,0 +1,13 @@ +if [[ "$1" ]] +then + RULE="--rule $1" +fi + +certoraRun src/AaveTokenV3.sol:AaveTokenV3 \ + --verify AaveTokenV3:certora/specs/bdgSpec.spec \ + --solc solc8.13 \ + --optimistic_loop \ + --rule $1 \ + --staging \ + --msg "AaveTokenV3:bgdSpec.spec $1" + \ No newline at end of file diff --git a/certora/specs/bgdSpec.spec b/certora/specs/bgdSpec.spec new file mode 100644 index 0000000..23ede7e --- /dev/null +++ b/certora/specs/bgdSpec.spec @@ -0,0 +1,20 @@ +methods{ + _votingDelegateeV2(address) returns (address) + _propositionDelegateeV2(address) returns (address) + DELEGATED_POWER_DIVIDER() returns (uint256) + DELEGATE_BY_TYPE_TYPEHASH() returns (bytes32) + DELEGATE_TYPEHASH() returns (bytes32) + // _delegationMoveByType(uint104, uint104, address, GovernancePowerType delegationType, function(uint72, uint72) returns (uint72) operation) + // _delegationMove(address, DelegationAwareBalance userState, uint104, uint104, function(uint72, uint72) returns (uint72) operation) + _transferWithDelegation(address, address, uint256) + // _getDelegatedPowerByType(DelegationAwareBalance userState, GovernancePowerType delegationType) returns (uint72) + // _getDelegateeByType(address, DelegationAwareBalance userState, GovernancePowerType delegationType) returns (address) + // _updateDelegateeByType(address, GovernancePowerType delegationType, address) + // _updateDelegationFlagByType(DelegationAwareBalance userState, GovernancePowerType delegationType, bool) returns (DelegationAwareBalance) + // _delegateByType(address, address, GovernancePowerType delegationType) + // delegateByType(address, GovernancePowerType delegationType) + delegate(address delegatee) + // getDelegateeByType(address delegator, GovernancePowerType delegationType) returns (address) + // getPowerCurrent(address, GovernancePowerType delegationType) returns (uint256) + // metaDelegateByType(address, address, GovernancePowerType delegationType, uint256, uint8, bytes32, bytes32) + metaDelegate(address, address, uint256, uint8, bytes32, bytes32) \ No newline at end of file diff --git a/properties.md b/properties.md index 41a1dae..e7ceefc 100644 --- a/properties.md +++ b/properties.md @@ -30,7 +30,6 @@ $t_1$ → the state of the system after a transaction. ## General rules -- The total power (of one type) of all users in the system is less or equal than the sum of balances of all AAVE holders (totalSupply of AAVE token): $$\sum powerOfAccount_i <= \sum balanceOf(account_i)$$ - If an account is delegating a power to itself or to `address(0)`, that means that account is not delegating that power to anybody: $$powerOfAccountX = (accountXDelegatingPower \ ? \ 0 : balanceOf(accountX)) + From 72e3e5de5002f5c99844ba1ee7df888f1a46ec1f Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Tue, 28 Jun 2022 18:52:38 +0300 Subject: [PATCH 06/58] feat: initial harness and unit test --- certora/properties.md | 33 ++ certora/scripts/verifyBgdSpec.sh | 7 +- certora/specs/bgdSpec.spec | 107 ++++++- certora/specs/complexity.spec | 18 +- src/AaveTokenV3.sol | 2 + src/BaseAaveTokenV3.sol | 0 src/harness/AaveTokenV3Harness.sol | 421 +++++++++++++++++++++++++ src/harness/BaseAaveTokenHarness.sol | 301 ++++++++++++++++++ src/harness/BaseAaveTokenV2Harness.sol | 86 +++++ 9 files changed, 962 insertions(+), 13 deletions(-) create mode 100644 certora/properties.md create mode 100644 src/BaseAaveTokenV3.sol create mode 100644 src/harness/AaveTokenV3Harness.sol create mode 100644 src/harness/BaseAaveTokenHarness.sol create mode 100644 src/harness/BaseAaveTokenV2Harness.sol diff --git a/certora/properties.md b/certora/properties.md new file mode 100644 index 0000000..f1e0a71 --- /dev/null +++ b/certora/properties.md @@ -0,0 +1,33 @@ +## functions summary + +### internals + +- \_delegationMoveByType internal: apply operation on proper delegation balance +- \_delegationMove: delegation move by type _voting_, delegation move by type _proposition_ +- \_transferWithDelegation: delegation move with `-` op for `from`, delegation move with `+` op for `to` +- \_getDelegatedPowerByType: returns the voting/proposition power by type +- \_getDelegateeByType: returns the delegate address if user is delegating, or 0 if not +- \_updateDelegateeByType: updates the delegate for user. if delegate == user, then delegate is recorded as 0. +- \_updateDelegationFlagByType: updates the user's flag for delegating by type +- \_delegateByType: the whole delegation process - update voting power and flags + +### externals +- delegateByType: call the internal +- delegate(): call the internal on both types +- getDelegateeByType(): call the internal +- getPowerCurrent(): (if not delegating ) user balance + delegated balance +- metaDelegateByType(): delegate voting power using a signature from the delegator +- metaDelegate: metaDelegateByType for both types + +## ideas for properties + +- transfer where from == to doesn't change delegation balances +- address 0 has no voting/prop power +- \_transferWithDelegation removes delegation from `from` but does nothing on `to` if it's 0. + who can call this? +- delegation flag <=> delegatee != 0 +- anyone can delegate to zero. which means they're forfeiting the voting power. + + +## properties for Aave Token v3 spec + diff --git a/certora/scripts/verifyBgdSpec.sh b/certora/scripts/verifyBgdSpec.sh index 59e58e5..f8fc77c 100755 --- a/certora/scripts/verifyBgdSpec.sh +++ b/certora/scripts/verifyBgdSpec.sh @@ -3,11 +3,12 @@ then RULE="--rule $1" fi -certoraRun src/AaveTokenV3.sol:AaveTokenV3 \ - --verify AaveTokenV3:certora/specs/bdgSpec.spec \ +certoraRun src/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ + --verify AaveTokenV3:certora/specs/bgdSpec.spec \ + --rule $1 \ --solc solc8.13 \ --optimistic_loop \ - --rule $1 \ + --send_only \ --staging \ --msg "AaveTokenV3:bgdSpec.spec $1" \ No newline at end of file diff --git a/certora/specs/bgdSpec.spec b/certora/specs/bgdSpec.spec index 23ede7e..2af0171 100644 --- a/certora/specs/bgdSpec.spec +++ b/certora/specs/bgdSpec.spec @@ -1,4 +1,15 @@ +// using DelegationAwareBalance from "./BaseAaveToken.sol"; + +// issues: +// for enum, just use 0 (voting) and 1 (proposition) or local definition +// for struct use harness that replaces reads and writes with solidity functions + methods{ + totalSupply() returns (uint256) envfree + balanceOf(address addr) returns (uint256) envfree + transfer(address to, uint256 amount) returns (bool) + transferFrom(address from, address to) returns (bool) + _votingDelegateeV2(address) returns (address) _propositionDelegateeV2(address) returns (address) DELEGATED_POWER_DIVIDER() returns (uint256) @@ -17,4 +28,98 @@ methods{ // getDelegateeByType(address delegator, GovernancePowerType delegationType) returns (address) // getPowerCurrent(address, GovernancePowerType delegationType) returns (uint256) // metaDelegateByType(address, address, GovernancePowerType delegationType, uint256, uint8, bytes32, bytes32) - metaDelegate(address, address, uint256, uint8, bytes32, bytes32) \ No newline at end of file + metaDelegate(address, address, uint256, uint8, bytes32, bytes32) + // enum GovernancePowerType { + // VOTING, + // PROPOSITION + // } + getPowerCurrent(address user, uint8 delegationType) returns (uint256) envfree + + getBalance(address user) returns (uint104) envfree + getDelegatedPropositionBalance(address user) returns (uint72) envfree + getDelegatedVotingBalance(address user) returns (uint72) envfree + getDelegatingProposition(address user) returns (bool) envfree + getDelegatingVoting(address user) returns (bool) envfree +} + +definition VOTING_POWER() returns uint8 = 0; +definition PROPOSITION_POWER() returns uint8 = 1; + +// for test - it shouldnt pass +// invariant ZeroAddressNoDelegation() +// getPowerCurrent(0, 0) == 0 && getPowerCurrent(0, 1) == 0 + +// The total power (of one type) of all users in the system is less or equal than +// the sum of balances of all AAVE holders (totalSupply of AAVE token) + +// accumulator for a sum of proposition voting power +ghost mathint sumDelegatedProposition { + init_state axiom forall uint256 t. sumDelegatedProposition == 0; +} + +ghost mathint sumBalances { + init_state axiom forall uint256 t. sumBalances == 0; +} + +/* + update proposition balance on each store + */ +hook Sstore _balances[KEY address user].delegatedPropositionBalance uint72 balance + (uint72 old_balance) STORAGE { + sumDelegatedProposition = sumDelegatedProposition + to_mathint(balance) - to_mathint(old_balance); + } + +// try to rewrite using power.spec in aave-tokenv2 customer code +hook Sstore _balances[KEY address user].balance uint104 balance + (uint104 old_balance) STORAGE { + sumBalances = sumBalances + to_mathint(balance) - to_mathint(old_balance); + } + +invariant sumDelegatedPropositionCorrectness() sumDelegatedProposition <= totalSupply() { + // fails + preserved transfer(address to, uint256 amount) with (env e) + { + require(balanceOf(e.msg.sender) + balanceOf(to)) < totalSupply(); + } + preserved transferFrom(address from, address to, uint256 amount) with (env e) + { + require(balanceOf(from) + balanceOf(to)) < totalSupply(); + } +} + +rule totalSupplyCorrectness(method f) { + env e; + calldataarg args; + + require sumBalances == to_mathint(totalSupply()); + f(e, args); + assert sumBalances == to_mathint(totalSupply()); +} + +// doesn't work cause we can start with a state in which an address can have delegated balance field +// larger than total supply. +// rule sumDelegatedPropositionCorrect(method f) { +// env e; +// calldataarg args; + +// uint256 supplyBefore = totalSupply(); +// require sumDelegatedProposition <= supplyBefore; +// f(e, args); +// uint256 supplyAfter = totalSupply(); +// assert sumDelegatedProposition <= supplyAfter; +// } + + +rule transferUnitTest() { + env e; + address to; + uint256 amount; + require(to != e.msg.sender); + + uint256 powerToBefore = getPowerCurrent(to, VOTING_POWER()); + uint256 powerSenderBefore = getPowerCurrent(e.msg.sender, VOTING_POWER()); + transfer(e, to, amount); + uint256 powerToAfter = getPowerCurrent(to, VOTING_POWER()); + + assert powerToAfter == powerToBefore + powerSenderBefore; +} \ No newline at end of file diff --git a/certora/specs/complexity.spec b/certora/specs/complexity.spec index 0645e07..40a009a 100644 --- a/certora/specs/complexity.spec +++ b/certora/specs/complexity.spec @@ -1,4 +1,4 @@ -import "erc20.spec" +// import "erc20.spec" rule sanity(method f) { @@ -93,11 +93,11 @@ description "$f can be called by more than one user without reverting" assert !(firstSucceeded && secondSucceeded), "${f.selector} can be called by both ${e1.msg.sender} and ${e2.msg.sender}, so it is not privileged"; } -rule whoChangedBalanceOf(method f, address u) { - env eB; - env eF; - calldataarg args; - uint256 before = balanceOf(eB, u); - f(eF,args); - assert balanceOf(eB, u) == before, "balanceOf changed"; -} \ No newline at end of file +// rule whoChangedBalanceOf(method f, address u) { +// env eB; +// env eF; +// calldataarg args; +// uint256 before = balanceOf(eB, u); +// f(eF,args); +// assert balanceOf(eB, u) == before, "balanceOf changed"; +// } \ No newline at end of file diff --git a/src/AaveTokenV3.sol b/src/AaveTokenV3.sol index df6c893..7f61aed 100644 --- a/src/AaveTokenV3.sol +++ b/src/AaveTokenV3.sol @@ -9,6 +9,8 @@ import {BaseAaveTokenV2} from './BaseAaveTokenV2.sol'; import {MathUtils} from './utils/MathUtils.sol'; contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { + + mapping(address => address) internal _votingDelegateeV2; mapping(address => address) internal _propositionDelegateeV2; diff --git a/src/BaseAaveTokenV3.sol b/src/BaseAaveTokenV3.sol new file mode 100644 index 0000000..e69de29 diff --git a/src/harness/AaveTokenV3Harness.sol b/src/harness/AaveTokenV3Harness.sol new file mode 100644 index 0000000..23f3ec6 --- /dev/null +++ b/src/harness/AaveTokenV3Harness.sol @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {VersionedInitializable} from '../utils/VersionedInitializable.sol'; + +import {IGovernancePowerDelegationToken} from '../interfaces/IGovernancePowerDelegationToken.sol'; +import {BaseAaveTokenV2} from './BaseAaveTokenV2Harness.sol'; +import {MathUtils} from '../utils/MathUtils.sol'; + +contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { + + + mapping(address => address) internal _votingDelegateeV2; + mapping(address => address) internal _propositionDelegateeV2; + + uint256 public constant DELEGATED_POWER_DIVIDER = 10**10; + + bytes32 public constant DELEGATE_BY_TYPE_TYPEHASH = + keccak256( + 'DelegateByType(address delegator,address delegatee,GovernancePowerType delegationType,uint256 nonce,uint256 deadline)' + ); + bytes32 public constant DELEGATE_TYPEHASH = + keccak256('Delegate(address delegator,address delegatee,uint256 nonce,uint256 deadline)'); + + /** + Harness section - replace struct reads and writes with function calls + */ + +// struct DelegationAwareBalance { +// uint104 balance; +// uint72 delegatedPropositionBalance; +// uint72 delegatedVotingBalance; +// bool delegatingProposition; +// bool delegatingVoting; +// } + + function _setBalance(address user, uint104 balance) internal { + _balances[user].balance = balance; + } + + function getBalance(address user) view public returns (uint104) { + return _balances[user].balance; + } + + function _setDelegatedPropositionBalance(address user, uint72 dpb) internal { + _balances[user].delegatedPropositionBalance = dpb; + } + + function getDelegatedPropositionBalance(address user) view public returns (uint72) { + return _balances[user].delegatedPropositionBalance; + } + + function _setDelegatedVotingBalance(address user, uint72 dvb) internal { + _balances[user].delegatedVotingBalance = dvb; + } + + function getDelegatedVotingBalance(address user) view public returns (uint72) { + return _balances[user].delegatedVotingBalance; + } + + function _setDelegatingProposition(address user, bool _delegating) internal { + _balances[user].delegatingProposition = _delegating; + } + + function getDelegatingProposition(address user) view public returns (bool) { + return _balances[user].delegatingProposition; + } + + function _setDelegatingVoting(address user, bool _delegating) internal { + _balances[user].delegatingVoting = _delegating; + } + + function getDelegatingVoting(address user) view public returns (bool) { + return _balances[user].delegatingVoting; + } + + /** + End of harness section + */ + + /** + * @dev changing one of delegated governance powers of delegatee depending on the delegator balance change + * @param userBalanceBefore delegator balance before operation + * @param userBalanceAfter delegator balance after operation + * @param delegatee the user whom delegated governance power will be changed + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + * @param operation math operation which will be applied depends on increasing or decreasing of the delegator balance (plus, minus) + **/ + function _delegationMoveByType( + uint104 userBalanceBefore, + uint104 userBalanceAfter, + address delegatee, + GovernancePowerType delegationType, + function(uint72, uint72) returns (uint72) operation + ) internal { + if (delegatee == address(0)) return; + + // @dev to make delegated balance fit into uin72 we're decreasing precision of delegated balance by DELEGATED_POWER_DIVIDER + uint72 delegationDelta = uint72( + (userBalanceBefore / DELEGATED_POWER_DIVIDER) - (userBalanceAfter / DELEGATED_POWER_DIVIDER) + ); + if (delegationDelta == 0) return; + + if (delegationType == GovernancePowerType.VOTING) { + _balances[delegatee].delegatedVotingBalance = operation( + _balances[delegatee].delegatedVotingBalance, + delegationDelta + ); + //TODO: emit DelegatedPowerChanged maybe; + } else { + _balances[delegatee].delegatedPropositionBalance = operation( + _balances[delegatee].delegatedPropositionBalance, + delegationDelta + ); + //TODO: emit DelegatedPowerChanged maybe; + } + } + + /** + * @dev changing one of governance power(Voting and Proposition) of delegatees depending on the delegator balance change + * @param user delegator + * @param userState the current state of the delegator + * @param balanceBefore delegator balance before operation + * @param balanceAfter delegator balance after operation + * @param operation math operation which will be applied depends on increasing or decreasing of the delegator balance (plus, minus) + **/ + function _delegationMove( + address user, + DelegationAwareBalance memory userState, + uint104 balanceBefore, + uint104 balanceAfter, + function(uint72, uint72) returns (uint72) operation + ) internal { + _delegationMoveByType( + balanceBefore, + balanceAfter, + _getDelegateeByType(user, userState, GovernancePowerType.VOTING), + GovernancePowerType.VOTING, + operation + ); + _delegationMoveByType( + balanceBefore, + balanceAfter, + _getDelegateeByType(user, userState, GovernancePowerType.PROPOSITION), + GovernancePowerType.PROPOSITION, + operation + ); + } + + /** + * @dev performs all state changes related to balance transfer and corresponding delegation changes + * @param from token sender + * @param to token recipient + * @param amount amount of tokens sent + **/ + function _transferWithDelegation( + address from, + address to, + uint256 amount + ) internal override { + if (from == to) { + return; + } + + if (from != address(0)) { + DelegationAwareBalance memory fromUserState = _balances[from]; + require(fromUserState.balance >= amount, 'ERC20: transfer amount exceeds balance'); + + uint104 fromBalanceAfter; + unchecked { + //TODO: in general we don't need to check cast to uint104 because we know that it's less then balance from require + fromBalanceAfter = fromUserState.balance - uint104(amount); + } + _balances[from].balance = fromBalanceAfter; + if (fromUserState.delegatingProposition || fromUserState.delegatingVoting) + _delegationMove( + from, + fromUserState, + fromUserState.balance, + fromBalanceAfter, + MathUtils.minus + ); + } + + if (to != address(0)) { + DelegationAwareBalance memory toUserState = _balances[to]; + uint104 toBalanceBefore = toUserState.balance; + toUserState.balance = toBalanceBefore + uint104(amount); // TODO: check overflow? + _balances[to] = toUserState; + + if (toUserState.delegatingVoting || toUserState.delegatingProposition) { + _delegationMove(to, toUserState, toUserState.balance, toBalanceBefore, MathUtils.plus); + } + } + } + + /** + * @dev extracting and returning delegated governance power(Voting or Proposition) from user state + * @param userState the current state of a user + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + **/ + function _getDelegatedPowerByType( + DelegationAwareBalance memory userState, + GovernancePowerType delegationType + ) internal pure returns (uint72) { + return + delegationType == GovernancePowerType.VOTING + ? userState.delegatedVotingBalance + : userState.delegatedPropositionBalance; + } + + /** + * @dev extracts from user state and returning delegatee by type of governance power(Voting or Proposition) + * @param user delegator + * @param userState the current state of a user + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + **/ + function _getDelegateeByType( + address user, + DelegationAwareBalance memory userState, + GovernancePowerType delegationType + ) internal view returns (address) { + if (delegationType == GovernancePowerType.VOTING) { + return userState.delegatingVoting ? _votingDelegateeV2[user] : address(0); + } + return userState.delegatingProposition ? _propositionDelegateeV2[user] : address(0); + } + + /** + * @dev changing user's delegatee address by type of governance power(Voting or Proposition) + * @param user delegator + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + * @param _newDelegatee the new delegatee + **/ + function _updateDelegateeByType( + address user, + GovernancePowerType delegationType, + address _newDelegatee + ) internal { + address newDelegatee = _newDelegatee == user ? address(0) : _newDelegatee; + if (delegationType == GovernancePowerType.VOTING) { + _votingDelegateeV2[user] = newDelegatee; + } else { + _propositionDelegateeV2[user] = newDelegatee; + } + } + + /** + * @dev updates the specific flag which signaling about existence of delegation of governance power(Voting or Proposition) + * @param userState a user state to change + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + * @param willDelegate next state of delegation + **/ + function _updateDelegationFlagByType( + DelegationAwareBalance memory userState, + GovernancePowerType delegationType, + bool willDelegate + ) internal pure returns (DelegationAwareBalance memory) { + if (delegationType == GovernancePowerType.VOTING) { + userState.delegatingVoting = willDelegate; + } else { + userState.delegatingProposition = willDelegate; + } + return userState; + } + + /** + * @dev delegates the specific power to a delegatee + * @param user delegator + * @param _delegatee the user which delegated power has changed + * @param delegationType the type of delegation (VOTING, PROPOSITION) + **/ + function _delegateByType( + address user, + address _delegatee, + GovernancePowerType delegationType + ) internal { + //we consider to 0x0 as delegation to self + address delegatee = _delegatee == user ? address(0) : _delegatee; + + DelegationAwareBalance memory userState = _balances[user]; + address currentDelegatee = _getDelegateeByType(user, userState, delegationType); + if (delegatee == currentDelegatee) return; + + bool delegatingNow = currentDelegatee != address(0); + bool willDelegateAfter = delegatee != address(0); + + if (delegatingNow) { + _delegationMoveByType( + userState.balance, + 0, + currentDelegatee, + delegationType, + MathUtils.minus + ); + } + if (willDelegateAfter) { + _updateDelegateeByType(user, delegationType, delegatee); + _delegationMoveByType(userState.balance, 0, delegatee, delegationType, MathUtils.plus); + } + + if (willDelegateAfter != delegatingNow) { + _balances[user] = _updateDelegationFlagByType(userState, delegationType, willDelegateAfter); + } + + emit DelegateChanged(user, delegatee, delegationType); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function delegateByType(address delegatee, GovernancePowerType delegationType) + external + virtual + override + { + _delegateByType(msg.sender, delegatee, delegationType); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function delegate(address delegatee) external override { + _delegateByType(msg.sender, delegatee, GovernancePowerType.VOTING); + _delegateByType(msg.sender, delegatee, GovernancePowerType.PROPOSITION); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function getDelegateeByType(address delegator, GovernancePowerType delegationType) + external + view + override + returns (address) + { + return _getDelegateeByType(delegator, _balances[delegator], delegationType); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function getPowerCurrent(address user, GovernancePowerType delegationType) + external + view + override + returns (uint256) + { + DelegationAwareBalance memory userState = _balances[user]; + uint256 userOwnPower = (delegationType == GovernancePowerType.VOTING && + !userState.delegatingVoting) || + (delegationType == GovernancePowerType.PROPOSITION && !userState.delegatingProposition) + ? _balances[user].balance + : 0; + uint256 userDelegatedPower = _getDelegatedPowerByType(userState, delegationType) * + DELEGATED_POWER_DIVIDER; + return userOwnPower + userDelegatedPower; + } + + /// @inheritdoc IGovernancePowerDelegationToken + function metaDelegateByType( + address delegator, + address delegatee, + GovernancePowerType delegationType, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external override { + require(delegator != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _nonces[delegator]; + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256( + abi.encode( + DELEGATE_BY_TYPE_TYPEHASH, + delegator, + delegatee, + delegationType, + currentValidNonce, + deadline + ) + ) + ) + ); + + require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + unchecked { + // does not make sense to check because it's not realistic to reach uint256.max in nonce + _nonces[delegator] = currentValidNonce + 1; + } + _delegateByType(delegator, delegatee, delegationType); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function metaDelegate( + address delegator, + address delegatee, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external override { + require(delegator != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _nonces[delegator]; + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256(abi.encode(DELEGATE_TYPEHASH, delegator, delegatee, currentValidNonce, deadline)) + ) + ); + + require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + unchecked { + // does not make sense to check because it's not realistic to reach uint256.max in nonce + _nonces[delegator] = currentValidNonce + 1; + } + _delegateByType(delegator, delegatee, GovernancePowerType.VOTING); + _delegateByType(delegator, delegatee, GovernancePowerType.PROPOSITION); + } +} diff --git a/src/harness/BaseAaveTokenHarness.sol b/src/harness/BaseAaveTokenHarness.sol new file mode 100644 index 0000000..cb85e8e --- /dev/null +++ b/src/harness/BaseAaveTokenHarness.sol @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) + +// harness: balances and allowances are public variables + +pragma solidity ^0.8.0; + +import {Context} from '../../lib/openzeppelin-contracts/contracts/utils/Context.sol'; +import {IERC20} from '../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; +import {IERC20Metadata} from '../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; + + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +abstract contract BaseAaveToken is Context, IERC20Metadata { + struct DelegationAwareBalance { + uint104 balance; + uint72 delegatedPropositionBalance; + uint72 delegatedVotingBalance; + bool delegatingProposition; + bool delegatingVoting; + } + + mapping(address => DelegationAwareBalance) public _balances; + + mapping(address => mapping(address => uint256)) public _allowances; + + uint256 internal _totalSupply; + + string internal _name; + string internal _symbol; + + // @dev DEPRECATED + // kept for backwards compatibility with old storage layout + uint8 private ______DEPRECATED_OLD_ERC20_DECIMALS; + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless this function is + * overridden; + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual override returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account].balance; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address to, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) + public + view + virtual + override + returns (uint256) + { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + * - the caller must have allowance for ``from``'s tokens of at least + * `amount`. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual override returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, _allowances[owner][spender] + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) + public + virtual + returns (bool) + { + address owner = _msgSender(); + uint256 currentAllowance = _allowances[owner][spender]; + require(currentAllowance >= subtractedValue, 'ERC20: decreased allowance below zero'); + unchecked { + _approve(owner, spender, currentAllowance - subtractedValue); + } + + return true; + } + + /** + * @dev Moves `amount` of tokens from `sender` to `recipient`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + */ + function _transfer( + address from, + address to, + uint256 amount + ) internal virtual { + require(from != address(0), 'ERC20: transfer from the zero address'); + require(to != address(0), 'ERC20: transfer to the zero address'); + + _transferWithDelegation(from, to, amount); + emit Transfer(from, to, amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual { + require(owner != address(0), 'ERC20: approve from the zero address'); + require(spender != address(0), 'ERC20: approve to the zero address'); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Spend `amount` form the allowance of `owner` toward `spender`. + * + * Does not update the allowance amount in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Might emit an {Approval} event. + */ + function _spendAllowance( + address owner, + address spender, + uint256 amount + ) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require(currentAllowance >= amount, 'ERC20: insufficient allowance'); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } + + function _transferWithDelegation( + address from, + address to, + uint256 amount + ) internal virtual {} +} diff --git a/src/harness/BaseAaveTokenV2Harness.sol b/src/harness/BaseAaveTokenV2Harness.sol new file mode 100644 index 0000000..318f210 --- /dev/null +++ b/src/harness/BaseAaveTokenV2Harness.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +// harness: import BaseAaveToken from harness file + +import {VersionedInitializable} from '../utils/VersionedInitializable.sol'; + +import {BaseAaveToken} from './BaseAaveTokenHarness.sol'; + +abstract contract BaseAaveTokenV2 is BaseAaveToken, VersionedInitializable { + /// @dev owner => next valid nonce to submit with permit() + mapping(address => uint256) public _nonces; + + ///////// @dev DEPRECATED from AaveToken v1 ////////////////////////// + //////// kept for backwards compatibility with old storage layout //// + uint256[3] private ______DEPRECATED_FROM_AAVE_V1; + ///////// @dev END OF DEPRECATED from AaveToken v1 ////////////////////////// + + bytes32 public DOMAIN_SEPARATOR; + + ///////// @dev DEPRECATED from AaveToken v2 ////////////////////////// + //////// kept for backwards compatibility with old storage layout //// + uint256[4] private ______DEPRECATED_FROM_AAVE_V2; + ///////// @dev END OF DEPRECATED from AaveToken v2 ////////////////////////// + + bytes public constant EIP712_REVISION = bytes('1'); + bytes32 internal constant EIP712_DOMAIN = + keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'); + bytes32 public constant PERMIT_TYPEHASH = + keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'); + + uint256 public constant REVISION = 3; // TODO: CHECK, but most probably was 2 before + + /** + * @dev initializes the contract upon assignment to the InitializableAdminUpgradeabilityProxy + */ + function initialize() external initializer {} + + /** + * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md + * @param owner the owner of the funds + * @param spender the spender + * @param value the amount + * @param deadline the deadline timestamp, type(uint256).max for no deadline + * @param v signature param + * @param s signature param + * @param r signature param + */ + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external { + require(owner != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _nonces[owner]; + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline)) + ) + ); + + require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + unchecked { + // does not make sense to check because it's not realistic to reach uint256.max in nonce + _nonces[owner] = currentValidNonce + 1; + } + _approve(owner, spender, value); + } + + /** + * @dev returns the revision of the implementation contract + */ + function getRevision() internal pure override returns (uint256) { + return REVISION; + } +} From bf7fe909f9e4cd259d555ab52ecefb7e3d6069bf Mon Sep 17 00:00:00 2001 From: Michael M <91594326+MichaelMorami@users.noreply.github.com> Date: Wed, 29 Jun 2022 16:46:10 +0300 Subject: [PATCH 07/58] splitting certora's gitignore from main gitignore --- .gitignore | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.gitignore b/.gitignore index a2edf0f..c2e1bd9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,3 @@ cache/ out/ .idea - -# certora -.certora* -.certora*.json -**.last_conf* -certora-logs From f5f767afcb3c9bb537606281d42d7f61e8a28335 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Tue, 12 Jul 2022 13:58:32 +0300 Subject: [PATCH 08/58] feat: new rules --- certora/harness/AaveTokenV3Harness.sol | 9 -- certora/properties.md | 10 +++ certora/specs/bgdSpec.spec | 115 +++++++++++++++++++++---- src/harness/AaveTokenV3Harness.sol | 10 +++ 4 files changed, 119 insertions(+), 25 deletions(-) delete mode 100644 certora/harness/AaveTokenV3Harness.sol diff --git a/certora/harness/AaveTokenV3Harness.sol b/certora/harness/AaveTokenV3Harness.sol deleted file mode 100644 index 6ad679c..0000000 --- a/certora/harness/AaveTokenV3Harness.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {AaveTokenV3} from '../../src/AaveTokenV3.sol'; - -contract AaveTokenV3Harness is AaveTokenV3 { - -} \ No newline at end of file diff --git a/certora/properties.md b/certora/properties.md index f1e0a71..cd1ac21 100644 --- a/certora/properties.md +++ b/certora/properties.md @@ -31,3 +31,13 @@ ## properties for Aave Token v3 spec +-- on token transfer, the delegation balances change correctly for all cases: +from delegating, to delegating, both delegating, none delegating + +-- delegating to 0 == delegating to self. which means the flags are set to false + +-- the flags are updated properly after delegation + +-- the delegated power is stored divided by 10^10. make sure this doesn't kill precision. + +-- \ No newline at end of file diff --git a/certora/specs/bgdSpec.spec b/certora/specs/bgdSpec.spec index 2af0171..15dcd22 100644 --- a/certora/specs/bgdSpec.spec +++ b/certora/specs/bgdSpec.spec @@ -8,22 +8,11 @@ methods{ totalSupply() returns (uint256) envfree balanceOf(address addr) returns (uint256) envfree transfer(address to, uint256 amount) returns (bool) - transferFrom(address from, address to) returns (bool) + transferFrom(address from, address to, uint256 amount) returns (bool) - _votingDelegateeV2(address) returns (address) - _propositionDelegateeV2(address) returns (address) DELEGATED_POWER_DIVIDER() returns (uint256) DELEGATE_BY_TYPE_TYPEHASH() returns (bytes32) DELEGATE_TYPEHASH() returns (bytes32) - // _delegationMoveByType(uint104, uint104, address, GovernancePowerType delegationType, function(uint72, uint72) returns (uint72) operation) - // _delegationMove(address, DelegationAwareBalance userState, uint104, uint104, function(uint72, uint72) returns (uint72) operation) - _transferWithDelegation(address, address, uint256) - // _getDelegatedPowerByType(DelegationAwareBalance userState, GovernancePowerType delegationType) returns (uint72) - // _getDelegateeByType(address, DelegationAwareBalance userState, GovernancePowerType delegationType) returns (address) - // _updateDelegateeByType(address, GovernancePowerType delegationType, address) - // _updateDelegationFlagByType(DelegationAwareBalance userState, GovernancePowerType delegationType, bool) returns (DelegationAwareBalance) - // _delegateByType(address, address, GovernancePowerType delegationType) - // delegateByType(address, GovernancePowerType delegationType) delegate(address delegatee) // getDelegateeByType(address delegator, GovernancePowerType delegationType) returns (address) // getPowerCurrent(address, GovernancePowerType delegationType) returns (uint256) @@ -40,6 +29,8 @@ methods{ getDelegatedVotingBalance(address user) returns (uint72) envfree getDelegatingProposition(address user) returns (bool) envfree getDelegatingVoting(address user) returns (bool) envfree + getVotingDelegate(address user) returns (address) envfree + getPropositionDelegate(address user) returns (address) envfree } definition VOTING_POWER() returns uint8 = 0; @@ -54,11 +45,11 @@ definition PROPOSITION_POWER() returns uint8 = 1; // accumulator for a sum of proposition voting power ghost mathint sumDelegatedProposition { - init_state axiom forall uint256 t. sumDelegatedProposition == 0; + init_state axiom sumDelegatedProposition == 0; } ghost mathint sumBalances { - init_state axiom forall uint256 t. sumBalances == 0; + init_state axiom sumBalances == 0; } /* @@ -75,7 +66,7 @@ hook Sstore _balances[KEY address user].balance uint104 balance sumBalances = sumBalances + to_mathint(balance) - to_mathint(old_balance); } -invariant sumDelegatedPropositionCorrectness() sumDelegatedProposition <= totalSupply() { +invariant sumDelegatedPropositionCorrectness() sumDelegatedProposition <= sumBalances { // fails preserved transfer(address to, uint256 amount) with (env e) { @@ -87,6 +78,15 @@ invariant sumDelegatedPropositionCorrectness() sumDelegatedProposition <= totalS } } +invariant nonDelegatingBalance(address user) + !getDelegatingProposition(user) => balanceOf(user) == getDelegatedPropositionBalance(user) { + preserved transfer(address to, uint256 amount) with (env e) + { + require(getVotingDelegate(to) != user); + } + } + + rule totalSupplyCorrectness(method f) { env e; calldataarg args; @@ -122,4 +122,87 @@ rule transferUnitTest() { uint256 powerToAfter = getPowerCurrent(to, VOTING_POWER()); assert powerToAfter == powerToBefore + powerSenderBefore; -} \ No newline at end of file +} + +// for non delegating address +rule votingPowerEqualsBalance(address user) { + uint256 userBalance = balanceOf(user); + require(!getDelegatingProposition(user)); + require(!getDelegatingVoting(user)); + assert userBalance == getDelegatedPropositionBalance(user) && userBalance == getDelegatedVotingBalance(user); +} + +// Verify that the voting delegation balances update correctly +// probably a scaling issue +rule tokenTransferCorrectnessVoting(address from, address to, uint256 amount) { + env e; + + require(from != 0 && to != 0); + + uint256 balanceFromBefore = balanceOf(from); + uint256 balanceToBefore = balanceOf(to); + + address fromDelegate = getVotingDelegate(from); + address toDelegate = getVotingDelegate(to); + + uint256 powerFromDelegateBefore = getPowerCurrent(fromDelegate, VOTING_POWER()); + uint256 powerToDelegateBefore = getPowerCurrent(toDelegate, VOTING_POWER()); + + bool isDelegatingVotingFromBefore = getDelegatingVoting(from); + bool isDelegatingVotingToBefore = getDelegatingVoting(to); + + // non reverting path + transferFrom(e, from, to, amount); + + uint256 balanceFromAfter = balanceOf(from); + uint256 balanceToAfter = balanceOf(to); + + address fromDelegateAfter = getVotingDelegate(from); + address toDelegateAfter = getVotingDelegate(to); + + uint256 powerFromDelegateAfter = getPowerCurrent(fromDelegateAfter, VOTING_POWER()); + uint256 powerToDelegateAfter = getPowerCurrent(toDelegateAfter, VOTING_POWER()); + + bool isDelegatingVotingFromAfter = getDelegatingVoting(from); + bool isDelegatingVotingToAfter = getDelegatingVoting(to); + + assert fromDelegateAfter == toDelegateAfter => powerFromDelegateBefore == powerFromDelegateAfter; + + assert isDelegatingVotingFromBefore => + powerFromDelegateAfter - powerFromDelegateBefore == amount || + (fromDelegateAfter == toDelegateAfter && powerFromDelegateBefore == powerFromDelegateAfter); + assert isDelegatingVotingToBefore => + powerToDelegateAfter - powerToDelegateBefore == amount || + (fromDelegateAfter == toDelegateAfter && powerToDelegateBefore == powerToDelegateAfter); + +} + +// If an account is not receiving delegation of power (one type) from anybody, +// and that account is not delegating that power to anybody, the power of that account +// must be equal to its AAVE balance. + +rule powerWhenNotDelegating(address account) { + uint256 balance = balanceOf(account); + bool isDelegatingVoting = getDelegatingVoting(account); + bool isDelegatingProposition = getDelegatingProposition(account); + uint72 dvb = getDelegatedVotingBalance(account); + uint72 dpb = getDelegatedPropositionBalance(account); + + uint256 votingPower = getPowerCurrent(account, VOTING_POWER()); + uint256 propositionPower = getPowerCurrent(account, PROPOSITION_POWER()); + + assert dvb == 0 && !isDelegatingVoting => votingPower == balance; + assert dpb == 0 && !isDelegatingProposition => propositionPower == balance; +} + +// wrong, user may delegate to himself/0 and the flag will be set true +rule selfDelegationCorrectness(address account) { + bool isDelegatingVoting = getDelegatingVoting(account); + bool isDelegatingProposition = getDelegatingProposition(account); + address votingDelegate = getVotingDelegate(account); + address propositionDelegate = getPropositionDelegate(account); + + assert votingDelegate == 0 || votingDelegate == account => isDelegatingVoting == false; + assert propositionDelegate == 0 || propositionDelegate == account => isDelegatingProposition == false; + +} diff --git a/src/harness/AaveTokenV3Harness.sol b/src/harness/AaveTokenV3Harness.sol index 23f3ec6..17fa0b8 100644 --- a/src/harness/AaveTokenV3Harness.sol +++ b/src/harness/AaveTokenV3Harness.sol @@ -75,6 +75,16 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { return _balances[user].delegatingVoting; } + function getVotingDelegate(address user) view public returns (address) { + return _votingDelegateeV2[user]; + } + + function getPropositionDelegate(address user) view public returns (address) { + return _propositionDelegateeV2[user]; + } + + + /** End of harness section */ From da405d37aa4f8d3b19611f7e51899f4172087f39 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Tue, 12 Jul 2022 13:59:42 +0300 Subject: [PATCH 09/58] fix: upgate gitignore to exclude certora config --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index c2e1bd9..4f0ad61 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ cache/ out/ .idea +.certora_config/ +.last_confs/ +.certora_* From 0d93d6b3fe7b80a46e9c7060e2fcad6412015a68 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Tue, 19 Jul 2022 09:11:56 +0300 Subject: [PATCH 10/58] update folders --- .../harness/AaveTokenV3Harness.sol | 15 +- .../harness/BaseAaveTokenHarness.sol | 0 .../harness/BaseAaveTokenV2Harness.sol | 2 +- certora/scripts/verifyBgdSpec.sh | 2 +- certora/specs/bgdSpec.spec | 364 +++++++++++++++++- certora/specs/setup.spec | 1 + src/BaseAaveTokenV3.sol | 84 ++++ 7 files changed, 450 insertions(+), 18 deletions(-) rename {src => certora}/harness/AaveTokenV3Harness.sol (96%) rename {src => certora}/harness/BaseAaveTokenHarness.sol (100%) rename {src => certora}/harness/BaseAaveTokenV2Harness.sol (97%) create mode 100644 certora/specs/setup.spec diff --git a/src/harness/AaveTokenV3Harness.sol b/certora/harness/AaveTokenV3Harness.sol similarity index 96% rename from src/harness/AaveTokenV3Harness.sol rename to certora/harness/AaveTokenV3Harness.sol index 17fa0b8..d0f44e8 100644 --- a/src/harness/AaveTokenV3Harness.sol +++ b/certora/harness/AaveTokenV3Harness.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8.0; -import {VersionedInitializable} from '../utils/VersionedInitializable.sol'; +import {VersionedInitializable} from '../../src/utils/VersionedInitializable.sol'; -import {IGovernancePowerDelegationToken} from '../interfaces/IGovernancePowerDelegationToken.sol'; +import {IGovernancePowerDelegationToken} from '../../src/interfaces/IGovernancePowerDelegationToken.sol'; import {BaseAaveTokenV2} from './BaseAaveTokenV2Harness.sol'; -import {MathUtils} from '../utils/MathUtils.sol'; +import {MathUtils} from '../../src/utils/MathUtils.sol'; contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { @@ -106,10 +106,13 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { ) internal { if (delegatee == address(0)) return; + // FIXING A PRECISION ISSUE HERE + // @dev to make delegated balance fit into uin72 we're decreasing precision of delegated balance by DELEGATED_POWER_DIVIDER - uint72 delegationDelta = uint72( - (userBalanceBefore / DELEGATED_POWER_DIVIDER) - (userBalanceAfter / DELEGATED_POWER_DIVIDER) - ); + // uint72 delegationDelta = uint72( + // (userBalanceBefore / DELEGATED_POWER_DIVIDER) - (userBalanceAfter / DELEGATED_POWER_DIVIDER) + // ); + uint72 delegationDelta = uint72((userBalanceBefore - userBalanceAfter) / DELEGATED_POWER_DIVIDER); if (delegationDelta == 0) return; if (delegationType == GovernancePowerType.VOTING) { diff --git a/src/harness/BaseAaveTokenHarness.sol b/certora/harness/BaseAaveTokenHarness.sol similarity index 100% rename from src/harness/BaseAaveTokenHarness.sol rename to certora/harness/BaseAaveTokenHarness.sol diff --git a/src/harness/BaseAaveTokenV2Harness.sol b/certora/harness/BaseAaveTokenV2Harness.sol similarity index 97% rename from src/harness/BaseAaveTokenV2Harness.sol rename to certora/harness/BaseAaveTokenV2Harness.sol index 318f210..9108728 100644 --- a/src/harness/BaseAaveTokenV2Harness.sol +++ b/certora/harness/BaseAaveTokenV2Harness.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; // harness: import BaseAaveToken from harness file -import {VersionedInitializable} from '../utils/VersionedInitializable.sol'; +import {VersionedInitializable} from '../../src/utils/VersionedInitializable.sol'; import {BaseAaveToken} from './BaseAaveTokenHarness.sol'; diff --git a/certora/scripts/verifyBgdSpec.sh b/certora/scripts/verifyBgdSpec.sh index f8fc77c..4e4eacf 100755 --- a/certora/scripts/verifyBgdSpec.sh +++ b/certora/scripts/verifyBgdSpec.sh @@ -3,7 +3,7 @@ then RULE="--rule $1" fi -certoraRun src/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ +certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ --verify AaveTokenV3:certora/specs/bgdSpec.spec \ --rule $1 \ --solc solc8.13 \ diff --git a/certora/specs/bgdSpec.spec b/certora/specs/bgdSpec.spec index 15dcd22..f4878ec 100644 --- a/certora/specs/bgdSpec.spec +++ b/certora/specs/bgdSpec.spec @@ -10,18 +10,8 @@ methods{ transfer(address to, uint256 amount) returns (bool) transferFrom(address from, address to, uint256 amount) returns (bool) - DELEGATED_POWER_DIVIDER() returns (uint256) - DELEGATE_BY_TYPE_TYPEHASH() returns (bytes32) - DELEGATE_TYPEHASH() returns (bytes32) delegate(address delegatee) - // getDelegateeByType(address delegator, GovernancePowerType delegationType) returns (address) - // getPowerCurrent(address, GovernancePowerType delegationType) returns (uint256) - // metaDelegateByType(address, address, GovernancePowerType delegationType, uint256, uint8, bytes32, bytes32) metaDelegate(address, address, uint256, uint8, bytes32, bytes32) - // enum GovernancePowerType { - // VOTING, - // PROPOSITION - // } getPowerCurrent(address user, uint8 delegationType) returns (uint256) envfree getBalance(address user) returns (uint104) envfree @@ -35,6 +25,11 @@ methods{ definition VOTING_POWER() returns uint8 = 0; definition PROPOSITION_POWER() returns uint8 = 1; +definition DELEGATED_POWER_DIVIDER() returns uint256 = 10^10; + +function normalize(uint256 amount) returns uint256 { + return to_uint256(amount / DELEGATED_POWER_DIVIDER() * DELEGATED_POWER_DIVIDER()); +} // for test - it shouldnt pass // invariant ZeroAddressNoDelegation() @@ -206,3 +201,352 @@ rule selfDelegationCorrectness(address account) { assert propositionDelegate == 0 || propositionDelegate == account => isDelegatingProposition == false; } + +/** + Account1 and account2 are not delegating power +*/ + +rule vpTransferWhenBothNotDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + bool isBobDelegatingVoting = getDelegatingVoting(bob); + + require !isAliceDelegatingVoting && !isBobDelegatingVoting; + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + + assert alicePowerAfter == alicePowerBefore - amount; + assert bobPowerAfter == bobPowerBefore + amount; + assert charliePowerAfter == charliePowerBefore; +} + + +rule ppTransferWhenBothNotDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingProposition = getDelegatingProposition(alice); + // bool isAliceDelegatingProposition = getDelegatedProposition(alice); + + bool isBobDelegatingProposition = getDelegatingProposition(bob); + // bool isBobDelegatingProposition = getDelegatedProposition(bob); + + require !isAliceDelegatingProposition && !isBobDelegatingProposition; + + uint256 alicePowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, PROPOSITION_POWER()); + + assert alicePowerAfter == alicePowerBefore - amount; + assert bobPowerAfter == bobPowerBefore + amount; + assert charliePowerAfter == charliePowerBefore; +} + +rule vpDelegateWhenBothNotDelegating(address alice, address bob, address charlie) { + env e; + require alice == e.msg.sender; + require alice != 0 && bob != 0 && charlie != 0; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + bool isBobDelegatingVoting = getDelegatingVoting(bob); + + require !isAliceDelegatingVoting && !isBobDelegatingVoting; + + uint256 aliceBalance = balanceOf(alice); + uint256 bobBalance = balanceOf(bob); + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + + delegate(e, bob); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + + assert alicePowerAfter == alicePowerBefore - aliceBalance; + assert bobPowerAfter == bobPowerBefore + (aliceBalance / DELEGATED_POWER_DIVIDER()) * DELEGATED_POWER_DIVIDER(); + assert getVotingDelegate(alice) == bob; + assert charliePowerAfter == charliePowerBefore; +} + +rule ppDelegateWhenBothNotDelegating(address alice, address bob, address charlie) { + env e; + require alice == e.msg.sender; + require alice != 0 && bob != 0 && charlie != 0; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingProposition = getDelegatingProposition(alice); + bool isBobDelegatingProposition = getDelegatingProposition(bob); + + require !isAliceDelegatingProposition && !isBobDelegatingProposition; + + uint256 aliceBalance = balanceOf(alice); + uint256 bobBalance = balanceOf(bob); + + uint256 alicePowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); + + delegate(e, bob); + + uint256 alicePowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, PROPOSITION_POWER()); + + assert alicePowerAfter == alicePowerBefore - aliceBalance; + assert bobPowerAfter == bobPowerBefore + (aliceBalance / DELEGATED_POWER_DIVIDER()) * DELEGATED_POWER_DIVIDER(); + assert getPropositionDelegate(alice) == bob; + assert charliePowerAfter == charliePowerBefore; +} + +/** + Account1 is delegating power to delegatee1, account2 is not delegating power to anybody +*/ + +// token transfer from alice to bob + +rule vpTransferWhenOnlyOneIsDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + bool isBobDelegatingVoting = getDelegatingVoting(bob); + address aliceDelegate = getVotingDelegate(alice); + require aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != bob && aliceDelegate != charlie; + + require isAliceDelegatingVoting && !isBobDelegatingVoting; + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + // no delegation of anyone to Alice + require alicePowerBefore == 0; + + uint256 bobPowerBefore = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, VOTING_POWER()); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, VOTING_POWER()); + + // still zero + assert alicePowerBefore == alicePowerAfter; + assert aliceDelegatePowerAfter == + aliceDelegatePowerBefore - normalize(amount); + assert bobPowerAfter == bobPowerBefore + amount; + assert charliePowerBefore == charliePowerAfter; +} + +/** +before: 133160000000000 +amount: 30900000000001 +after: 102250000000000 + +*/ + +rule ppTransferWhenOnlyOneIsDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingProposition = getDelegatingProposition(alice); + bool isBobDelegatingProposition = getDelegatingProposition(bob); + address aliceDelegate = getPropositionDelegate(alice); + + require isAliceDelegatingProposition && !isBobDelegatingProposition; + + uint256 alicePowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + // no delegation of anyone to Alice + require alicePowerBefore == 0; + + uint256 bobPowerBefore = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + + // still zero + assert alicePowerBefore == alicePowerAfter; + // this is the equation in the properties.md, but it's wrong when amount == 10 ^ 10 + // assert aliceDelegatePowerAfter == + // aliceDelegatePowerBefore - (amount / DELEGATED_POWER_DIVIDER() * DELEGATED_POWER_DIVIDER()); + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore; + assert bobPowerAfter == bobPowerBefore + amount; + assert charliePowerBefore == charliePowerAfter; +} + +// After account1 will stop delegating his power to delegatee1 +rule vpStopDelegatingWhenOnlyOneIsDelegating(address alice, address charlie) { + env e; + require alice != charlie; + require alice == e.msg.sender; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + address aliceDelegate = getVotingDelegate(alice); + + require isAliceDelegatingVoting; + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, VOTING_POWER()); + + delegate(e, 0); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, VOTING_POWER()); + + assert alicePowerAfter == alicePowerBefore + balanceOf(alice); + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - balanceOf(alice); + assert charliePowerAfter == charliePowerBefore; +} + +rule ppStopDelegatingWhenOnlyOneIsDelegating(address alice, address charlie) { + env e; + require alice != charlie; + require alice == e.msg.sender; + + bool isAliceDelegatingProposition = getDelegatingProposition(alice); + address aliceDelegate = getPropositionDelegate(alice); + + require isAliceDelegatingProposition; + + uint256 alicePowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + + delegate(e, 0); + + uint256 alicePowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + + assert alicePowerAfter == alicePowerBefore + balanceOf(alice); + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - balanceOf(alice); + assert charliePowerAfter == charliePowerBefore; +} + +rule vpChangeDelegateWhenOnlyOneIsDelegating(address alice, address delegate2, address charlie) { + env e; + require alice != charlie && alice != delegate2 && charlie != delegate2; + require alice == e.msg.sender; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + address aliceDelegate = getVotingDelegate(alice); + require aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != delegate2 && delegate2 != 0; + + require isAliceDelegatingVoting; + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, VOTING_POWER()); + uint256 delegate2PowerBefore = getPowerCurrent(delegate2, VOTING_POWER()); + + delegate(e, delegate2); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, VOTING_POWER()); + uint256 delegate2PowerAfter = getPowerCurrent(delegate2, VOTING_POWER()); + address aliceDelegateAfter = getVotingDelegate(alice); + + assert alicePowerBefore == alicePowerAfter; + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - normalize(balanceOf(alice)); + assert delegate2PowerAfter == delegate2PowerBefore + normalize(balanceOf(alice)); + assert aliceDelegateAfter == delegate2; + assert charliePowerAfter == charliePowerBefore; +} + +// Account1 not delegating power to anybody, account2 is delegating power to delegatee2 + +rule vpOnlyAccount2IsDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + bool isBobDelegatingVoting = getDelegatingVoting(bob); + address bobDelegate = getVotingDelegate(bob); + require bobDelegate != bob && bobDelegate != 0 && bobDelegate != alice && bobDelegate != charlie; + + require !isAliceDelegatingVoting && isBobDelegatingVoting; + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, VOTING_POWER()); + require bobPowerBefore == 0; + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + uint256 bobDelegatePowerBefore = getPowerCurrent(bobDelegate, VOTING_POWER()); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + uint256 bobDelegatePowerAfter = getPowerCurrent(bobDelegate, VOTING_POWER()); + + assert alicePowerAfter == alicePowerBefore - amount; + assert bobPowerAfter == 0; + assert bobDelegatePowerAfter == bobDelegatePowerBefore + normalize(amount); + + assert charliePowerAfter == charliePowerBefore; +} + +//add for proposition + +// Account1 is delegating power to delegatee1, account2 is delegating power to delegatee2 +rule vpTransferWhenBothAreDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + bool isBobDelegatingVoting = getDelegatingVoting(bob); + require isAliceDelegatingVoting && isBobDelegatingVoting; + address aliceDelegate = getVotingDelegate(alice); + address bobDelegate = getVotingDelegate(bob); + require aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != bob && aliceDelegate != charlie; + require bobDelegate != bob && bobDelegate != 0 && bobDelegate != alice && bobDelegate != charlie; + require aliceDelegate != bobDelegate; + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, VOTING_POWER()); + uint256 bobDelegatePowerBefore = getPowerCurrent(bobDelegate, VOTING_POWER()); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, VOTING_POWER()); + uint256 bobDelegatePowerAfter = getPowerCurrent(bobDelegate, VOTING_POWER()); + + assert alicePowerAfter == alicePowerBefore; + assert bobPowerAfter == bobPowerBefore; + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - normalize(amount); + assert bobDelegatePowerAfter == bobDelegatePowerBefore + normalize (amount); +} \ No newline at end of file diff --git a/certora/specs/setup.spec b/certora/specs/setup.spec new file mode 100644 index 0000000..f3c54f9 --- /dev/null +++ b/certora/specs/setup.spec @@ -0,0 +1 @@ +// unit test, invariant, parametric test, ghost + hook, documentation \ No newline at end of file diff --git a/src/BaseAaveTokenV3.sol b/src/BaseAaveTokenV3.sol index e69de29..9b773a8 100644 --- a/src/BaseAaveTokenV3.sol +++ b/src/BaseAaveTokenV3.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {VersionedInitializable} from './utils/VersionedInitializable.sol'; + +import {BaseAaveToken} from './BaseAaveToken.sol'; + +abstract contract BaseAaveTokenV2 is BaseAaveToken, VersionedInitializable { + /// @dev owner => next valid nonce to submit with permit() + mapping(address => uint256) public _nonces; + + ///////// @dev DEPRECATED from AaveToken v1 ////////////////////////// + //////// kept for backwards compatibility with old storage layout //// + uint256[3] private ______DEPRECATED_FROM_AAVE_V1; + ///////// @dev END OF DEPRECATED from AaveToken v1 ////////////////////////// + + bytes32 public DOMAIN_SEPARATOR; + + ///////// @dev DEPRECATED from AaveToken v2 ////////////////////////// + //////// kept for backwards compatibility with old storage layout //// + uint256[4] private ______DEPRECATED_FROM_AAVE_V2; + ///////// @dev END OF DEPRECATED from AaveToken v2 ////////////////////////// + + bytes public constant EIP712_REVISION = bytes('1'); + bytes32 internal constant EIP712_DOMAIN = + keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'); + bytes32 public constant PERMIT_TYPEHASH = + keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'); + + uint256 public constant REVISION = 3; // TODO: CHECK, but most probably was 2 before + + /** + * @dev initializes the contract upon assignment to the InitializableAdminUpgradeabilityProxy + */ + function initialize() external initializer {} + + /** + * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md + * @param owner the owner of the funds + * @param spender the spender + * @param value the amount + * @param deadline the deadline timestamp, type(uint256).max for no deadline + * @param v signature param + * @param s signature param + * @param r signature param + */ + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external { + require(owner != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _nonces[owner]; + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline)) + ) + ); + + require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + unchecked { + // does not make sense to check because it's not realistic to reach uint256.max in nonce + _nonces[owner] = currentValidNonce + 1; + } + _approve(owner, spender, value); + } + + /** + * @dev returns the revision of the implementation contract + */ + function getRevision() internal pure override returns (uint256) { + return REVISION; + } +} From 249a7f2919b2535d08e4e89644ac7a55a83ef2da Mon Sep 17 00:00:00 2001 From: Michael M <91594326+MichaelMorami@users.noreply.github.com> Date: Wed, 25 May 2022 11:33:31 +0300 Subject: [PATCH 11/58] merge certora folder with complexit check + git ignore --- .gitignore | 8 ++- certora/harness/DummyERC20A.sol | 5 ++ certora/harness/DummyERC20B.sol | 5 ++ certora/harness/DummyERC20Impl.sol | 57 ++++++++++++++++ certora/scripts/run.sh | 7 ++ certora/specs/complexity.spec | 103 +++++++++++++++++++++++++++++ certora/specs/erc20.spec | 12 ++++ resource_errors.json | 3 + 8 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 certora/harness/DummyERC20A.sol create mode 100644 certora/harness/DummyERC20B.sol create mode 100644 certora/harness/DummyERC20Impl.sol create mode 100755 certora/scripts/run.sh create mode 100644 certora/specs/complexity.spec create mode 100644 certora/specs/erc20.spec create mode 100644 resource_errors.json diff --git a/.gitignore b/.gitignore index 1e5c2d4..a2edf0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ cache/ out/ .idea -node_modules -package-lock.json + +# certora +.certora* +.certora*.json +**.last_conf* +certora-logs diff --git a/certora/harness/DummyERC20A.sol b/certora/harness/DummyERC20A.sol new file mode 100644 index 0000000..188b926 --- /dev/null +++ b/certora/harness/DummyERC20A.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.0; +import "./DummyERC20Impl.sol"; + +contract DummyERC20A is DummyERC20Impl {} \ No newline at end of file diff --git a/certora/harness/DummyERC20B.sol b/certora/harness/DummyERC20B.sol new file mode 100644 index 0000000..0f97f1e --- /dev/null +++ b/certora/harness/DummyERC20B.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.0; +import "./DummyERC20Impl.sol"; + +contract DummyERC20B is DummyERC20Impl {} \ No newline at end of file diff --git a/certora/harness/DummyERC20Impl.sol b/certora/harness/DummyERC20Impl.sol new file mode 100644 index 0000000..42e7f23 --- /dev/null +++ b/certora/harness/DummyERC20Impl.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.0; + +// with mint +contract DummyERC20Impl { + uint256 t; + mapping (address => uint256) b; + mapping (address => mapping (address => uint256)) a; + + string public name; + string public symbol; + uint public decimals; + + function myAddress() public returns (address) { + return address(this); + } + + function add(uint a, uint b) internal pure returns (uint256) { + uint c = a +b; + require (c >= a); + return c; + } + function sub(uint a, uint b) internal pure returns (uint256) { + require (a>=b); + return a-b; + } + + function totalSupply() external view returns (uint256) { + return t; + } + function balanceOf(address account) external view returns (uint256) { + return b[account]; + } + function transfer(address recipient, uint256 amount) external returns (bool) { + b[msg.sender] = sub(b[msg.sender], amount); + b[recipient] = add(b[recipient], amount); + return true; + } + function allowance(address owner, address spender) external view returns (uint256) { + return a[owner][spender]; + } + function approve(address spender, uint256 amount) external returns (bool) { + a[msg.sender][spender] = amount; + return true; + } + + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool) { + b[sender] = sub(b[sender], amount); + b[recipient] = add(b[recipient], amount); + a[sender][msg.sender] = sub(a[sender][msg.sender], amount); + return true; + } +} \ No newline at end of file diff --git a/certora/scripts/run.sh b/certora/scripts/run.sh new file mode 100755 index 0000000..03cffd3 --- /dev/null +++ b/certora/scripts/run.sh @@ -0,0 +1,7 @@ +certoraRun src/AaveTokenV3.sol:AaveTokenV3 \ + --verify AaveTokenV3:certora/specs/complexity.spec \ + --solc solc8.13 \ + --optimistic_loop \ + --staging \ + --msg "AaveTokenV3 complexity check" + \ No newline at end of file diff --git a/certora/specs/complexity.spec b/certora/specs/complexity.spec new file mode 100644 index 0000000..0645e07 --- /dev/null +++ b/certora/specs/complexity.spec @@ -0,0 +1,103 @@ +import "erc20.spec" + +rule sanity(method f) +{ + env e; + calldataarg args; + f(e,args); + assert false; +} + + +/* +This rule find which functions never reverts. + +*/ + + +rule noRevert(method f) +description "$f has reverting paths" +{ + env e; + calldataarg arg; + require e.msg.value == 0; + f@withrevert(e, arg); + assert !lastReverted, "${f.selector} can revert"; +} + + +rule alwaysRevert(method f) +description "$f has reverting paths" +{ + env e; + calldataarg arg; + f@withrevert(e, arg); + assert lastReverted, "${f.selector} succeeds"; +} + + +/* +This rule find which functions that can be called, may fail due to someone else calling a function right before. + +This is n expensive rule - might fail on the demo site on big contracts +*/ + +rule simpleFrontRunning(method f, address privileged) filtered { f-> !f.isView } +description "$f can no longer be called after it had been called by someone else" +{ + env e1; + calldataarg arg; + require e1.msg.sender == privileged; + + storage initialStorage = lastStorage; + f(e1, arg); + bool firstSucceeded = !lastReverted; + + env e2; + calldataarg arg2; + require e2.msg.sender != e1.msg.sender; + f(e2, arg2) at initialStorage; + f@withrevert(e1, arg); + bool succeeded = !lastReverted; + + assert succeeded, "${f.selector} can be not be called if was called by someone else"; +} + + +/* +This rule find which functions are privileged. +A function is privileged if there is only one address that can call it. + +The rules finds this by finding which functions can be called by two different users. + +*/ + + +rule privilegedOperation(method f, address privileged) +description "$f can be called by more than one user without reverting" +{ + env e1; + calldataarg arg; + require e1.msg.sender == privileged; + + storage initialStorage = lastStorage; + f@withrevert(e1, arg); // privileged succeeds executing candidate privileged operation. + bool firstSucceeded = !lastReverted; + + env e2; + calldataarg arg2; + require e2.msg.sender != privileged; + f@withrevert(e2, arg2) at initialStorage; // unprivileged + bool secondSucceeded = !lastReverted; + + assert !(firstSucceeded && secondSucceeded), "${f.selector} can be called by both ${e1.msg.sender} and ${e2.msg.sender}, so it is not privileged"; +} + +rule whoChangedBalanceOf(method f, address u) { + env eB; + env eF; + calldataarg args; + uint256 before = balanceOf(eB, u); + f(eF,args); + assert balanceOf(eB, u) == before, "balanceOf changed"; +} \ No newline at end of file diff --git a/certora/specs/erc20.spec b/certora/specs/erc20.spec new file mode 100644 index 0000000..b12fec1 --- /dev/null +++ b/certora/specs/erc20.spec @@ -0,0 +1,12 @@ +// erc20 methods +methods { + name() returns (string) => DISPATCHER(true) + symbol() returns (string) => DISPATCHER(true) + decimals() returns (string) => DISPATCHER(true) + totalSupply() returns (uint256) => DISPATCHER(true) + balanceOf(address) returns (uint256) => DISPATCHER(true) + allowance(address,address) returns (uint) => DISPATCHER(true) + approve(address,uint256) returns (bool) => DISPATCHER(true) + transfer(address,uint256) returns (bool) => DISPATCHER(true) + transferFrom(address,address,uint256) returns (bool) => DISPATCHER(true) +} diff --git a/resource_errors.json b/resource_errors.json new file mode 100644 index 0000000..d9bd792 --- /dev/null +++ b/resource_errors.json @@ -0,0 +1,3 @@ +{ + "topics": [] +} \ No newline at end of file From d161df8c13175489b71337f6bea9cfb18f60bdb6 Mon Sep 17 00:00:00 2001 From: Michael M <91594326+MichaelMorami@users.noreply.github.com> Date: Wed, 25 May 2022 11:44:51 +0300 Subject: [PATCH 12/58] removing unnecessary files and updating contract --- certora/harness/DummyERC20A.sol | 5 -- certora/harness/DummyERC20B.sol | 5 -- certora/harness/DummyERC20Impl.sol | 57 -------------------- certora/scripts/{run.sh => runComplexity.sh} | 0 certora/specs/erc20.spec | 12 ----- 5 files changed, 79 deletions(-) delete mode 100644 certora/harness/DummyERC20A.sol delete mode 100644 certora/harness/DummyERC20B.sol delete mode 100644 certora/harness/DummyERC20Impl.sol rename certora/scripts/{run.sh => runComplexity.sh} (100%) delete mode 100644 certora/specs/erc20.spec diff --git a/certora/harness/DummyERC20A.sol b/certora/harness/DummyERC20A.sol deleted file mode 100644 index 188b926..0000000 --- a/certora/harness/DummyERC20A.sol +++ /dev/null @@ -1,5 +0,0 @@ -// SPDX-License-Identifier: agpl-3.0 -pragma solidity ^0.8.0; -import "./DummyERC20Impl.sol"; - -contract DummyERC20A is DummyERC20Impl {} \ No newline at end of file diff --git a/certora/harness/DummyERC20B.sol b/certora/harness/DummyERC20B.sol deleted file mode 100644 index 0f97f1e..0000000 --- a/certora/harness/DummyERC20B.sol +++ /dev/null @@ -1,5 +0,0 @@ -// SPDX-License-Identifier: agpl-3.0 -pragma solidity ^0.8.0; -import "./DummyERC20Impl.sol"; - -contract DummyERC20B is DummyERC20Impl {} \ No newline at end of file diff --git a/certora/harness/DummyERC20Impl.sol b/certora/harness/DummyERC20Impl.sol deleted file mode 100644 index 42e7f23..0000000 --- a/certora/harness/DummyERC20Impl.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: agpl-3.0 -pragma solidity ^0.8.0; - -// with mint -contract DummyERC20Impl { - uint256 t; - mapping (address => uint256) b; - mapping (address => mapping (address => uint256)) a; - - string public name; - string public symbol; - uint public decimals; - - function myAddress() public returns (address) { - return address(this); - } - - function add(uint a, uint b) internal pure returns (uint256) { - uint c = a +b; - require (c >= a); - return c; - } - function sub(uint a, uint b) internal pure returns (uint256) { - require (a>=b); - return a-b; - } - - function totalSupply() external view returns (uint256) { - return t; - } - function balanceOf(address account) external view returns (uint256) { - return b[account]; - } - function transfer(address recipient, uint256 amount) external returns (bool) { - b[msg.sender] = sub(b[msg.sender], amount); - b[recipient] = add(b[recipient], amount); - return true; - } - function allowance(address owner, address spender) external view returns (uint256) { - return a[owner][spender]; - } - function approve(address spender, uint256 amount) external returns (bool) { - a[msg.sender][spender] = amount; - return true; - } - - function transferFrom( - address sender, - address recipient, - uint256 amount - ) external returns (bool) { - b[sender] = sub(b[sender], amount); - b[recipient] = add(b[recipient], amount); - a[sender][msg.sender] = sub(a[sender][msg.sender], amount); - return true; - } -} \ No newline at end of file diff --git a/certora/scripts/run.sh b/certora/scripts/runComplexity.sh similarity index 100% rename from certora/scripts/run.sh rename to certora/scripts/runComplexity.sh diff --git a/certora/specs/erc20.spec b/certora/specs/erc20.spec deleted file mode 100644 index b12fec1..0000000 --- a/certora/specs/erc20.spec +++ /dev/null @@ -1,12 +0,0 @@ -// erc20 methods -methods { - name() returns (string) => DISPATCHER(true) - symbol() returns (string) => DISPATCHER(true) - decimals() returns (string) => DISPATCHER(true) - totalSupply() returns (uint256) => DISPATCHER(true) - balanceOf(address) returns (uint256) => DISPATCHER(true) - allowance(address,address) returns (uint) => DISPATCHER(true) - approve(address,uint256) returns (bool) => DISPATCHER(true) - transfer(address,uint256) returns (bool) => DISPATCHER(true) - transferFrom(address,address,uint256) returns (bool) => DISPATCHER(true) -} From 676fe4a1ae6113c9cb758c9d9ddc74104ef405f5 Mon Sep 17 00:00:00 2001 From: Michael M <91594326+MichaelMorami@users.noreply.github.com> Date: Wed, 25 May 2022 11:33:31 +0300 Subject: [PATCH 13/58] certora folder with complexit check + git ignore --- certora/harness/DummyERC20A.sol | 5 +++ certora/harness/DummyERC20B.sol | 5 +++ certora/harness/DummyERC20Impl.sol | 57 ++++++++++++++++++++++++++++++ certora/scripts/run.sh | 7 ++++ certora/specs/erc20.spec | 12 +++++++ 5 files changed, 86 insertions(+) create mode 100644 certora/harness/DummyERC20A.sol create mode 100644 certora/harness/DummyERC20B.sol create mode 100644 certora/harness/DummyERC20Impl.sol create mode 100755 certora/scripts/run.sh create mode 100644 certora/specs/erc20.spec diff --git a/certora/harness/DummyERC20A.sol b/certora/harness/DummyERC20A.sol new file mode 100644 index 0000000..188b926 --- /dev/null +++ b/certora/harness/DummyERC20A.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.0; +import "./DummyERC20Impl.sol"; + +contract DummyERC20A is DummyERC20Impl {} \ No newline at end of file diff --git a/certora/harness/DummyERC20B.sol b/certora/harness/DummyERC20B.sol new file mode 100644 index 0000000..0f97f1e --- /dev/null +++ b/certora/harness/DummyERC20B.sol @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.0; +import "./DummyERC20Impl.sol"; + +contract DummyERC20B is DummyERC20Impl {} \ No newline at end of file diff --git a/certora/harness/DummyERC20Impl.sol b/certora/harness/DummyERC20Impl.sol new file mode 100644 index 0000000..42e7f23 --- /dev/null +++ b/certora/harness/DummyERC20Impl.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.0; + +// with mint +contract DummyERC20Impl { + uint256 t; + mapping (address => uint256) b; + mapping (address => mapping (address => uint256)) a; + + string public name; + string public symbol; + uint public decimals; + + function myAddress() public returns (address) { + return address(this); + } + + function add(uint a, uint b) internal pure returns (uint256) { + uint c = a +b; + require (c >= a); + return c; + } + function sub(uint a, uint b) internal pure returns (uint256) { + require (a>=b); + return a-b; + } + + function totalSupply() external view returns (uint256) { + return t; + } + function balanceOf(address account) external view returns (uint256) { + return b[account]; + } + function transfer(address recipient, uint256 amount) external returns (bool) { + b[msg.sender] = sub(b[msg.sender], amount); + b[recipient] = add(b[recipient], amount); + return true; + } + function allowance(address owner, address spender) external view returns (uint256) { + return a[owner][spender]; + } + function approve(address spender, uint256 amount) external returns (bool) { + a[msg.sender][spender] = amount; + return true; + } + + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool) { + b[sender] = sub(b[sender], amount); + b[recipient] = add(b[recipient], amount); + a[sender][msg.sender] = sub(a[sender][msg.sender], amount); + return true; + } +} \ No newline at end of file diff --git a/certora/scripts/run.sh b/certora/scripts/run.sh new file mode 100755 index 0000000..03cffd3 --- /dev/null +++ b/certora/scripts/run.sh @@ -0,0 +1,7 @@ +certoraRun src/AaveTokenV3.sol:AaveTokenV3 \ + --verify AaveTokenV3:certora/specs/complexity.spec \ + --solc solc8.13 \ + --optimistic_loop \ + --staging \ + --msg "AaveTokenV3 complexity check" + \ No newline at end of file diff --git a/certora/specs/erc20.spec b/certora/specs/erc20.spec new file mode 100644 index 0000000..b12fec1 --- /dev/null +++ b/certora/specs/erc20.spec @@ -0,0 +1,12 @@ +// erc20 methods +methods { + name() returns (string) => DISPATCHER(true) + symbol() returns (string) => DISPATCHER(true) + decimals() returns (string) => DISPATCHER(true) + totalSupply() returns (uint256) => DISPATCHER(true) + balanceOf(address) returns (uint256) => DISPATCHER(true) + allowance(address,address) returns (uint) => DISPATCHER(true) + approve(address,uint256) returns (bool) => DISPATCHER(true) + transfer(address,uint256) returns (bool) => DISPATCHER(true) + transferFrom(address,address,uint256) returns (bool) => DISPATCHER(true) +} From abe6a90fef31984356e43f7ab0cd4bcaa54c7ae8 Mon Sep 17 00:00:00 2001 From: Michael M <91594326+MichaelMorami@users.noreply.github.com> Date: Wed, 25 May 2022 11:45:53 +0300 Subject: [PATCH 14/58] removing unnecessary files --- certora/harness/DummyERC20A.sol | 5 --- certora/harness/DummyERC20B.sol | 5 --- certora/harness/DummyERC20Impl.sol | 57 ------------------------------ certora/scripts/run.sh | 7 ---- certora/specs/erc20.spec | 12 ------- 5 files changed, 86 deletions(-) delete mode 100644 certora/harness/DummyERC20A.sol delete mode 100644 certora/harness/DummyERC20B.sol delete mode 100644 certora/harness/DummyERC20Impl.sol delete mode 100755 certora/scripts/run.sh delete mode 100644 certora/specs/erc20.spec diff --git a/certora/harness/DummyERC20A.sol b/certora/harness/DummyERC20A.sol deleted file mode 100644 index 188b926..0000000 --- a/certora/harness/DummyERC20A.sol +++ /dev/null @@ -1,5 +0,0 @@ -// SPDX-License-Identifier: agpl-3.0 -pragma solidity ^0.8.0; -import "./DummyERC20Impl.sol"; - -contract DummyERC20A is DummyERC20Impl {} \ No newline at end of file diff --git a/certora/harness/DummyERC20B.sol b/certora/harness/DummyERC20B.sol deleted file mode 100644 index 0f97f1e..0000000 --- a/certora/harness/DummyERC20B.sol +++ /dev/null @@ -1,5 +0,0 @@ -// SPDX-License-Identifier: agpl-3.0 -pragma solidity ^0.8.0; -import "./DummyERC20Impl.sol"; - -contract DummyERC20B is DummyERC20Impl {} \ No newline at end of file diff --git a/certora/harness/DummyERC20Impl.sol b/certora/harness/DummyERC20Impl.sol deleted file mode 100644 index 42e7f23..0000000 --- a/certora/harness/DummyERC20Impl.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: agpl-3.0 -pragma solidity ^0.8.0; - -// with mint -contract DummyERC20Impl { - uint256 t; - mapping (address => uint256) b; - mapping (address => mapping (address => uint256)) a; - - string public name; - string public symbol; - uint public decimals; - - function myAddress() public returns (address) { - return address(this); - } - - function add(uint a, uint b) internal pure returns (uint256) { - uint c = a +b; - require (c >= a); - return c; - } - function sub(uint a, uint b) internal pure returns (uint256) { - require (a>=b); - return a-b; - } - - function totalSupply() external view returns (uint256) { - return t; - } - function balanceOf(address account) external view returns (uint256) { - return b[account]; - } - function transfer(address recipient, uint256 amount) external returns (bool) { - b[msg.sender] = sub(b[msg.sender], amount); - b[recipient] = add(b[recipient], amount); - return true; - } - function allowance(address owner, address spender) external view returns (uint256) { - return a[owner][spender]; - } - function approve(address spender, uint256 amount) external returns (bool) { - a[msg.sender][spender] = amount; - return true; - } - - function transferFrom( - address sender, - address recipient, - uint256 amount - ) external returns (bool) { - b[sender] = sub(b[sender], amount); - b[recipient] = add(b[recipient], amount); - a[sender][msg.sender] = sub(a[sender][msg.sender], amount); - return true; - } -} \ No newline at end of file diff --git a/certora/scripts/run.sh b/certora/scripts/run.sh deleted file mode 100755 index 03cffd3..0000000 --- a/certora/scripts/run.sh +++ /dev/null @@ -1,7 +0,0 @@ -certoraRun src/AaveTokenV3.sol:AaveTokenV3 \ - --verify AaveTokenV3:certora/specs/complexity.spec \ - --solc solc8.13 \ - --optimistic_loop \ - --staging \ - --msg "AaveTokenV3 complexity check" - \ No newline at end of file diff --git a/certora/specs/erc20.spec b/certora/specs/erc20.spec deleted file mode 100644 index b12fec1..0000000 --- a/certora/specs/erc20.spec +++ /dev/null @@ -1,12 +0,0 @@ -// erc20 methods -methods { - name() returns (string) => DISPATCHER(true) - symbol() returns (string) => DISPATCHER(true) - decimals() returns (string) => DISPATCHER(true) - totalSupply() returns (uint256) => DISPATCHER(true) - balanceOf(address) returns (uint256) => DISPATCHER(true) - allowance(address,address) returns (uint) => DISPATCHER(true) - approve(address,uint256) returns (bool) => DISPATCHER(true) - transfer(address,uint256) returns (bool) => DISPATCHER(true) - transferFrom(address,address,uint256) returns (bool) => DISPATCHER(true) -} From 72013e21a3f3a0533d6b59aed9c61551bf2e54f1 Mon Sep 17 00:00:00 2001 From: Michael M <91594326+MichaelMorami@users.noreply.github.com> Date: Mon, 20 Jun 2022 11:43:22 +0300 Subject: [PATCH 15/58] starting setup --- certora/harness/AaveTokenV3Harness.sol | 9 +++++++++ certora/scripts/verifyBgdSpec.sh | 13 +++++++++++++ certora/specs/bgdSpec.spec | 20 ++++++++++++++++++++ properties.md | 1 - 4 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 certora/harness/AaveTokenV3Harness.sol create mode 100755 certora/scripts/verifyBgdSpec.sh create mode 100644 certora/specs/bgdSpec.spec diff --git a/certora/harness/AaveTokenV3Harness.sol b/certora/harness/AaveTokenV3Harness.sol new file mode 100644 index 0000000..6ad679c --- /dev/null +++ b/certora/harness/AaveTokenV3Harness.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {AaveTokenV3} from '../../src/AaveTokenV3.sol'; + +contract AaveTokenV3Harness is AaveTokenV3 { + +} \ No newline at end of file diff --git a/certora/scripts/verifyBgdSpec.sh b/certora/scripts/verifyBgdSpec.sh new file mode 100755 index 0000000..59e58e5 --- /dev/null +++ b/certora/scripts/verifyBgdSpec.sh @@ -0,0 +1,13 @@ +if [[ "$1" ]] +then + RULE="--rule $1" +fi + +certoraRun src/AaveTokenV3.sol:AaveTokenV3 \ + --verify AaveTokenV3:certora/specs/bdgSpec.spec \ + --solc solc8.13 \ + --optimistic_loop \ + --rule $1 \ + --staging \ + --msg "AaveTokenV3:bgdSpec.spec $1" + \ No newline at end of file diff --git a/certora/specs/bgdSpec.spec b/certora/specs/bgdSpec.spec new file mode 100644 index 0000000..23ede7e --- /dev/null +++ b/certora/specs/bgdSpec.spec @@ -0,0 +1,20 @@ +methods{ + _votingDelegateeV2(address) returns (address) + _propositionDelegateeV2(address) returns (address) + DELEGATED_POWER_DIVIDER() returns (uint256) + DELEGATE_BY_TYPE_TYPEHASH() returns (bytes32) + DELEGATE_TYPEHASH() returns (bytes32) + // _delegationMoveByType(uint104, uint104, address, GovernancePowerType delegationType, function(uint72, uint72) returns (uint72) operation) + // _delegationMove(address, DelegationAwareBalance userState, uint104, uint104, function(uint72, uint72) returns (uint72) operation) + _transferWithDelegation(address, address, uint256) + // _getDelegatedPowerByType(DelegationAwareBalance userState, GovernancePowerType delegationType) returns (uint72) + // _getDelegateeByType(address, DelegationAwareBalance userState, GovernancePowerType delegationType) returns (address) + // _updateDelegateeByType(address, GovernancePowerType delegationType, address) + // _updateDelegationFlagByType(DelegationAwareBalance userState, GovernancePowerType delegationType, bool) returns (DelegationAwareBalance) + // _delegateByType(address, address, GovernancePowerType delegationType) + // delegateByType(address, GovernancePowerType delegationType) + delegate(address delegatee) + // getDelegateeByType(address delegator, GovernancePowerType delegationType) returns (address) + // getPowerCurrent(address, GovernancePowerType delegationType) returns (uint256) + // metaDelegateByType(address, address, GovernancePowerType delegationType, uint256, uint8, bytes32, bytes32) + metaDelegate(address, address, uint256, uint8, bytes32, bytes32) \ No newline at end of file diff --git a/properties.md b/properties.md index 41a1dae..e7ceefc 100644 --- a/properties.md +++ b/properties.md @@ -30,7 +30,6 @@ $t_1$ → the state of the system after a transaction. ## General rules -- The total power (of one type) of all users in the system is less or equal than the sum of balances of all AAVE holders (totalSupply of AAVE token): $$\sum powerOfAccount_i <= \sum balanceOf(account_i)$$ - If an account is delegating a power to itself or to `address(0)`, that means that account is not delegating that power to anybody: $$powerOfAccountX = (accountXDelegatingPower \ ? \ 0 : balanceOf(accountX)) + From 9cb5293a076aea13c0687f887f1b33009422ec57 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Tue, 28 Jun 2022 18:52:38 +0300 Subject: [PATCH 16/58] feat: initial harness and unit test --- certora/properties.md | 33 ++ certora/scripts/verifyBgdSpec.sh | 7 +- certora/specs/bgdSpec.spec | 107 ++++++- certora/specs/complexity.spec | 18 +- src/AaveTokenV3.sol | 2 + src/BaseAaveTokenV3.sol | 0 src/harness/AaveTokenV3Harness.sol | 421 +++++++++++++++++++++++++ src/harness/BaseAaveTokenHarness.sol | 301 ++++++++++++++++++ src/harness/BaseAaveTokenV2Harness.sol | 86 +++++ 9 files changed, 962 insertions(+), 13 deletions(-) create mode 100644 certora/properties.md create mode 100644 src/BaseAaveTokenV3.sol create mode 100644 src/harness/AaveTokenV3Harness.sol create mode 100644 src/harness/BaseAaveTokenHarness.sol create mode 100644 src/harness/BaseAaveTokenV2Harness.sol diff --git a/certora/properties.md b/certora/properties.md new file mode 100644 index 0000000..f1e0a71 --- /dev/null +++ b/certora/properties.md @@ -0,0 +1,33 @@ +## functions summary + +### internals + +- \_delegationMoveByType internal: apply operation on proper delegation balance +- \_delegationMove: delegation move by type _voting_, delegation move by type _proposition_ +- \_transferWithDelegation: delegation move with `-` op for `from`, delegation move with `+` op for `to` +- \_getDelegatedPowerByType: returns the voting/proposition power by type +- \_getDelegateeByType: returns the delegate address if user is delegating, or 0 if not +- \_updateDelegateeByType: updates the delegate for user. if delegate == user, then delegate is recorded as 0. +- \_updateDelegationFlagByType: updates the user's flag for delegating by type +- \_delegateByType: the whole delegation process - update voting power and flags + +### externals +- delegateByType: call the internal +- delegate(): call the internal on both types +- getDelegateeByType(): call the internal +- getPowerCurrent(): (if not delegating ) user balance + delegated balance +- metaDelegateByType(): delegate voting power using a signature from the delegator +- metaDelegate: metaDelegateByType for both types + +## ideas for properties + +- transfer where from == to doesn't change delegation balances +- address 0 has no voting/prop power +- \_transferWithDelegation removes delegation from `from` but does nothing on `to` if it's 0. + who can call this? +- delegation flag <=> delegatee != 0 +- anyone can delegate to zero. which means they're forfeiting the voting power. + + +## properties for Aave Token v3 spec + diff --git a/certora/scripts/verifyBgdSpec.sh b/certora/scripts/verifyBgdSpec.sh index 59e58e5..f8fc77c 100755 --- a/certora/scripts/verifyBgdSpec.sh +++ b/certora/scripts/verifyBgdSpec.sh @@ -3,11 +3,12 @@ then RULE="--rule $1" fi -certoraRun src/AaveTokenV3.sol:AaveTokenV3 \ - --verify AaveTokenV3:certora/specs/bdgSpec.spec \ +certoraRun src/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ + --verify AaveTokenV3:certora/specs/bgdSpec.spec \ + --rule $1 \ --solc solc8.13 \ --optimistic_loop \ - --rule $1 \ + --send_only \ --staging \ --msg "AaveTokenV3:bgdSpec.spec $1" \ No newline at end of file diff --git a/certora/specs/bgdSpec.spec b/certora/specs/bgdSpec.spec index 23ede7e..2af0171 100644 --- a/certora/specs/bgdSpec.spec +++ b/certora/specs/bgdSpec.spec @@ -1,4 +1,15 @@ +// using DelegationAwareBalance from "./BaseAaveToken.sol"; + +// issues: +// for enum, just use 0 (voting) and 1 (proposition) or local definition +// for struct use harness that replaces reads and writes with solidity functions + methods{ + totalSupply() returns (uint256) envfree + balanceOf(address addr) returns (uint256) envfree + transfer(address to, uint256 amount) returns (bool) + transferFrom(address from, address to) returns (bool) + _votingDelegateeV2(address) returns (address) _propositionDelegateeV2(address) returns (address) DELEGATED_POWER_DIVIDER() returns (uint256) @@ -17,4 +28,98 @@ methods{ // getDelegateeByType(address delegator, GovernancePowerType delegationType) returns (address) // getPowerCurrent(address, GovernancePowerType delegationType) returns (uint256) // metaDelegateByType(address, address, GovernancePowerType delegationType, uint256, uint8, bytes32, bytes32) - metaDelegate(address, address, uint256, uint8, bytes32, bytes32) \ No newline at end of file + metaDelegate(address, address, uint256, uint8, bytes32, bytes32) + // enum GovernancePowerType { + // VOTING, + // PROPOSITION + // } + getPowerCurrent(address user, uint8 delegationType) returns (uint256) envfree + + getBalance(address user) returns (uint104) envfree + getDelegatedPropositionBalance(address user) returns (uint72) envfree + getDelegatedVotingBalance(address user) returns (uint72) envfree + getDelegatingProposition(address user) returns (bool) envfree + getDelegatingVoting(address user) returns (bool) envfree +} + +definition VOTING_POWER() returns uint8 = 0; +definition PROPOSITION_POWER() returns uint8 = 1; + +// for test - it shouldnt pass +// invariant ZeroAddressNoDelegation() +// getPowerCurrent(0, 0) == 0 && getPowerCurrent(0, 1) == 0 + +// The total power (of one type) of all users in the system is less or equal than +// the sum of balances of all AAVE holders (totalSupply of AAVE token) + +// accumulator for a sum of proposition voting power +ghost mathint sumDelegatedProposition { + init_state axiom forall uint256 t. sumDelegatedProposition == 0; +} + +ghost mathint sumBalances { + init_state axiom forall uint256 t. sumBalances == 0; +} + +/* + update proposition balance on each store + */ +hook Sstore _balances[KEY address user].delegatedPropositionBalance uint72 balance + (uint72 old_balance) STORAGE { + sumDelegatedProposition = sumDelegatedProposition + to_mathint(balance) - to_mathint(old_balance); + } + +// try to rewrite using power.spec in aave-tokenv2 customer code +hook Sstore _balances[KEY address user].balance uint104 balance + (uint104 old_balance) STORAGE { + sumBalances = sumBalances + to_mathint(balance) - to_mathint(old_balance); + } + +invariant sumDelegatedPropositionCorrectness() sumDelegatedProposition <= totalSupply() { + // fails + preserved transfer(address to, uint256 amount) with (env e) + { + require(balanceOf(e.msg.sender) + balanceOf(to)) < totalSupply(); + } + preserved transferFrom(address from, address to, uint256 amount) with (env e) + { + require(balanceOf(from) + balanceOf(to)) < totalSupply(); + } +} + +rule totalSupplyCorrectness(method f) { + env e; + calldataarg args; + + require sumBalances == to_mathint(totalSupply()); + f(e, args); + assert sumBalances == to_mathint(totalSupply()); +} + +// doesn't work cause we can start with a state in which an address can have delegated balance field +// larger than total supply. +// rule sumDelegatedPropositionCorrect(method f) { +// env e; +// calldataarg args; + +// uint256 supplyBefore = totalSupply(); +// require sumDelegatedProposition <= supplyBefore; +// f(e, args); +// uint256 supplyAfter = totalSupply(); +// assert sumDelegatedProposition <= supplyAfter; +// } + + +rule transferUnitTest() { + env e; + address to; + uint256 amount; + require(to != e.msg.sender); + + uint256 powerToBefore = getPowerCurrent(to, VOTING_POWER()); + uint256 powerSenderBefore = getPowerCurrent(e.msg.sender, VOTING_POWER()); + transfer(e, to, amount); + uint256 powerToAfter = getPowerCurrent(to, VOTING_POWER()); + + assert powerToAfter == powerToBefore + powerSenderBefore; +} \ No newline at end of file diff --git a/certora/specs/complexity.spec b/certora/specs/complexity.spec index 0645e07..40a009a 100644 --- a/certora/specs/complexity.spec +++ b/certora/specs/complexity.spec @@ -1,4 +1,4 @@ -import "erc20.spec" +// import "erc20.spec" rule sanity(method f) { @@ -93,11 +93,11 @@ description "$f can be called by more than one user without reverting" assert !(firstSucceeded && secondSucceeded), "${f.selector} can be called by both ${e1.msg.sender} and ${e2.msg.sender}, so it is not privileged"; } -rule whoChangedBalanceOf(method f, address u) { - env eB; - env eF; - calldataarg args; - uint256 before = balanceOf(eB, u); - f(eF,args); - assert balanceOf(eB, u) == before, "balanceOf changed"; -} \ No newline at end of file +// rule whoChangedBalanceOf(method f, address u) { +// env eB; +// env eF; +// calldataarg args; +// uint256 before = balanceOf(eB, u); +// f(eF,args); +// assert balanceOf(eB, u) == before, "balanceOf changed"; +// } \ No newline at end of file diff --git a/src/AaveTokenV3.sol b/src/AaveTokenV3.sol index 89b4a1a..94c5a46 100644 --- a/src/AaveTokenV3.sol +++ b/src/AaveTokenV3.sol @@ -6,6 +6,8 @@ import {IGovernancePowerDelegationToken} from './interfaces/IGovernancePowerDele import {BaseAaveTokenV2} from './BaseAaveTokenV2.sol'; contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { + + mapping(address => address) internal _votingDelegateeV2; mapping(address => address) internal _propositionDelegateeV2; diff --git a/src/BaseAaveTokenV3.sol b/src/BaseAaveTokenV3.sol new file mode 100644 index 0000000..e69de29 diff --git a/src/harness/AaveTokenV3Harness.sol b/src/harness/AaveTokenV3Harness.sol new file mode 100644 index 0000000..23f3ec6 --- /dev/null +++ b/src/harness/AaveTokenV3Harness.sol @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {VersionedInitializable} from '../utils/VersionedInitializable.sol'; + +import {IGovernancePowerDelegationToken} from '../interfaces/IGovernancePowerDelegationToken.sol'; +import {BaseAaveTokenV2} from './BaseAaveTokenV2Harness.sol'; +import {MathUtils} from '../utils/MathUtils.sol'; + +contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { + + + mapping(address => address) internal _votingDelegateeV2; + mapping(address => address) internal _propositionDelegateeV2; + + uint256 public constant DELEGATED_POWER_DIVIDER = 10**10; + + bytes32 public constant DELEGATE_BY_TYPE_TYPEHASH = + keccak256( + 'DelegateByType(address delegator,address delegatee,GovernancePowerType delegationType,uint256 nonce,uint256 deadline)' + ); + bytes32 public constant DELEGATE_TYPEHASH = + keccak256('Delegate(address delegator,address delegatee,uint256 nonce,uint256 deadline)'); + + /** + Harness section - replace struct reads and writes with function calls + */ + +// struct DelegationAwareBalance { +// uint104 balance; +// uint72 delegatedPropositionBalance; +// uint72 delegatedVotingBalance; +// bool delegatingProposition; +// bool delegatingVoting; +// } + + function _setBalance(address user, uint104 balance) internal { + _balances[user].balance = balance; + } + + function getBalance(address user) view public returns (uint104) { + return _balances[user].balance; + } + + function _setDelegatedPropositionBalance(address user, uint72 dpb) internal { + _balances[user].delegatedPropositionBalance = dpb; + } + + function getDelegatedPropositionBalance(address user) view public returns (uint72) { + return _balances[user].delegatedPropositionBalance; + } + + function _setDelegatedVotingBalance(address user, uint72 dvb) internal { + _balances[user].delegatedVotingBalance = dvb; + } + + function getDelegatedVotingBalance(address user) view public returns (uint72) { + return _balances[user].delegatedVotingBalance; + } + + function _setDelegatingProposition(address user, bool _delegating) internal { + _balances[user].delegatingProposition = _delegating; + } + + function getDelegatingProposition(address user) view public returns (bool) { + return _balances[user].delegatingProposition; + } + + function _setDelegatingVoting(address user, bool _delegating) internal { + _balances[user].delegatingVoting = _delegating; + } + + function getDelegatingVoting(address user) view public returns (bool) { + return _balances[user].delegatingVoting; + } + + /** + End of harness section + */ + + /** + * @dev changing one of delegated governance powers of delegatee depending on the delegator balance change + * @param userBalanceBefore delegator balance before operation + * @param userBalanceAfter delegator balance after operation + * @param delegatee the user whom delegated governance power will be changed + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + * @param operation math operation which will be applied depends on increasing or decreasing of the delegator balance (plus, minus) + **/ + function _delegationMoveByType( + uint104 userBalanceBefore, + uint104 userBalanceAfter, + address delegatee, + GovernancePowerType delegationType, + function(uint72, uint72) returns (uint72) operation + ) internal { + if (delegatee == address(0)) return; + + // @dev to make delegated balance fit into uin72 we're decreasing precision of delegated balance by DELEGATED_POWER_DIVIDER + uint72 delegationDelta = uint72( + (userBalanceBefore / DELEGATED_POWER_DIVIDER) - (userBalanceAfter / DELEGATED_POWER_DIVIDER) + ); + if (delegationDelta == 0) return; + + if (delegationType == GovernancePowerType.VOTING) { + _balances[delegatee].delegatedVotingBalance = operation( + _balances[delegatee].delegatedVotingBalance, + delegationDelta + ); + //TODO: emit DelegatedPowerChanged maybe; + } else { + _balances[delegatee].delegatedPropositionBalance = operation( + _balances[delegatee].delegatedPropositionBalance, + delegationDelta + ); + //TODO: emit DelegatedPowerChanged maybe; + } + } + + /** + * @dev changing one of governance power(Voting and Proposition) of delegatees depending on the delegator balance change + * @param user delegator + * @param userState the current state of the delegator + * @param balanceBefore delegator balance before operation + * @param balanceAfter delegator balance after operation + * @param operation math operation which will be applied depends on increasing or decreasing of the delegator balance (plus, minus) + **/ + function _delegationMove( + address user, + DelegationAwareBalance memory userState, + uint104 balanceBefore, + uint104 balanceAfter, + function(uint72, uint72) returns (uint72) operation + ) internal { + _delegationMoveByType( + balanceBefore, + balanceAfter, + _getDelegateeByType(user, userState, GovernancePowerType.VOTING), + GovernancePowerType.VOTING, + operation + ); + _delegationMoveByType( + balanceBefore, + balanceAfter, + _getDelegateeByType(user, userState, GovernancePowerType.PROPOSITION), + GovernancePowerType.PROPOSITION, + operation + ); + } + + /** + * @dev performs all state changes related to balance transfer and corresponding delegation changes + * @param from token sender + * @param to token recipient + * @param amount amount of tokens sent + **/ + function _transferWithDelegation( + address from, + address to, + uint256 amount + ) internal override { + if (from == to) { + return; + } + + if (from != address(0)) { + DelegationAwareBalance memory fromUserState = _balances[from]; + require(fromUserState.balance >= amount, 'ERC20: transfer amount exceeds balance'); + + uint104 fromBalanceAfter; + unchecked { + //TODO: in general we don't need to check cast to uint104 because we know that it's less then balance from require + fromBalanceAfter = fromUserState.balance - uint104(amount); + } + _balances[from].balance = fromBalanceAfter; + if (fromUserState.delegatingProposition || fromUserState.delegatingVoting) + _delegationMove( + from, + fromUserState, + fromUserState.balance, + fromBalanceAfter, + MathUtils.minus + ); + } + + if (to != address(0)) { + DelegationAwareBalance memory toUserState = _balances[to]; + uint104 toBalanceBefore = toUserState.balance; + toUserState.balance = toBalanceBefore + uint104(amount); // TODO: check overflow? + _balances[to] = toUserState; + + if (toUserState.delegatingVoting || toUserState.delegatingProposition) { + _delegationMove(to, toUserState, toUserState.balance, toBalanceBefore, MathUtils.plus); + } + } + } + + /** + * @dev extracting and returning delegated governance power(Voting or Proposition) from user state + * @param userState the current state of a user + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + **/ + function _getDelegatedPowerByType( + DelegationAwareBalance memory userState, + GovernancePowerType delegationType + ) internal pure returns (uint72) { + return + delegationType == GovernancePowerType.VOTING + ? userState.delegatedVotingBalance + : userState.delegatedPropositionBalance; + } + + /** + * @dev extracts from user state and returning delegatee by type of governance power(Voting or Proposition) + * @param user delegator + * @param userState the current state of a user + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + **/ + function _getDelegateeByType( + address user, + DelegationAwareBalance memory userState, + GovernancePowerType delegationType + ) internal view returns (address) { + if (delegationType == GovernancePowerType.VOTING) { + return userState.delegatingVoting ? _votingDelegateeV2[user] : address(0); + } + return userState.delegatingProposition ? _propositionDelegateeV2[user] : address(0); + } + + /** + * @dev changing user's delegatee address by type of governance power(Voting or Proposition) + * @param user delegator + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + * @param _newDelegatee the new delegatee + **/ + function _updateDelegateeByType( + address user, + GovernancePowerType delegationType, + address _newDelegatee + ) internal { + address newDelegatee = _newDelegatee == user ? address(0) : _newDelegatee; + if (delegationType == GovernancePowerType.VOTING) { + _votingDelegateeV2[user] = newDelegatee; + } else { + _propositionDelegateeV2[user] = newDelegatee; + } + } + + /** + * @dev updates the specific flag which signaling about existence of delegation of governance power(Voting or Proposition) + * @param userState a user state to change + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + * @param willDelegate next state of delegation + **/ + function _updateDelegationFlagByType( + DelegationAwareBalance memory userState, + GovernancePowerType delegationType, + bool willDelegate + ) internal pure returns (DelegationAwareBalance memory) { + if (delegationType == GovernancePowerType.VOTING) { + userState.delegatingVoting = willDelegate; + } else { + userState.delegatingProposition = willDelegate; + } + return userState; + } + + /** + * @dev delegates the specific power to a delegatee + * @param user delegator + * @param _delegatee the user which delegated power has changed + * @param delegationType the type of delegation (VOTING, PROPOSITION) + **/ + function _delegateByType( + address user, + address _delegatee, + GovernancePowerType delegationType + ) internal { + //we consider to 0x0 as delegation to self + address delegatee = _delegatee == user ? address(0) : _delegatee; + + DelegationAwareBalance memory userState = _balances[user]; + address currentDelegatee = _getDelegateeByType(user, userState, delegationType); + if (delegatee == currentDelegatee) return; + + bool delegatingNow = currentDelegatee != address(0); + bool willDelegateAfter = delegatee != address(0); + + if (delegatingNow) { + _delegationMoveByType( + userState.balance, + 0, + currentDelegatee, + delegationType, + MathUtils.minus + ); + } + if (willDelegateAfter) { + _updateDelegateeByType(user, delegationType, delegatee); + _delegationMoveByType(userState.balance, 0, delegatee, delegationType, MathUtils.plus); + } + + if (willDelegateAfter != delegatingNow) { + _balances[user] = _updateDelegationFlagByType(userState, delegationType, willDelegateAfter); + } + + emit DelegateChanged(user, delegatee, delegationType); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function delegateByType(address delegatee, GovernancePowerType delegationType) + external + virtual + override + { + _delegateByType(msg.sender, delegatee, delegationType); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function delegate(address delegatee) external override { + _delegateByType(msg.sender, delegatee, GovernancePowerType.VOTING); + _delegateByType(msg.sender, delegatee, GovernancePowerType.PROPOSITION); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function getDelegateeByType(address delegator, GovernancePowerType delegationType) + external + view + override + returns (address) + { + return _getDelegateeByType(delegator, _balances[delegator], delegationType); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function getPowerCurrent(address user, GovernancePowerType delegationType) + external + view + override + returns (uint256) + { + DelegationAwareBalance memory userState = _balances[user]; + uint256 userOwnPower = (delegationType == GovernancePowerType.VOTING && + !userState.delegatingVoting) || + (delegationType == GovernancePowerType.PROPOSITION && !userState.delegatingProposition) + ? _balances[user].balance + : 0; + uint256 userDelegatedPower = _getDelegatedPowerByType(userState, delegationType) * + DELEGATED_POWER_DIVIDER; + return userOwnPower + userDelegatedPower; + } + + /// @inheritdoc IGovernancePowerDelegationToken + function metaDelegateByType( + address delegator, + address delegatee, + GovernancePowerType delegationType, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external override { + require(delegator != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _nonces[delegator]; + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256( + abi.encode( + DELEGATE_BY_TYPE_TYPEHASH, + delegator, + delegatee, + delegationType, + currentValidNonce, + deadline + ) + ) + ) + ); + + require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + unchecked { + // does not make sense to check because it's not realistic to reach uint256.max in nonce + _nonces[delegator] = currentValidNonce + 1; + } + _delegateByType(delegator, delegatee, delegationType); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function metaDelegate( + address delegator, + address delegatee, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external override { + require(delegator != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _nonces[delegator]; + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256(abi.encode(DELEGATE_TYPEHASH, delegator, delegatee, currentValidNonce, deadline)) + ) + ); + + require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + unchecked { + // does not make sense to check because it's not realistic to reach uint256.max in nonce + _nonces[delegator] = currentValidNonce + 1; + } + _delegateByType(delegator, delegatee, GovernancePowerType.VOTING); + _delegateByType(delegator, delegatee, GovernancePowerType.PROPOSITION); + } +} diff --git a/src/harness/BaseAaveTokenHarness.sol b/src/harness/BaseAaveTokenHarness.sol new file mode 100644 index 0000000..cb85e8e --- /dev/null +++ b/src/harness/BaseAaveTokenHarness.sol @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) + +// harness: balances and allowances are public variables + +pragma solidity ^0.8.0; + +import {Context} from '../../lib/openzeppelin-contracts/contracts/utils/Context.sol'; +import {IERC20} from '../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; +import {IERC20Metadata} from '../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; + + +/** + * @dev Implementation of the {IERC20} interface. + * + * This implementation is agnostic to the way tokens are created. This means + * that a supply mechanism has to be added in a derived contract using {_mint}. + * For a generic mechanism see {ERC20PresetMinterPauser}. + * + * TIP: For a detailed writeup see our guide + * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How + * to implement supply mechanisms]. + * + * We have followed general OpenZeppelin Contracts guidelines: functions revert + * instead returning `false` on failure. This behavior is nonetheless + * conventional and does not conflict with the expectations of ERC20 + * applications. + * + * Additionally, an {Approval} event is emitted on calls to {transferFrom}. + * This allows applications to reconstruct the allowance for all accounts just + * by listening to said events. Other implementations of the EIP may not emit + * these events, as it isn't required by the specification. + * + * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} + * functions have been added to mitigate the well-known issues around setting + * allowances. See {IERC20-approve}. + */ +abstract contract BaseAaveToken is Context, IERC20Metadata { + struct DelegationAwareBalance { + uint104 balance; + uint72 delegatedPropositionBalance; + uint72 delegatedVotingBalance; + bool delegatingProposition; + bool delegatingVoting; + } + + mapping(address => DelegationAwareBalance) public _balances; + + mapping(address => mapping(address => uint256)) public _allowances; + + uint256 internal _totalSupply; + + string internal _name; + string internal _symbol; + + // @dev DEPRECATED + // kept for backwards compatibility with old storage layout + uint8 private ______DEPRECATED_OLD_ERC20_DECIMALS; + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to get its user representation. + * For example, if `decimals` equals `2`, a balance of `505` tokens should + * be displayed to a user as `5.05` (`505 / 10 ** 2`). + * + * Tokens usually opt for a value of 18, imitating the relationship between + * Ether and Wei. This is the value {ERC20} uses, unless this function is + * overridden; + * + * NOTE: This information is only used for _display_ purposes: it in + * no way affects any of the arithmetic of the contract, including + * {IERC20-balanceOf} and {IERC20-transfer}. + */ + function decimals() public view virtual override returns (uint8) { + return 18; + } + + /** + * @dev See {IERC20-totalSupply}. + */ + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + /** + * @dev See {IERC20-balanceOf}. + */ + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account].balance; + } + + /** + * @dev See {IERC20-transfer}. + * + * Requirements: + * + * - `to` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + */ + function transfer(address to, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, amount); + return true; + } + + /** + * @dev See {IERC20-allowance}. + */ + function allowance(address owner, address spender) + public + view + virtual + override + returns (uint256) + { + return _allowances[owner][spender]; + } + + /** + * @dev See {IERC20-approve}. + * + * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on + * `transferFrom`. This is semantically equivalent to an infinite approval. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function approve(address spender, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, amount); + return true; + } + + /** + * @dev See {IERC20-transferFrom}. + * + * Emits an {Approval} event indicating the updated allowance. This is not + * required by the EIP. See the note at the beginning of {ERC20}. + * + * NOTE: Does not update the allowance if the current allowance + * is the maximum `uint256`. + * + * Requirements: + * + * - `from` and `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + * - the caller must have allowance for ``from``'s tokens of at least + * `amount`. + */ + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual override returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + /** + * @dev Atomically increases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + */ + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, _allowances[owner][spender] + addedValue); + return true; + } + + /** + * @dev Atomically decreases the allowance granted to `spender` by the caller. + * + * This is an alternative to {approve} that can be used as a mitigation for + * problems described in {IERC20-approve}. + * + * Emits an {Approval} event indicating the updated allowance. + * + * Requirements: + * + * - `spender` cannot be the zero address. + * - `spender` must have allowance for the caller of at least + * `subtractedValue`. + */ + function decreaseAllowance(address spender, uint256 subtractedValue) + public + virtual + returns (bool) + { + address owner = _msgSender(); + uint256 currentAllowance = _allowances[owner][spender]; + require(currentAllowance >= subtractedValue, 'ERC20: decreased allowance below zero'); + unchecked { + _approve(owner, spender, currentAllowance - subtractedValue); + } + + return true; + } + + /** + * @dev Moves `amount` of tokens from `sender` to `recipient`. + * + * This internal function is equivalent to {transfer}, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a {Transfer} event. + * + * Requirements: + * + * - `from` cannot be the zero address. + * - `to` cannot be the zero address. + * - `from` must have a balance of at least `amount`. + */ + function _transfer( + address from, + address to, + uint256 amount + ) internal virtual { + require(from != address(0), 'ERC20: transfer from the zero address'); + require(to != address(0), 'ERC20: transfer to the zero address'); + + _transferWithDelegation(from, to, amount); + emit Transfer(from, to, amount); + } + + /** + * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. + * + * This internal function is equivalent to `approve`, and can be used to + * e.g. set automatic allowances for certain subsystems, etc. + * + * Emits an {Approval} event. + * + * Requirements: + * + * - `owner` cannot be the zero address. + * - `spender` cannot be the zero address. + */ + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual { + require(owner != address(0), 'ERC20: approve from the zero address'); + require(spender != address(0), 'ERC20: approve to the zero address'); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + /** + * @dev Spend `amount` form the allowance of `owner` toward `spender`. + * + * Does not update the allowance amount in case of infinite allowance. + * Revert if not enough allowance is available. + * + * Might emit an {Approval} event. + */ + function _spendAllowance( + address owner, + address spender, + uint256 amount + ) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require(currentAllowance >= amount, 'ERC20: insufficient allowance'); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } + + function _transferWithDelegation( + address from, + address to, + uint256 amount + ) internal virtual {} +} diff --git a/src/harness/BaseAaveTokenV2Harness.sol b/src/harness/BaseAaveTokenV2Harness.sol new file mode 100644 index 0000000..318f210 --- /dev/null +++ b/src/harness/BaseAaveTokenV2Harness.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +// harness: import BaseAaveToken from harness file + +import {VersionedInitializable} from '../utils/VersionedInitializable.sol'; + +import {BaseAaveToken} from './BaseAaveTokenHarness.sol'; + +abstract contract BaseAaveTokenV2 is BaseAaveToken, VersionedInitializable { + /// @dev owner => next valid nonce to submit with permit() + mapping(address => uint256) public _nonces; + + ///////// @dev DEPRECATED from AaveToken v1 ////////////////////////// + //////// kept for backwards compatibility with old storage layout //// + uint256[3] private ______DEPRECATED_FROM_AAVE_V1; + ///////// @dev END OF DEPRECATED from AaveToken v1 ////////////////////////// + + bytes32 public DOMAIN_SEPARATOR; + + ///////// @dev DEPRECATED from AaveToken v2 ////////////////////////// + //////// kept for backwards compatibility with old storage layout //// + uint256[4] private ______DEPRECATED_FROM_AAVE_V2; + ///////// @dev END OF DEPRECATED from AaveToken v2 ////////////////////////// + + bytes public constant EIP712_REVISION = bytes('1'); + bytes32 internal constant EIP712_DOMAIN = + keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'); + bytes32 public constant PERMIT_TYPEHASH = + keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'); + + uint256 public constant REVISION = 3; // TODO: CHECK, but most probably was 2 before + + /** + * @dev initializes the contract upon assignment to the InitializableAdminUpgradeabilityProxy + */ + function initialize() external initializer {} + + /** + * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md + * @param owner the owner of the funds + * @param spender the spender + * @param value the amount + * @param deadline the deadline timestamp, type(uint256).max for no deadline + * @param v signature param + * @param s signature param + * @param r signature param + */ + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external { + require(owner != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _nonces[owner]; + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline)) + ) + ); + + require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + unchecked { + // does not make sense to check because it's not realistic to reach uint256.max in nonce + _nonces[owner] = currentValidNonce + 1; + } + _approve(owner, spender, value); + } + + /** + * @dev returns the revision of the implementation contract + */ + function getRevision() internal pure override returns (uint256) { + return REVISION; + } +} From bb2641749262e34f2e76d87febd42a0dd4a5c88c Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Tue, 12 Jul 2022 13:58:32 +0300 Subject: [PATCH 17/58] feat: new rules --- certora/harness/AaveTokenV3Harness.sol | 9 -- certora/properties.md | 10 +++ certora/specs/bgdSpec.spec | 115 +++++++++++++++++++++---- src/harness/AaveTokenV3Harness.sol | 10 +++ 4 files changed, 119 insertions(+), 25 deletions(-) delete mode 100644 certora/harness/AaveTokenV3Harness.sol diff --git a/certora/harness/AaveTokenV3Harness.sol b/certora/harness/AaveTokenV3Harness.sol deleted file mode 100644 index 6ad679c..0000000 --- a/certora/harness/AaveTokenV3Harness.sol +++ /dev/null @@ -1,9 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {AaveTokenV3} from '../../src/AaveTokenV3.sol'; - -contract AaveTokenV3Harness is AaveTokenV3 { - -} \ No newline at end of file diff --git a/certora/properties.md b/certora/properties.md index f1e0a71..cd1ac21 100644 --- a/certora/properties.md +++ b/certora/properties.md @@ -31,3 +31,13 @@ ## properties for Aave Token v3 spec +-- on token transfer, the delegation balances change correctly for all cases: +from delegating, to delegating, both delegating, none delegating + +-- delegating to 0 == delegating to self. which means the flags are set to false + +-- the flags are updated properly after delegation + +-- the delegated power is stored divided by 10^10. make sure this doesn't kill precision. + +-- \ No newline at end of file diff --git a/certora/specs/bgdSpec.spec b/certora/specs/bgdSpec.spec index 2af0171..15dcd22 100644 --- a/certora/specs/bgdSpec.spec +++ b/certora/specs/bgdSpec.spec @@ -8,22 +8,11 @@ methods{ totalSupply() returns (uint256) envfree balanceOf(address addr) returns (uint256) envfree transfer(address to, uint256 amount) returns (bool) - transferFrom(address from, address to) returns (bool) + transferFrom(address from, address to, uint256 amount) returns (bool) - _votingDelegateeV2(address) returns (address) - _propositionDelegateeV2(address) returns (address) DELEGATED_POWER_DIVIDER() returns (uint256) DELEGATE_BY_TYPE_TYPEHASH() returns (bytes32) DELEGATE_TYPEHASH() returns (bytes32) - // _delegationMoveByType(uint104, uint104, address, GovernancePowerType delegationType, function(uint72, uint72) returns (uint72) operation) - // _delegationMove(address, DelegationAwareBalance userState, uint104, uint104, function(uint72, uint72) returns (uint72) operation) - _transferWithDelegation(address, address, uint256) - // _getDelegatedPowerByType(DelegationAwareBalance userState, GovernancePowerType delegationType) returns (uint72) - // _getDelegateeByType(address, DelegationAwareBalance userState, GovernancePowerType delegationType) returns (address) - // _updateDelegateeByType(address, GovernancePowerType delegationType, address) - // _updateDelegationFlagByType(DelegationAwareBalance userState, GovernancePowerType delegationType, bool) returns (DelegationAwareBalance) - // _delegateByType(address, address, GovernancePowerType delegationType) - // delegateByType(address, GovernancePowerType delegationType) delegate(address delegatee) // getDelegateeByType(address delegator, GovernancePowerType delegationType) returns (address) // getPowerCurrent(address, GovernancePowerType delegationType) returns (uint256) @@ -40,6 +29,8 @@ methods{ getDelegatedVotingBalance(address user) returns (uint72) envfree getDelegatingProposition(address user) returns (bool) envfree getDelegatingVoting(address user) returns (bool) envfree + getVotingDelegate(address user) returns (address) envfree + getPropositionDelegate(address user) returns (address) envfree } definition VOTING_POWER() returns uint8 = 0; @@ -54,11 +45,11 @@ definition PROPOSITION_POWER() returns uint8 = 1; // accumulator for a sum of proposition voting power ghost mathint sumDelegatedProposition { - init_state axiom forall uint256 t. sumDelegatedProposition == 0; + init_state axiom sumDelegatedProposition == 0; } ghost mathint sumBalances { - init_state axiom forall uint256 t. sumBalances == 0; + init_state axiom sumBalances == 0; } /* @@ -75,7 +66,7 @@ hook Sstore _balances[KEY address user].balance uint104 balance sumBalances = sumBalances + to_mathint(balance) - to_mathint(old_balance); } -invariant sumDelegatedPropositionCorrectness() sumDelegatedProposition <= totalSupply() { +invariant sumDelegatedPropositionCorrectness() sumDelegatedProposition <= sumBalances { // fails preserved transfer(address to, uint256 amount) with (env e) { @@ -87,6 +78,15 @@ invariant sumDelegatedPropositionCorrectness() sumDelegatedProposition <= totalS } } +invariant nonDelegatingBalance(address user) + !getDelegatingProposition(user) => balanceOf(user) == getDelegatedPropositionBalance(user) { + preserved transfer(address to, uint256 amount) with (env e) + { + require(getVotingDelegate(to) != user); + } + } + + rule totalSupplyCorrectness(method f) { env e; calldataarg args; @@ -122,4 +122,87 @@ rule transferUnitTest() { uint256 powerToAfter = getPowerCurrent(to, VOTING_POWER()); assert powerToAfter == powerToBefore + powerSenderBefore; -} \ No newline at end of file +} + +// for non delegating address +rule votingPowerEqualsBalance(address user) { + uint256 userBalance = balanceOf(user); + require(!getDelegatingProposition(user)); + require(!getDelegatingVoting(user)); + assert userBalance == getDelegatedPropositionBalance(user) && userBalance == getDelegatedVotingBalance(user); +} + +// Verify that the voting delegation balances update correctly +// probably a scaling issue +rule tokenTransferCorrectnessVoting(address from, address to, uint256 amount) { + env e; + + require(from != 0 && to != 0); + + uint256 balanceFromBefore = balanceOf(from); + uint256 balanceToBefore = balanceOf(to); + + address fromDelegate = getVotingDelegate(from); + address toDelegate = getVotingDelegate(to); + + uint256 powerFromDelegateBefore = getPowerCurrent(fromDelegate, VOTING_POWER()); + uint256 powerToDelegateBefore = getPowerCurrent(toDelegate, VOTING_POWER()); + + bool isDelegatingVotingFromBefore = getDelegatingVoting(from); + bool isDelegatingVotingToBefore = getDelegatingVoting(to); + + // non reverting path + transferFrom(e, from, to, amount); + + uint256 balanceFromAfter = balanceOf(from); + uint256 balanceToAfter = balanceOf(to); + + address fromDelegateAfter = getVotingDelegate(from); + address toDelegateAfter = getVotingDelegate(to); + + uint256 powerFromDelegateAfter = getPowerCurrent(fromDelegateAfter, VOTING_POWER()); + uint256 powerToDelegateAfter = getPowerCurrent(toDelegateAfter, VOTING_POWER()); + + bool isDelegatingVotingFromAfter = getDelegatingVoting(from); + bool isDelegatingVotingToAfter = getDelegatingVoting(to); + + assert fromDelegateAfter == toDelegateAfter => powerFromDelegateBefore == powerFromDelegateAfter; + + assert isDelegatingVotingFromBefore => + powerFromDelegateAfter - powerFromDelegateBefore == amount || + (fromDelegateAfter == toDelegateAfter && powerFromDelegateBefore == powerFromDelegateAfter); + assert isDelegatingVotingToBefore => + powerToDelegateAfter - powerToDelegateBefore == amount || + (fromDelegateAfter == toDelegateAfter && powerToDelegateBefore == powerToDelegateAfter); + +} + +// If an account is not receiving delegation of power (one type) from anybody, +// and that account is not delegating that power to anybody, the power of that account +// must be equal to its AAVE balance. + +rule powerWhenNotDelegating(address account) { + uint256 balance = balanceOf(account); + bool isDelegatingVoting = getDelegatingVoting(account); + bool isDelegatingProposition = getDelegatingProposition(account); + uint72 dvb = getDelegatedVotingBalance(account); + uint72 dpb = getDelegatedPropositionBalance(account); + + uint256 votingPower = getPowerCurrent(account, VOTING_POWER()); + uint256 propositionPower = getPowerCurrent(account, PROPOSITION_POWER()); + + assert dvb == 0 && !isDelegatingVoting => votingPower == balance; + assert dpb == 0 && !isDelegatingProposition => propositionPower == balance; +} + +// wrong, user may delegate to himself/0 and the flag will be set true +rule selfDelegationCorrectness(address account) { + bool isDelegatingVoting = getDelegatingVoting(account); + bool isDelegatingProposition = getDelegatingProposition(account); + address votingDelegate = getVotingDelegate(account); + address propositionDelegate = getPropositionDelegate(account); + + assert votingDelegate == 0 || votingDelegate == account => isDelegatingVoting == false; + assert propositionDelegate == 0 || propositionDelegate == account => isDelegatingProposition == false; + +} diff --git a/src/harness/AaveTokenV3Harness.sol b/src/harness/AaveTokenV3Harness.sol index 23f3ec6..17fa0b8 100644 --- a/src/harness/AaveTokenV3Harness.sol +++ b/src/harness/AaveTokenV3Harness.sol @@ -75,6 +75,16 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { return _balances[user].delegatingVoting; } + function getVotingDelegate(address user) view public returns (address) { + return _votingDelegateeV2[user]; + } + + function getPropositionDelegate(address user) view public returns (address) { + return _propositionDelegateeV2[user]; + } + + + /** End of harness section */ From 452e2c57e2e51f3452096942c4b119c321405312 Mon Sep 17 00:00:00 2001 From: Michael M <91594326+MichaelMorami@users.noreply.github.com> Date: Wed, 29 Jun 2022 16:46:10 +0300 Subject: [PATCH 18/58] splitting certora's gitignore from main gitignore --- .gitignore | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.gitignore b/.gitignore index a2edf0f..c2e1bd9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,3 @@ cache/ out/ .idea - -# certora -.certora* -.certora*.json -**.last_conf* -certora-logs From 75f28eb95c28292c0377cc5ac96c1ffca1eccf55 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Tue, 12 Jul 2022 13:59:42 +0300 Subject: [PATCH 19/58] fix: upgate gitignore to exclude certora config --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index c2e1bd9..4f0ad61 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ cache/ out/ .idea +.certora_config/ +.last_confs/ +.certora_* From 1988a656fe63099421a4121fbbc87da95431520e Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Tue, 19 Jul 2022 09:11:56 +0300 Subject: [PATCH 20/58] update folders --- .../harness/AaveTokenV3Harness.sol | 15 +- .../harness/BaseAaveTokenHarness.sol | 0 .../harness/BaseAaveTokenV2Harness.sol | 2 +- certora/scripts/verifyBgdSpec.sh | 2 +- certora/specs/bgdSpec.spec | 364 +++++++++++++++++- certora/specs/setup.spec | 1 + src/BaseAaveTokenV3.sol | 84 ++++ 7 files changed, 450 insertions(+), 18 deletions(-) rename {src => certora}/harness/AaveTokenV3Harness.sol (96%) rename {src => certora}/harness/BaseAaveTokenHarness.sol (100%) rename {src => certora}/harness/BaseAaveTokenV2Harness.sol (97%) create mode 100644 certora/specs/setup.spec diff --git a/src/harness/AaveTokenV3Harness.sol b/certora/harness/AaveTokenV3Harness.sol similarity index 96% rename from src/harness/AaveTokenV3Harness.sol rename to certora/harness/AaveTokenV3Harness.sol index 17fa0b8..d0f44e8 100644 --- a/src/harness/AaveTokenV3Harness.sol +++ b/certora/harness/AaveTokenV3Harness.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8.0; -import {VersionedInitializable} from '../utils/VersionedInitializable.sol'; +import {VersionedInitializable} from '../../src/utils/VersionedInitializable.sol'; -import {IGovernancePowerDelegationToken} from '../interfaces/IGovernancePowerDelegationToken.sol'; +import {IGovernancePowerDelegationToken} from '../../src/interfaces/IGovernancePowerDelegationToken.sol'; import {BaseAaveTokenV2} from './BaseAaveTokenV2Harness.sol'; -import {MathUtils} from '../utils/MathUtils.sol'; +import {MathUtils} from '../../src/utils/MathUtils.sol'; contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { @@ -106,10 +106,13 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { ) internal { if (delegatee == address(0)) return; + // FIXING A PRECISION ISSUE HERE + // @dev to make delegated balance fit into uin72 we're decreasing precision of delegated balance by DELEGATED_POWER_DIVIDER - uint72 delegationDelta = uint72( - (userBalanceBefore / DELEGATED_POWER_DIVIDER) - (userBalanceAfter / DELEGATED_POWER_DIVIDER) - ); + // uint72 delegationDelta = uint72( + // (userBalanceBefore / DELEGATED_POWER_DIVIDER) - (userBalanceAfter / DELEGATED_POWER_DIVIDER) + // ); + uint72 delegationDelta = uint72((userBalanceBefore - userBalanceAfter) / DELEGATED_POWER_DIVIDER); if (delegationDelta == 0) return; if (delegationType == GovernancePowerType.VOTING) { diff --git a/src/harness/BaseAaveTokenHarness.sol b/certora/harness/BaseAaveTokenHarness.sol similarity index 100% rename from src/harness/BaseAaveTokenHarness.sol rename to certora/harness/BaseAaveTokenHarness.sol diff --git a/src/harness/BaseAaveTokenV2Harness.sol b/certora/harness/BaseAaveTokenV2Harness.sol similarity index 97% rename from src/harness/BaseAaveTokenV2Harness.sol rename to certora/harness/BaseAaveTokenV2Harness.sol index 318f210..9108728 100644 --- a/src/harness/BaseAaveTokenV2Harness.sol +++ b/certora/harness/BaseAaveTokenV2Harness.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; // harness: import BaseAaveToken from harness file -import {VersionedInitializable} from '../utils/VersionedInitializable.sol'; +import {VersionedInitializable} from '../../src/utils/VersionedInitializable.sol'; import {BaseAaveToken} from './BaseAaveTokenHarness.sol'; diff --git a/certora/scripts/verifyBgdSpec.sh b/certora/scripts/verifyBgdSpec.sh index f8fc77c..4e4eacf 100755 --- a/certora/scripts/verifyBgdSpec.sh +++ b/certora/scripts/verifyBgdSpec.sh @@ -3,7 +3,7 @@ then RULE="--rule $1" fi -certoraRun src/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ +certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ --verify AaveTokenV3:certora/specs/bgdSpec.spec \ --rule $1 \ --solc solc8.13 \ diff --git a/certora/specs/bgdSpec.spec b/certora/specs/bgdSpec.spec index 15dcd22..f4878ec 100644 --- a/certora/specs/bgdSpec.spec +++ b/certora/specs/bgdSpec.spec @@ -10,18 +10,8 @@ methods{ transfer(address to, uint256 amount) returns (bool) transferFrom(address from, address to, uint256 amount) returns (bool) - DELEGATED_POWER_DIVIDER() returns (uint256) - DELEGATE_BY_TYPE_TYPEHASH() returns (bytes32) - DELEGATE_TYPEHASH() returns (bytes32) delegate(address delegatee) - // getDelegateeByType(address delegator, GovernancePowerType delegationType) returns (address) - // getPowerCurrent(address, GovernancePowerType delegationType) returns (uint256) - // metaDelegateByType(address, address, GovernancePowerType delegationType, uint256, uint8, bytes32, bytes32) metaDelegate(address, address, uint256, uint8, bytes32, bytes32) - // enum GovernancePowerType { - // VOTING, - // PROPOSITION - // } getPowerCurrent(address user, uint8 delegationType) returns (uint256) envfree getBalance(address user) returns (uint104) envfree @@ -35,6 +25,11 @@ methods{ definition VOTING_POWER() returns uint8 = 0; definition PROPOSITION_POWER() returns uint8 = 1; +definition DELEGATED_POWER_DIVIDER() returns uint256 = 10^10; + +function normalize(uint256 amount) returns uint256 { + return to_uint256(amount / DELEGATED_POWER_DIVIDER() * DELEGATED_POWER_DIVIDER()); +} // for test - it shouldnt pass // invariant ZeroAddressNoDelegation() @@ -206,3 +201,352 @@ rule selfDelegationCorrectness(address account) { assert propositionDelegate == 0 || propositionDelegate == account => isDelegatingProposition == false; } + +/** + Account1 and account2 are not delegating power +*/ + +rule vpTransferWhenBothNotDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + bool isBobDelegatingVoting = getDelegatingVoting(bob); + + require !isAliceDelegatingVoting && !isBobDelegatingVoting; + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + + assert alicePowerAfter == alicePowerBefore - amount; + assert bobPowerAfter == bobPowerBefore + amount; + assert charliePowerAfter == charliePowerBefore; +} + + +rule ppTransferWhenBothNotDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingProposition = getDelegatingProposition(alice); + // bool isAliceDelegatingProposition = getDelegatedProposition(alice); + + bool isBobDelegatingProposition = getDelegatingProposition(bob); + // bool isBobDelegatingProposition = getDelegatedProposition(bob); + + require !isAliceDelegatingProposition && !isBobDelegatingProposition; + + uint256 alicePowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, PROPOSITION_POWER()); + + assert alicePowerAfter == alicePowerBefore - amount; + assert bobPowerAfter == bobPowerBefore + amount; + assert charliePowerAfter == charliePowerBefore; +} + +rule vpDelegateWhenBothNotDelegating(address alice, address bob, address charlie) { + env e; + require alice == e.msg.sender; + require alice != 0 && bob != 0 && charlie != 0; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + bool isBobDelegatingVoting = getDelegatingVoting(bob); + + require !isAliceDelegatingVoting && !isBobDelegatingVoting; + + uint256 aliceBalance = balanceOf(alice); + uint256 bobBalance = balanceOf(bob); + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + + delegate(e, bob); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + + assert alicePowerAfter == alicePowerBefore - aliceBalance; + assert bobPowerAfter == bobPowerBefore + (aliceBalance / DELEGATED_POWER_DIVIDER()) * DELEGATED_POWER_DIVIDER(); + assert getVotingDelegate(alice) == bob; + assert charliePowerAfter == charliePowerBefore; +} + +rule ppDelegateWhenBothNotDelegating(address alice, address bob, address charlie) { + env e; + require alice == e.msg.sender; + require alice != 0 && bob != 0 && charlie != 0; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingProposition = getDelegatingProposition(alice); + bool isBobDelegatingProposition = getDelegatingProposition(bob); + + require !isAliceDelegatingProposition && !isBobDelegatingProposition; + + uint256 aliceBalance = balanceOf(alice); + uint256 bobBalance = balanceOf(bob); + + uint256 alicePowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); + + delegate(e, bob); + + uint256 alicePowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, PROPOSITION_POWER()); + + assert alicePowerAfter == alicePowerBefore - aliceBalance; + assert bobPowerAfter == bobPowerBefore + (aliceBalance / DELEGATED_POWER_DIVIDER()) * DELEGATED_POWER_DIVIDER(); + assert getPropositionDelegate(alice) == bob; + assert charliePowerAfter == charliePowerBefore; +} + +/** + Account1 is delegating power to delegatee1, account2 is not delegating power to anybody +*/ + +// token transfer from alice to bob + +rule vpTransferWhenOnlyOneIsDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + bool isBobDelegatingVoting = getDelegatingVoting(bob); + address aliceDelegate = getVotingDelegate(alice); + require aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != bob && aliceDelegate != charlie; + + require isAliceDelegatingVoting && !isBobDelegatingVoting; + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + // no delegation of anyone to Alice + require alicePowerBefore == 0; + + uint256 bobPowerBefore = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, VOTING_POWER()); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, VOTING_POWER()); + + // still zero + assert alicePowerBefore == alicePowerAfter; + assert aliceDelegatePowerAfter == + aliceDelegatePowerBefore - normalize(amount); + assert bobPowerAfter == bobPowerBefore + amount; + assert charliePowerBefore == charliePowerAfter; +} + +/** +before: 133160000000000 +amount: 30900000000001 +after: 102250000000000 + +*/ + +rule ppTransferWhenOnlyOneIsDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingProposition = getDelegatingProposition(alice); + bool isBobDelegatingProposition = getDelegatingProposition(bob); + address aliceDelegate = getPropositionDelegate(alice); + + require isAliceDelegatingProposition && !isBobDelegatingProposition; + + uint256 alicePowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + // no delegation of anyone to Alice + require alicePowerBefore == 0; + + uint256 bobPowerBefore = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + + // still zero + assert alicePowerBefore == alicePowerAfter; + // this is the equation in the properties.md, but it's wrong when amount == 10 ^ 10 + // assert aliceDelegatePowerAfter == + // aliceDelegatePowerBefore - (amount / DELEGATED_POWER_DIVIDER() * DELEGATED_POWER_DIVIDER()); + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore; + assert bobPowerAfter == bobPowerBefore + amount; + assert charliePowerBefore == charliePowerAfter; +} + +// After account1 will stop delegating his power to delegatee1 +rule vpStopDelegatingWhenOnlyOneIsDelegating(address alice, address charlie) { + env e; + require alice != charlie; + require alice == e.msg.sender; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + address aliceDelegate = getVotingDelegate(alice); + + require isAliceDelegatingVoting; + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, VOTING_POWER()); + + delegate(e, 0); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, VOTING_POWER()); + + assert alicePowerAfter == alicePowerBefore + balanceOf(alice); + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - balanceOf(alice); + assert charliePowerAfter == charliePowerBefore; +} + +rule ppStopDelegatingWhenOnlyOneIsDelegating(address alice, address charlie) { + env e; + require alice != charlie; + require alice == e.msg.sender; + + bool isAliceDelegatingProposition = getDelegatingProposition(alice); + address aliceDelegate = getPropositionDelegate(alice); + + require isAliceDelegatingProposition; + + uint256 alicePowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + + delegate(e, 0); + + uint256 alicePowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + + assert alicePowerAfter == alicePowerBefore + balanceOf(alice); + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - balanceOf(alice); + assert charliePowerAfter == charliePowerBefore; +} + +rule vpChangeDelegateWhenOnlyOneIsDelegating(address alice, address delegate2, address charlie) { + env e; + require alice != charlie && alice != delegate2 && charlie != delegate2; + require alice == e.msg.sender; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + address aliceDelegate = getVotingDelegate(alice); + require aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != delegate2 && delegate2 != 0; + + require isAliceDelegatingVoting; + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, VOTING_POWER()); + uint256 delegate2PowerBefore = getPowerCurrent(delegate2, VOTING_POWER()); + + delegate(e, delegate2); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, VOTING_POWER()); + uint256 delegate2PowerAfter = getPowerCurrent(delegate2, VOTING_POWER()); + address aliceDelegateAfter = getVotingDelegate(alice); + + assert alicePowerBefore == alicePowerAfter; + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - normalize(balanceOf(alice)); + assert delegate2PowerAfter == delegate2PowerBefore + normalize(balanceOf(alice)); + assert aliceDelegateAfter == delegate2; + assert charliePowerAfter == charliePowerBefore; +} + +// Account1 not delegating power to anybody, account2 is delegating power to delegatee2 + +rule vpOnlyAccount2IsDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + bool isBobDelegatingVoting = getDelegatingVoting(bob); + address bobDelegate = getVotingDelegate(bob); + require bobDelegate != bob && bobDelegate != 0 && bobDelegate != alice && bobDelegate != charlie; + + require !isAliceDelegatingVoting && isBobDelegatingVoting; + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, VOTING_POWER()); + require bobPowerBefore == 0; + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + uint256 bobDelegatePowerBefore = getPowerCurrent(bobDelegate, VOTING_POWER()); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + uint256 bobDelegatePowerAfter = getPowerCurrent(bobDelegate, VOTING_POWER()); + + assert alicePowerAfter == alicePowerBefore - amount; + assert bobPowerAfter == 0; + assert bobDelegatePowerAfter == bobDelegatePowerBefore + normalize(amount); + + assert charliePowerAfter == charliePowerBefore; +} + +//add for proposition + +// Account1 is delegating power to delegatee1, account2 is delegating power to delegatee2 +rule vpTransferWhenBothAreDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + bool isBobDelegatingVoting = getDelegatingVoting(bob); + require isAliceDelegatingVoting && isBobDelegatingVoting; + address aliceDelegate = getVotingDelegate(alice); + address bobDelegate = getVotingDelegate(bob); + require aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != bob && aliceDelegate != charlie; + require bobDelegate != bob && bobDelegate != 0 && bobDelegate != alice && bobDelegate != charlie; + require aliceDelegate != bobDelegate; + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, VOTING_POWER()); + uint256 bobDelegatePowerBefore = getPowerCurrent(bobDelegate, VOTING_POWER()); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, VOTING_POWER()); + uint256 bobDelegatePowerAfter = getPowerCurrent(bobDelegate, VOTING_POWER()); + + assert alicePowerAfter == alicePowerBefore; + assert bobPowerAfter == bobPowerBefore; + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - normalize(amount); + assert bobDelegatePowerAfter == bobDelegatePowerBefore + normalize (amount); +} \ No newline at end of file diff --git a/certora/specs/setup.spec b/certora/specs/setup.spec new file mode 100644 index 0000000..f3c54f9 --- /dev/null +++ b/certora/specs/setup.spec @@ -0,0 +1 @@ +// unit test, invariant, parametric test, ghost + hook, documentation \ No newline at end of file diff --git a/src/BaseAaveTokenV3.sol b/src/BaseAaveTokenV3.sol index e69de29..9b773a8 100644 --- a/src/BaseAaveTokenV3.sol +++ b/src/BaseAaveTokenV3.sol @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {VersionedInitializable} from './utils/VersionedInitializable.sol'; + +import {BaseAaveToken} from './BaseAaveToken.sol'; + +abstract contract BaseAaveTokenV2 is BaseAaveToken, VersionedInitializable { + /// @dev owner => next valid nonce to submit with permit() + mapping(address => uint256) public _nonces; + + ///////// @dev DEPRECATED from AaveToken v1 ////////////////////////// + //////// kept for backwards compatibility with old storage layout //// + uint256[3] private ______DEPRECATED_FROM_AAVE_V1; + ///////// @dev END OF DEPRECATED from AaveToken v1 ////////////////////////// + + bytes32 public DOMAIN_SEPARATOR; + + ///////// @dev DEPRECATED from AaveToken v2 ////////////////////////// + //////// kept for backwards compatibility with old storage layout //// + uint256[4] private ______DEPRECATED_FROM_AAVE_V2; + ///////// @dev END OF DEPRECATED from AaveToken v2 ////////////////////////// + + bytes public constant EIP712_REVISION = bytes('1'); + bytes32 internal constant EIP712_DOMAIN = + keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'); + bytes32 public constant PERMIT_TYPEHASH = + keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'); + + uint256 public constant REVISION = 3; // TODO: CHECK, but most probably was 2 before + + /** + * @dev initializes the contract upon assignment to the InitializableAdminUpgradeabilityProxy + */ + function initialize() external initializer {} + + /** + * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md + * @param owner the owner of the funds + * @param spender the spender + * @param value the amount + * @param deadline the deadline timestamp, type(uint256).max for no deadline + * @param v signature param + * @param s signature param + * @param r signature param + */ + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external { + require(owner != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _nonces[owner]; + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline)) + ) + ); + + require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + unchecked { + // does not make sense to check because it's not realistic to reach uint256.max in nonce + _nonces[owner] = currentValidNonce + 1; + } + _approve(owner, spender, value); + } + + /** + * @dev returns the revision of the implementation contract + */ + function getRevision() internal pure override returns (uint256) { + return REVISION; + } +} From f1b94faa079f061b5176e596437eb9692afc9941 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Tue, 19 Jul 2022 14:21:24 +0300 Subject: [PATCH 21/58] rebase from main --- certora/harness/AaveTokenV3Harness.sol | 544 ++++++++++----------- certora/harness/AaveTokenV3Harness_old.sol | 433 ++++++++++++++++ certora/harness/BaseAaveTokenHarness.sol | 301 ------------ certora/harness/BaseAaveTokenV2Harness.sol | 86 ---- certora/scripts/verifyBgdSpec.sh | 2 +- certora/specs/bgdSpec.spec | 20 +- 6 files changed, 725 insertions(+), 661 deletions(-) create mode 100644 certora/harness/AaveTokenV3Harness_old.sol delete mode 100644 certora/harness/BaseAaveTokenHarness.sol delete mode 100644 certora/harness/BaseAaveTokenV2Harness.sol diff --git a/certora/harness/AaveTokenV3Harness.sol b/certora/harness/AaveTokenV3Harness.sol index d0f44e8..8df08d4 100644 --- a/certora/harness/AaveTokenV3Harness.sol +++ b/certora/harness/AaveTokenV3Harness.sol @@ -1,12 +1,9 @@ // SPDX-License-Identifier: MIT - pragma solidity ^0.8.0; import {VersionedInitializable} from '../../src/utils/VersionedInitializable.sol'; - import {IGovernancePowerDelegationToken} from '../../src/interfaces/IGovernancePowerDelegationToken.sol'; -import {BaseAaveTokenV2} from './BaseAaveTokenV2Harness.sol'; -import {MathUtils} from '../../src/utils/MathUtils.sol'; +import {BaseAaveTokenV2} from '../../src/BaseAaveTokenV2.sol'; contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { @@ -14,7 +11,9 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { mapping(address => address) internal _votingDelegateeV2; mapping(address => address) internal _propositionDelegateeV2; - uint256 public constant DELEGATED_POWER_DIVIDER = 10**10; + // @dev we assume that for the governance system 18 decimals of precision is not needed, + // by this constant we reduce it by 10, to 8 decimals + uint256 public constant DELEGATOR_POWER_SCALE_FACTOR = 1e10; bytes32 public constant DELEGATE_BY_TYPE_TYPEHASH = keccak256( @@ -23,144 +22,165 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { bytes32 public constant DELEGATE_TYPEHASH = keccak256('Delegate(address delegator,address delegatee,uint256 nonce,uint256 deadline)'); - /** - Harness section - replace struct reads and writes with function calls - */ - -// struct DelegationAwareBalance { -// uint104 balance; -// uint72 delegatedPropositionBalance; -// uint72 delegatedVotingBalance; -// bool delegatingProposition; -// bool delegatingVoting; -// } - - function _setBalance(address user, uint104 balance) internal { - _balances[user].balance = balance; - } - - function getBalance(address user) view public returns (uint104) { - return _balances[user].balance; - } - - function _setDelegatedPropositionBalance(address user, uint72 dpb) internal { - _balances[user].delegatedPropositionBalance = dpb; - } - - function getDelegatedPropositionBalance(address user) view public returns (uint72) { - return _balances[user].delegatedPropositionBalance; - } - - function _setDelegatedVotingBalance(address user, uint72 dvb) internal { - _balances[user].delegatedVotingBalance = dvb; - } - - function getDelegatedVotingBalance(address user) view public returns (uint72) { - return _balances[user].delegatedVotingBalance; - } + /// @inheritdoc IGovernancePowerDelegationToken + function delegateByType(address delegatee, GovernancePowerType delegationType) + external + virtual + override + { + _delegateByType(msg.sender, delegatee, delegationType); + } - function _setDelegatingProposition(address user, bool _delegating) internal { - _balances[user].delegatingProposition = _delegating; - } + /// @inheritdoc IGovernancePowerDelegationToken + function delegate(address delegatee) external override { + _delegateByType(msg.sender, delegatee, GovernancePowerType.VOTING); + _delegateByType(msg.sender, delegatee, GovernancePowerType.PROPOSITION); + } - function getDelegatingProposition(address user) view public returns (bool) { - return _balances[user].delegatingProposition; - } + /// @inheritdoc IGovernancePowerDelegationToken + function getDelegateeByType(address delegator, GovernancePowerType delegationType) + external + view + override + returns (address) + { + return _getDelegateeByType(delegator, _balances[delegator], delegationType); + } - function _setDelegatingVoting(address user, bool _delegating) internal { - _balances[user].delegatingVoting = _delegating; - } + /// @inheritdoc IGovernancePowerDelegationToken + function getDelegates(address delegator) external view override returns (address, address) { + DelegationAwareBalance memory delegatorBalance = _balances[delegator]; + return ( + _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.VOTING), + _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.PROPOSITION) + ); + } - function getDelegatingVoting(address user) view public returns (bool) { - return _balances[user].delegatingVoting; - } + /// @inheritdoc IGovernancePowerDelegationToken + function getPowerCurrent(address user, GovernancePowerType delegationType) + public + view + override + returns (uint256) + { + DelegationAwareBalance memory userState = _balances[user]; + uint256 userOwnPower = uint8(userState.delegationState) & (uint8(delegationType) + 1) == 0 + ? _balances[user].balance + : 0; + uint256 userDelegatedPower = _getDelegatedPowerByType(userState, delegationType); + return userOwnPower + userDelegatedPower; + } - function getVotingDelegate(address user) view public returns (address) { - return _votingDelegateeV2[user]; - } + /// @inheritdoc IGovernancePowerDelegationToken + function getPowersCurrent(address user) external view override returns (uint256, uint256) { + return ( + getPowerCurrent(user, GovernancePowerType.VOTING), + getPowerCurrent(user, GovernancePowerType.PROPOSITION) + ); + } - function getPropositionDelegate(address user) view public returns (address) { - return _propositionDelegateeV2[user]; - } + /// @inheritdoc IGovernancePowerDelegationToken + function metaDelegateByType( + address delegator, + address delegatee, + GovernancePowerType delegationType, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external override { + require(delegator != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _nonces[delegator]; + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256( + abi.encode( + DELEGATE_BY_TYPE_TYPEHASH, + delegator, + delegatee, + delegationType, + currentValidNonce, + deadline + ) + ) + ) + ); + require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + unchecked { + // Does not make sense to check because it's not realistic to reach uint256.max in nonce + _nonces[delegator] = currentValidNonce + 1; + } + _delegateByType(delegator, delegatee, delegationType); + } + /// @inheritdoc IGovernancePowerDelegationToken + function metaDelegate( + address delegator, + address delegatee, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external override { + require(delegator != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _nonces[delegator]; + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256(abi.encode(DELEGATE_TYPEHASH, delegator, delegatee, currentValidNonce, deadline)) + ) + ); - /** - End of harness section - */ + require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + unchecked { + // does not make sense to check because it's not realistic to reach uint256.max in nonce + _nonces[delegator] = currentValidNonce + 1; + } + _delegateByType(delegator, delegatee, GovernancePowerType.VOTING); + _delegateByType(delegator, delegatee, GovernancePowerType.PROPOSITION); + } /** - * @dev changing one of delegated governance powers of delegatee depending on the delegator balance change - * @param userBalanceBefore delegator balance before operation - * @param userBalanceAfter delegator balance after operation + * @dev Changing one of delegated governance powers of delegatee depending on the delegator balance change + * @param delegatorBalanceBefore delegator balance before operation + * @param delegatorBalanceAfter delegator balance after operation * @param delegatee the user whom delegated governance power will be changed * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) - * @param operation math operation which will be applied depends on increasing or decreasing of the delegator balance (plus, minus) **/ - function _delegationMoveByType( - uint104 userBalanceBefore, - uint104 userBalanceAfter, + function _governancePowerTransferByType( + uint104 delegatorBalanceBefore, + uint104 delegatorBalanceAfter, address delegatee, - GovernancePowerType delegationType, - function(uint72, uint72) returns (uint72) operation + GovernancePowerType delegationType ) internal { if (delegatee == address(0)) return; + if (delegatorBalanceBefore == delegatorBalanceAfter) return; - // FIXING A PRECISION ISSUE HERE - - // @dev to make delegated balance fit into uin72 we're decreasing precision of delegated balance by DELEGATED_POWER_DIVIDER - // uint72 delegationDelta = uint72( - // (userBalanceBefore / DELEGATED_POWER_DIVIDER) - (userBalanceAfter / DELEGATED_POWER_DIVIDER) - // ); - uint72 delegationDelta = uint72((userBalanceBefore - userBalanceAfter) / DELEGATED_POWER_DIVIDER); - if (delegationDelta == 0) return; + // To make delegated balance fit into uint72 we're decreasing precision of delegated balance by DELEGATOR_POWER_SCALE_FACTOR + uint72 delegatorBalanceBefore72 = uint72(delegatorBalanceBefore / DELEGATOR_POWER_SCALE_FACTOR); + uint72 delegatorBalanceAfter72 = uint72(delegatorBalanceAfter / DELEGATOR_POWER_SCALE_FACTOR); if (delegationType == GovernancePowerType.VOTING) { - _balances[delegatee].delegatedVotingBalance = operation( - _balances[delegatee].delegatedVotingBalance, - delegationDelta - ); - //TODO: emit DelegatedPowerChanged maybe; + _balances[delegatee].delegatedVotingBalance = + _balances[delegatee].delegatedVotingBalance - + delegatorBalanceBefore72 + + delegatorBalanceAfter72; } else { - _balances[delegatee].delegatedPropositionBalance = operation( - _balances[delegatee].delegatedPropositionBalance, - delegationDelta - ); - //TODO: emit DelegatedPowerChanged maybe; + _balances[delegatee].delegatedPropositionBalance = + _balances[delegatee].delegatedPropositionBalance - + delegatorBalanceBefore72 + + delegatorBalanceAfter72; } } - /** - * @dev changing one of governance power(Voting and Proposition) of delegatees depending on the delegator balance change - * @param user delegator - * @param userState the current state of the delegator - * @param balanceBefore delegator balance before operation - * @param balanceAfter delegator balance after operation - * @param operation math operation which will be applied depends on increasing or decreasing of the delegator balance (plus, minus) - **/ - function _delegationMove( - address user, - DelegationAwareBalance memory userState, - uint104 balanceBefore, - uint104 balanceAfter, - function(uint72, uint72) returns (uint72) operation - ) internal { - _delegationMoveByType( - balanceBefore, - balanceAfter, - _getDelegateeByType(user, userState, GovernancePowerType.VOTING), - GovernancePowerType.VOTING, - operation - ); - _delegationMoveByType( - balanceBefore, - balanceAfter, - _getDelegateeByType(user, userState, GovernancePowerType.PROPOSITION), - GovernancePowerType.PROPOSITION, - operation - ); - } - /** * @dev performs all state changes related to balance transfer and corresponding delegation changes * @param from token sender @@ -182,85 +202,113 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { uint104 fromBalanceAfter; unchecked { - //TODO: in general we don't need to check cast to uint104 because we know that it's less then balance from require fromBalanceAfter = fromUserState.balance - uint104(amount); } _balances[from].balance = fromBalanceAfter; - if (fromUserState.delegatingProposition || fromUserState.delegatingVoting) - _delegationMove( - from, - fromUserState, + if (fromUserState.delegationState != DelegationState.NO_DELEGATION) { + _governancePowerTransferByType( fromUserState.balance, fromBalanceAfter, - MathUtils.minus + _getDelegateeByType(from, fromUserState, GovernancePowerType.VOTING), + GovernancePowerType.VOTING ); + _governancePowerTransferByType( + fromUserState.balance, + fromBalanceAfter, + _getDelegateeByType(from, fromUserState, GovernancePowerType.PROPOSITION), + GovernancePowerType.PROPOSITION + ); + } } if (to != address(0)) { DelegationAwareBalance memory toUserState = _balances[to]; uint104 toBalanceBefore = toUserState.balance; - toUserState.balance = toBalanceBefore + uint104(amount); // TODO: check overflow? + toUserState.balance = toBalanceBefore + uint104(amount); _balances[to] = toUserState; - if (toUserState.delegatingVoting || toUserState.delegatingProposition) { - _delegationMove(to, toUserState, toUserState.balance, toBalanceBefore, MathUtils.plus); + if (toUserState.delegationState != DelegationState.NO_DELEGATION) { + _governancePowerTransferByType( + toUserState.balance, + toBalanceBefore, + _getDelegateeByType(to, toUserState, GovernancePowerType.VOTING), + GovernancePowerType.VOTING + ); + _governancePowerTransferByType( + toUserState.balance, + toBalanceBefore, + _getDelegateeByType(to, toUserState, GovernancePowerType.PROPOSITION), + GovernancePowerType.PROPOSITION + ); } } } /** - * @dev extracting and returning delegated governance power(Voting or Proposition) from user state + * @dev Extracts from state and returns delegated governance power (Voting, Proposition) * @param userState the current state of a user * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) **/ function _getDelegatedPowerByType( DelegationAwareBalance memory userState, GovernancePowerType delegationType - ) internal pure returns (uint72) { + ) internal pure returns (uint256) { return - delegationType == GovernancePowerType.VOTING - ? userState.delegatedVotingBalance - : userState.delegatedPropositionBalance; + DELEGATOR_POWER_SCALE_FACTOR * + ( + delegationType == GovernancePowerType.VOTING + ? userState.delegatedVotingBalance + : userState.delegatedPropositionBalance + ); } /** - * @dev extracts from user state and returning delegatee by type of governance power(Voting or Proposition) - * @param user delegator + * @dev Extracts from state and returns the delegatee of a delegator by type of governance power (Voting, Proposition) + * - If the delegator doesn't have any delegatee, returns address(0) + * @param delegator delegator * @param userState the current state of a user * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) **/ function _getDelegateeByType( - address user, + address delegator, DelegationAwareBalance memory userState, GovernancePowerType delegationType ) internal view returns (address) { if (delegationType == GovernancePowerType.VOTING) { - return userState.delegatingVoting ? _votingDelegateeV2[user] : address(0); + return + /// With the & operation, we cover both VOTING_DELEGATED delegation and FULL_POWER_DELEGATED + /// as VOTING_DELEGATED is equivalent to 01 in binary and FULL_POWER_DELEGATED is equivalent to 11 + (uint8(userState.delegationState) & uint8(DelegationState.VOTING_DELEGATED)) != 0 + ? _votingDelegateeV2[delegator] + : address(0); } - return userState.delegatingProposition ? _propositionDelegateeV2[user] : address(0); + return + userState.delegationState >= DelegationState.PROPOSITION_DELEGATED + ? _propositionDelegateeV2[delegator] + : address(0); } /** - * @dev changing user's delegatee address by type of governance power(Voting or Proposition) - * @param user delegator + * @dev Changes user's delegatee address by type of governance power (Voting, Proposition) + * @param delegator delegator * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) * @param _newDelegatee the new delegatee **/ function _updateDelegateeByType( - address user, + address delegator, GovernancePowerType delegationType, address _newDelegatee ) internal { - address newDelegatee = _newDelegatee == user ? address(0) : _newDelegatee; + address newDelegatee = _newDelegatee == delegator ? address(0) : _newDelegatee; if (delegationType == GovernancePowerType.VOTING) { - _votingDelegateeV2[user] = newDelegatee; + _votingDelegateeV2[delegator] = newDelegatee; } else { - _propositionDelegateeV2[user] = newDelegatee; + _propositionDelegateeV2[delegator] = newDelegatee; } } /** - * @dev updates the specific flag which signaling about existence of delegation of governance power(Voting or Proposition) + * @dev Updates the specific flag which signaling about existence of delegation of governance power (Voting, Proposition) * @param userState a user state to change * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) * @param willDelegate next state of delegation @@ -270,165 +318,117 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { GovernancePowerType delegationType, bool willDelegate ) internal pure returns (DelegationAwareBalance memory) { - if (delegationType == GovernancePowerType.VOTING) { - userState.delegatingVoting = willDelegate; + if (willDelegate) { + // Because GovernancePowerType starts from 0, we should add 1 first, then we apply bitwise OR + userState.delegationState = DelegationState( + uint8(userState.delegationState) | (uint8(delegationType) + 1) + ); } else { - userState.delegatingProposition = willDelegate; + // First bitwise NEGATION, ie was 01, after XOR with 11 will be 10, + // then bitwise AND, which means it will keep only another delegation type if it exists + userState.delegationState = DelegationState( + uint8(userState.delegationState) & + ((uint8(delegationType) + 1) ^ uint8(DelegationState.FULL_POWER_DELEGATED)) + ); } return userState; } /** - * @dev delegates the specific power to a delegatee - * @param user delegator - * @param _delegatee the user which delegated power has changed + * @dev This is the equivalent of an ERC20 transfer(), but for a power type: an atomic transfer of a balance (power). + * When needed, it decreases the power of the `delegator` and when needed, it increases the power of the `delegatee` + * @param delegator delegator + * @param _delegatee the user which delegated power will change * @param delegationType the type of delegation (VOTING, PROPOSITION) **/ function _delegateByType( - address user, + address delegator, address _delegatee, GovernancePowerType delegationType ) internal { - //we consider to 0x0 as delegation to self - address delegatee = _delegatee == user ? address(0) : _delegatee; - - DelegationAwareBalance memory userState = _balances[user]; - address currentDelegatee = _getDelegateeByType(user, userState, delegationType); + // Here we unify the property that delegating power to address(0) == delegating power to yourself == no delegation + // So from now on, not being delegating is (exclusively) that delegatee == address(0) + address delegatee = _delegatee == delegator ? address(0) : _delegatee; + + // We read the whole struct before validating delegatee, because in the optimistic case + // (_delegatee != currentDelegatee) we will reuse userState in the rest of the function + DelegationAwareBalance memory delegatorState = _balances[delegator]; + address currentDelegatee = _getDelegateeByType(delegator, delegatorState, delegationType); if (delegatee == currentDelegatee) return; bool delegatingNow = currentDelegatee != address(0); bool willDelegateAfter = delegatee != address(0); if (delegatingNow) { - _delegationMoveByType( - userState.balance, - 0, - currentDelegatee, - delegationType, - MathUtils.minus - ); + _governancePowerTransferByType(delegatorState.balance, 0, currentDelegatee, delegationType); } + if (willDelegateAfter) { - _updateDelegateeByType(user, delegationType, delegatee); - _delegationMoveByType(userState.balance, 0, delegatee, delegationType, MathUtils.plus); + _governancePowerTransferByType(0, delegatorState.balance, delegatee, delegationType); } + _updateDelegateeByType(delegator, delegationType, delegatee); + if (willDelegateAfter != delegatingNow) { - _balances[user] = _updateDelegationFlagByType(userState, delegationType, willDelegateAfter); + _balances[delegator] = _updateDelegationFlagByType( + delegatorState, + delegationType, + willDelegateAfter + ); } - emit DelegateChanged(user, delegatee, delegationType); + emit DelegateChanged(delegator, delegatee, delegationType); } - /// @inheritdoc IGovernancePowerDelegationToken - function delegateByType(address delegatee, GovernancePowerType delegationType) - external - virtual - override - { - _delegateByType(msg.sender, delegatee, delegationType); - } + /** + Harness section - replace struct reads and writes with function calls + */ - /// @inheritdoc IGovernancePowerDelegationToken - function delegate(address delegatee) external override { - _delegateByType(msg.sender, delegatee, GovernancePowerType.VOTING); - _delegateByType(msg.sender, delegatee, GovernancePowerType.PROPOSITION); - } +// struct DelegationAwareBalance { +// uint104 balance; +// uint72 delegatedPropositionBalance; +// uint72 delegatedVotingBalance; +// bool delegatingProposition; +// bool delegatingVoting; +// } - /// @inheritdoc IGovernancePowerDelegationToken - function getDelegateeByType(address delegator, GovernancePowerType delegationType) - external - view - override - returns (address) - { - return _getDelegateeByType(delegator, _balances[delegator], delegationType); - } - /// @inheritdoc IGovernancePowerDelegationToken - function getPowerCurrent(address user, GovernancePowerType delegationType) - external - view - override - returns (uint256) - { - DelegationAwareBalance memory userState = _balances[user]; - uint256 userOwnPower = (delegationType == GovernancePowerType.VOTING && - !userState.delegatingVoting) || - (delegationType == GovernancePowerType.PROPOSITION && !userState.delegatingProposition) - ? _balances[user].balance - : 0; - uint256 userDelegatedPower = _getDelegatedPowerByType(userState, delegationType) * - DELEGATED_POWER_DIVIDER; - return userOwnPower + userDelegatedPower; - } + function getBalance(address user) view public returns (uint104) { + return _balances[user].balance; + } - /// @inheritdoc IGovernancePowerDelegationToken - function metaDelegateByType( - address delegator, - address delegatee, - GovernancePowerType delegationType, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external override { - require(delegator != address(0), 'INVALID_OWNER'); - //solium-disable-next-line - require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); - uint256 currentValidNonce = _nonces[delegator]; - bytes32 digest = keccak256( - abi.encodePacked( - '\x19\x01', - DOMAIN_SEPARATOR, - keccak256( - abi.encode( - DELEGATE_BY_TYPE_TYPEHASH, - delegator, - delegatee, - delegationType, - currentValidNonce, - deadline - ) - ) - ) - ); + function getDelegatedPropositionBalance(address user) view public returns (uint72) { + return _balances[user].delegatedPropositionBalance; + } - require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); - unchecked { - // does not make sense to check because it's not realistic to reach uint256.max in nonce - _nonces[delegator] = currentValidNonce + 1; - } - _delegateByType(delegator, delegatee, delegationType); - } - /// @inheritdoc IGovernancePowerDelegationToken - function metaDelegate( - address delegator, - address delegatee, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external override { - require(delegator != address(0), 'INVALID_OWNER'); - //solium-disable-next-line - require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); - uint256 currentValidNonce = _nonces[delegator]; - bytes32 digest = keccak256( - abi.encodePacked( - '\x19\x01', - DOMAIN_SEPARATOR, - keccak256(abi.encode(DELEGATE_TYPEHASH, delegator, delegatee, currentValidNonce, deadline)) - ) - ); + function getDelegatedVotingBalance(address user) view public returns (uint72) { + return _balances[user].delegatedVotingBalance; + } - require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); - unchecked { - // does not make sense to check because it's not realistic to reach uint256.max in nonce - _nonces[delegator] = currentValidNonce + 1; - } - _delegateByType(delegator, delegatee, GovernancePowerType.VOTING); - _delegateByType(delegator, delegatee, GovernancePowerType.PROPOSITION); - } + + function getDelegatingProposition(address user) view public returns (bool) { + return _balances[user].delegationState == DelegationState.PROPOSITION_DELEGATED || + _balances[user].delegationState == DelegationState.FULL_POWER_DELEGATED; + } + + + function getDelegatingVoting(address user) view public returns (bool) { + return _balances[user].delegationState == DelegationState.VOTING_DELEGATED || + _balances[user].delegationState == DelegationState.FULL_POWER_DELEGATED; + } + + function getVotingDelegate(address user) view public returns (address) { + return _votingDelegateeV2[user]; + } + + function getPropositionDelegate(address user) view public returns (address) { + return _propositionDelegateeV2[user]; + } + + + + /** + End of harness section + */ } diff --git a/certora/harness/AaveTokenV3Harness_old.sol b/certora/harness/AaveTokenV3Harness_old.sol new file mode 100644 index 0000000..0c621e8 --- /dev/null +++ b/certora/harness/AaveTokenV3Harness_old.sol @@ -0,0 +1,433 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import {VersionedInitializable} from '../../src/utils/VersionedInitializable.sol'; + +import {IGovernancePowerDelegationToken} from '../../src/interfaces/IGovernancePowerDelegationToken.sol'; +import {BaseAaveTokenV2} from './BaseAaveTokenV2Harness.sol'; + +contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { + + + mapping(address => address) internal _votingDelegateeV2; + mapping(address => address) internal _propositionDelegateeV2; + + uint256 public constant DELEGATED_POWER_DIVIDER = 10**10; + + bytes32 public constant DELEGATE_BY_TYPE_TYPEHASH = + keccak256( + 'DelegateByType(address delegator,address delegatee,GovernancePowerType delegationType,uint256 nonce,uint256 deadline)' + ); + bytes32 public constant DELEGATE_TYPEHASH = + keccak256('Delegate(address delegator,address delegatee,uint256 nonce,uint256 deadline)'); + + /** + Harness section - replace struct reads and writes with function calls + */ + +// struct DelegationAwareBalance { +// uint104 balance; +// uint72 delegatedPropositionBalance; +// uint72 delegatedVotingBalance; +// bool delegatingProposition; +// bool delegatingVoting; +// } + + function _setBalance(address user, uint104 balance) internal { + _balances[user].balance = balance; + } + + function getBalance(address user) view public returns (uint104) { + return _balances[user].balance; + } + + function _setDelegatedPropositionBalance(address user, uint72 dpb) internal { + _balances[user].delegatedPropositionBalance = dpb; + } + + function getDelegatedPropositionBalance(address user) view public returns (uint72) { + return _balances[user].delegatedPropositionBalance; + } + + function _setDelegatedVotingBalance(address user, uint72 dvb) internal { + _balances[user].delegatedVotingBalance = dvb; + } + + function getDelegatedVotingBalance(address user) view public returns (uint72) { + return _balances[user].delegatedVotingBalance; + } + + function _setDelegatingProposition(address user, bool _delegating) internal { + _balances[user].delegatingProposition = _delegating; + } + + function getDelegatingProposition(address user) view public returns (bool) { + return _balances[user].delegatingProposition; + } + + function _setDelegatingVoting(address user, bool _delegating) internal { + _balances[user].delegatingVoting = _delegating; + } + + function getDelegatingVoting(address user) view public returns (bool) { + return _balances[user].delegatingVoting; + } + + function getVotingDelegate(address user) view public returns (address) { + return _votingDelegateeV2[user]; + } + + function getPropositionDelegate(address user) view public returns (address) { + return _propositionDelegateeV2[user]; + } + + + + /** + End of harness section + */ + + /** + * @dev changing one of delegated governance powers of delegatee depending on the delegator balance change + * @param userBalanceBefore delegator balance before operation + * @param userBalanceAfter delegator balance after operation + * @param delegatee the user whom delegated governance power will be changed + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + * @param operation math operation which will be applied depends on increasing or decreasing of the delegator balance (plus, minus) + **/ + function _delegationMoveByType( + uint104 userBalanceBefore, + uint104 userBalanceAfter, + address delegatee, + GovernancePowerType delegationType, + function(uint72, uint72) returns (uint72) operation + ) internal { + if (delegatee == address(0)) return; + + // FIXING A PRECISION ISSUE HERE + + // @dev to make delegated balance fit into uin72 we're decreasing precision of delegated balance by DELEGATED_POWER_DIVIDER + // uint72 delegationDelta = uint72( + // (userBalanceBefore / DELEGATED_POWER_DIVIDER) - (userBalanceAfter / DELEGATED_POWER_DIVIDER) + // ); + uint72 delegationDelta = uint72((userBalanceBefore - userBalanceAfter) / DELEGATED_POWER_DIVIDER); + if (delegationDelta == 0) return; + + if (delegationType == GovernancePowerType.VOTING) { + _balances[delegatee].delegatedVotingBalance = operation( + _balances[delegatee].delegatedVotingBalance, + delegationDelta + ); + //TODO: emit DelegatedPowerChanged maybe; + } else { + _balances[delegatee].delegatedPropositionBalance = operation( + _balances[delegatee].delegatedPropositionBalance, + delegationDelta + ); + //TODO: emit DelegatedPowerChanged maybe; + } + } + + /** + * @dev changing one of governance power(Voting and Proposition) of delegatees depending on the delegator balance change + * @param user delegator + * @param userState the current state of the delegator + * @param balanceBefore delegator balance before operation + * @param balanceAfter delegator balance after operation + * @param operation math operation which will be applied depends on increasing or decreasing of the delegator balance (plus, minus) + **/ + function _delegationMove( + address user, + DelegationAwareBalance memory userState, + uint104 balanceBefore, + uint104 balanceAfter, + function(uint72, uint72) returns (uint72) operation + ) internal { + _delegationMoveByType( + balanceBefore, + balanceAfter, + _getDelegateeByType(user, userState, GovernancePowerType.VOTING), + GovernancePowerType.VOTING, + operation + ); + _delegationMoveByType( + balanceBefore, + balanceAfter, + _getDelegateeByType(user, userState, GovernancePowerType.PROPOSITION), + GovernancePowerType.PROPOSITION, + operation + ); + } + + /** + * @dev performs all state changes related to balance transfer and corresponding delegation changes + * @param from token sender + * @param to token recipient + * @param amount amount of tokens sent + **/ + function _transferWithDelegation( + address from, + address to, + uint256 amount + ) internal override { + if (from == to) { + return; + } + + if (from != address(0)) { + DelegationAwareBalance memory fromUserState = _balances[from]; + require(fromUserState.balance >= amount, 'ERC20: transfer amount exceeds balance'); + + uint104 fromBalanceAfter; + unchecked { + //TODO: in general we don't need to check cast to uint104 because we know that it's less then balance from require + fromBalanceAfter = fromUserState.balance - uint104(amount); + } + _balances[from].balance = fromBalanceAfter; + if (fromUserState.delegatingProposition || fromUserState.delegatingVoting) + _delegationMove( + from, + fromUserState, + fromUserState.balance, + fromBalanceAfter, + MathUtils.minus + ); + } + + if (to != address(0)) { + DelegationAwareBalance memory toUserState = _balances[to]; + uint104 toBalanceBefore = toUserState.balance; + toUserState.balance = toBalanceBefore + uint104(amount); // TODO: check overflow? + _balances[to] = toUserState; + + if (toUserState.delegatingVoting || toUserState.delegatingProposition) { + _delegationMove(to, toUserState, toUserState.balance, toBalanceBefore, MathUtils.plus); + } + } + } + + /** + * @dev extracting and returning delegated governance power(Voting or Proposition) from user state + * @param userState the current state of a user + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + **/ + function _getDelegatedPowerByType( + DelegationAwareBalance memory userState, + GovernancePowerType delegationType + ) internal pure returns (uint72) { + return + delegationType == GovernancePowerType.VOTING + ? userState.delegatedVotingBalance + : userState.delegatedPropositionBalance; + } + + /** + * @dev extracts from user state and returning delegatee by type of governance power(Voting or Proposition) + * @param user delegator + * @param userState the current state of a user + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + **/ + function _getDelegateeByType( + address user, + DelegationAwareBalance memory userState, + GovernancePowerType delegationType + ) internal view returns (address) { + if (delegationType == GovernancePowerType.VOTING) { + return userState.delegatingVoting ? _votingDelegateeV2[user] : address(0); + } + return userState.delegatingProposition ? _propositionDelegateeV2[user] : address(0); + } + + /** + * @dev changing user's delegatee address by type of governance power(Voting or Proposition) + * @param user delegator + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + * @param _newDelegatee the new delegatee + **/ + function _updateDelegateeByType( + address user, + GovernancePowerType delegationType, + address _newDelegatee + ) internal { + address newDelegatee = _newDelegatee == user ? address(0) : _newDelegatee; + if (delegationType == GovernancePowerType.VOTING) { + _votingDelegateeV2[user] = newDelegatee; + } else { + _propositionDelegateeV2[user] = newDelegatee; + } + } + + /** + * @dev updates the specific flag which signaling about existence of delegation of governance power(Voting or Proposition) + * @param userState a user state to change + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + * @param willDelegate next state of delegation + **/ + function _updateDelegationFlagByType( + DelegationAwareBalance memory userState, + GovernancePowerType delegationType, + bool willDelegate + ) internal pure returns (DelegationAwareBalance memory) { + if (delegationType == GovernancePowerType.VOTING) { + userState.delegatingVoting = willDelegate; + } else { + userState.delegatingProposition = willDelegate; + } + return userState; + } + + /** + * @dev delegates the specific power to a delegatee + * @param user delegator + * @param _delegatee the user which delegated power has changed + * @param delegationType the type of delegation (VOTING, PROPOSITION) + **/ + function _delegateByType( + address user, + address _delegatee, + GovernancePowerType delegationType + ) internal { + //we consider to 0x0 as delegation to self + address delegatee = _delegatee == user ? address(0) : _delegatee; + + DelegationAwareBalance memory userState = _balances[user]; + address currentDelegatee = _getDelegateeByType(user, userState, delegationType); + if (delegatee == currentDelegatee) return; + + bool delegatingNow = currentDelegatee != address(0); + bool willDelegateAfter = delegatee != address(0); + + if (delegatingNow) { + _delegationMoveByType( + userState.balance, + 0, + currentDelegatee, + delegationType, + MathUtils.minus + ); + } + if (willDelegateAfter) { + _updateDelegateeByType(user, delegationType, delegatee); + _delegationMoveByType(userState.balance, 0, delegatee, delegationType, MathUtils.plus); + } + + if (willDelegateAfter != delegatingNow) { + _balances[user] = _updateDelegationFlagByType(userState, delegationType, willDelegateAfter); + } + + emit DelegateChanged(user, delegatee, delegationType); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function delegateByType(address delegatee, GovernancePowerType delegationType) + external + virtual + override + { + _delegateByType(msg.sender, delegatee, delegationType); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function delegate(address delegatee) external override { + _delegateByType(msg.sender, delegatee, GovernancePowerType.VOTING); + _delegateByType(msg.sender, delegatee, GovernancePowerType.PROPOSITION); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function getDelegateeByType(address delegator, GovernancePowerType delegationType) + external + view + override + returns (address) + { + return _getDelegateeByType(delegator, _balances[delegator], delegationType); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function getPowerCurrent(address user, GovernancePowerType delegationType) + external + view + override + returns (uint256) + { + DelegationAwareBalance memory userState = _balances[user]; + uint256 userOwnPower = (delegationType == GovernancePowerType.VOTING && + !userState.delegatingVoting) || + (delegationType == GovernancePowerType.PROPOSITION && !userState.delegatingProposition) + ? _balances[user].balance + : 0; + uint256 userDelegatedPower = _getDelegatedPowerByType(userState, delegationType) * + DELEGATED_POWER_DIVIDER; + return userOwnPower + userDelegatedPower; + } + + /// @inheritdoc IGovernancePowerDelegationToken + function metaDelegateByType( + address delegator, + address delegatee, + GovernancePowerType delegationType, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external override { + require(delegator != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _nonces[delegator]; + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256( + abi.encode( + DELEGATE_BY_TYPE_TYPEHASH, + delegator, + delegatee, + delegationType, + currentValidNonce, + deadline + ) + ) + ) + ); + + require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + unchecked { + // does not make sense to check because it's not realistic to reach uint256.max in nonce + _nonces[delegator] = currentValidNonce + 1; + } + _delegateByType(delegator, delegatee, delegationType); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function metaDelegate( + address delegator, + address delegatee, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external override { + require(delegator != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _nonces[delegator]; + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256(abi.encode(DELEGATE_TYPEHASH, delegator, delegatee, currentValidNonce, deadline)) + ) + ); + + require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + unchecked { + // does not make sense to check because it's not realistic to reach uint256.max in nonce + _nonces[delegator] = currentValidNonce + 1; + } + _delegateByType(delegator, delegatee, GovernancePowerType.VOTING); + _delegateByType(delegator, delegatee, GovernancePowerType.PROPOSITION); + } +} diff --git a/certora/harness/BaseAaveTokenHarness.sol b/certora/harness/BaseAaveTokenHarness.sol deleted file mode 100644 index cb85e8e..0000000 --- a/certora/harness/BaseAaveTokenHarness.sol +++ /dev/null @@ -1,301 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) - -// harness: balances and allowances are public variables - -pragma solidity ^0.8.0; - -import {Context} from '../../lib/openzeppelin-contracts/contracts/utils/Context.sol'; -import {IERC20} from '../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; -import {IERC20Metadata} from '../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; - - -/** - * @dev Implementation of the {IERC20} interface. - * - * This implementation is agnostic to the way tokens are created. This means - * that a supply mechanism has to be added in a derived contract using {_mint}. - * For a generic mechanism see {ERC20PresetMinterPauser}. - * - * TIP: For a detailed writeup see our guide - * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How - * to implement supply mechanisms]. - * - * We have followed general OpenZeppelin Contracts guidelines: functions revert - * instead returning `false` on failure. This behavior is nonetheless - * conventional and does not conflict with the expectations of ERC20 - * applications. - * - * Additionally, an {Approval} event is emitted on calls to {transferFrom}. - * This allows applications to reconstruct the allowance for all accounts just - * by listening to said events. Other implementations of the EIP may not emit - * these events, as it isn't required by the specification. - * - * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} - * functions have been added to mitigate the well-known issues around setting - * allowances. See {IERC20-approve}. - */ -abstract contract BaseAaveToken is Context, IERC20Metadata { - struct DelegationAwareBalance { - uint104 balance; - uint72 delegatedPropositionBalance; - uint72 delegatedVotingBalance; - bool delegatingProposition; - bool delegatingVoting; - } - - mapping(address => DelegationAwareBalance) public _balances; - - mapping(address => mapping(address => uint256)) public _allowances; - - uint256 internal _totalSupply; - - string internal _name; - string internal _symbol; - - // @dev DEPRECATED - // kept for backwards compatibility with old storage layout - uint8 private ______DEPRECATED_OLD_ERC20_DECIMALS; - - /** - * @dev Returns the name of the token. - */ - function name() public view virtual override returns (string memory) { - return _name; - } - - /** - * @dev Returns the symbol of the token, usually a shorter version of the - * name. - */ - function symbol() public view virtual override returns (string memory) { - return _symbol; - } - - /** - * @dev Returns the number of decimals used to get its user representation. - * For example, if `decimals` equals `2`, a balance of `505` tokens should - * be displayed to a user as `5.05` (`505 / 10 ** 2`). - * - * Tokens usually opt for a value of 18, imitating the relationship between - * Ether and Wei. This is the value {ERC20} uses, unless this function is - * overridden; - * - * NOTE: This information is only used for _display_ purposes: it in - * no way affects any of the arithmetic of the contract, including - * {IERC20-balanceOf} and {IERC20-transfer}. - */ - function decimals() public view virtual override returns (uint8) { - return 18; - } - - /** - * @dev See {IERC20-totalSupply}. - */ - function totalSupply() public view virtual override returns (uint256) { - return _totalSupply; - } - - /** - * @dev See {IERC20-balanceOf}. - */ - function balanceOf(address account) public view virtual override returns (uint256) { - return _balances[account].balance; - } - - /** - * @dev See {IERC20-transfer}. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - the caller must have a balance of at least `amount`. - */ - function transfer(address to, uint256 amount) public virtual override returns (bool) { - address owner = _msgSender(); - _transfer(owner, to, amount); - return true; - } - - /** - * @dev See {IERC20-allowance}. - */ - function allowance(address owner, address spender) - public - view - virtual - override - returns (uint256) - { - return _allowances[owner][spender]; - } - - /** - * @dev See {IERC20-approve}. - * - * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on - * `transferFrom`. This is semantically equivalent to an infinite approval. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function approve(address spender, uint256 amount) public virtual override returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, amount); - return true; - } - - /** - * @dev See {IERC20-transferFrom}. - * - * Emits an {Approval} event indicating the updated allowance. This is not - * required by the EIP. See the note at the beginning of {ERC20}. - * - * NOTE: Does not update the allowance if the current allowance - * is the maximum `uint256`. - * - * Requirements: - * - * - `from` and `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - * - the caller must have allowance for ``from``'s tokens of at least - * `amount`. - */ - function transferFrom( - address from, - address to, - uint256 amount - ) public virtual override returns (bool) { - address spender = _msgSender(); - _spendAllowance(from, spender, amount); - _transfer(from, to, amount); - return true; - } - - /** - * @dev Atomically increases the allowance granted to `spender` by the caller. - * - * This is an alternative to {approve} that can be used as a mitigation for - * problems described in {IERC20-approve}. - * - * Emits an {Approval} event indicating the updated allowance. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, _allowances[owner][spender] + addedValue); - return true; - } - - /** - * @dev Atomically decreases the allowance granted to `spender` by the caller. - * - * This is an alternative to {approve} that can be used as a mitigation for - * problems described in {IERC20-approve}. - * - * Emits an {Approval} event indicating the updated allowance. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - `spender` must have allowance for the caller of at least - * `subtractedValue`. - */ - function decreaseAllowance(address spender, uint256 subtractedValue) - public - virtual - returns (bool) - { - address owner = _msgSender(); - uint256 currentAllowance = _allowances[owner][spender]; - require(currentAllowance >= subtractedValue, 'ERC20: decreased allowance below zero'); - unchecked { - _approve(owner, spender, currentAllowance - subtractedValue); - } - - return true; - } - - /** - * @dev Moves `amount` of tokens from `sender` to `recipient`. - * - * This internal function is equivalent to {transfer}, and can be used to - * e.g. implement automatic token fees, slashing mechanisms, etc. - * - * Emits a {Transfer} event. - * - * Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - */ - function _transfer( - address from, - address to, - uint256 amount - ) internal virtual { - require(from != address(0), 'ERC20: transfer from the zero address'); - require(to != address(0), 'ERC20: transfer to the zero address'); - - _transferWithDelegation(from, to, amount); - emit Transfer(from, to, amount); - } - - /** - * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. - * - * This internal function is equivalent to `approve`, and can be used to - * e.g. set automatic allowances for certain subsystems, etc. - * - * Emits an {Approval} event. - * - * Requirements: - * - * - `owner` cannot be the zero address. - * - `spender` cannot be the zero address. - */ - function _approve( - address owner, - address spender, - uint256 amount - ) internal virtual { - require(owner != address(0), 'ERC20: approve from the zero address'); - require(spender != address(0), 'ERC20: approve to the zero address'); - - _allowances[owner][spender] = amount; - emit Approval(owner, spender, amount); - } - - /** - * @dev Spend `amount` form the allowance of `owner` toward `spender`. - * - * Does not update the allowance amount in case of infinite allowance. - * Revert if not enough allowance is available. - * - * Might emit an {Approval} event. - */ - function _spendAllowance( - address owner, - address spender, - uint256 amount - ) internal virtual { - uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance != type(uint256).max) { - require(currentAllowance >= amount, 'ERC20: insufficient allowance'); - unchecked { - _approve(owner, spender, currentAllowance - amount); - } - } - } - - function _transferWithDelegation( - address from, - address to, - uint256 amount - ) internal virtual {} -} diff --git a/certora/harness/BaseAaveTokenV2Harness.sol b/certora/harness/BaseAaveTokenV2Harness.sol deleted file mode 100644 index 9108728..0000000 --- a/certora/harness/BaseAaveTokenV2Harness.sol +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -// harness: import BaseAaveToken from harness file - -import {VersionedInitializable} from '../../src/utils/VersionedInitializable.sol'; - -import {BaseAaveToken} from './BaseAaveTokenHarness.sol'; - -abstract contract BaseAaveTokenV2 is BaseAaveToken, VersionedInitializable { - /// @dev owner => next valid nonce to submit with permit() - mapping(address => uint256) public _nonces; - - ///////// @dev DEPRECATED from AaveToken v1 ////////////////////////// - //////// kept for backwards compatibility with old storage layout //// - uint256[3] private ______DEPRECATED_FROM_AAVE_V1; - ///////// @dev END OF DEPRECATED from AaveToken v1 ////////////////////////// - - bytes32 public DOMAIN_SEPARATOR; - - ///////// @dev DEPRECATED from AaveToken v2 ////////////////////////// - //////// kept for backwards compatibility with old storage layout //// - uint256[4] private ______DEPRECATED_FROM_AAVE_V2; - ///////// @dev END OF DEPRECATED from AaveToken v2 ////////////////////////// - - bytes public constant EIP712_REVISION = bytes('1'); - bytes32 internal constant EIP712_DOMAIN = - keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'); - bytes32 public constant PERMIT_TYPEHASH = - keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'); - - uint256 public constant REVISION = 3; // TODO: CHECK, but most probably was 2 before - - /** - * @dev initializes the contract upon assignment to the InitializableAdminUpgradeabilityProxy - */ - function initialize() external initializer {} - - /** - * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md - * @param owner the owner of the funds - * @param spender the spender - * @param value the amount - * @param deadline the deadline timestamp, type(uint256).max for no deadline - * @param v signature param - * @param s signature param - * @param r signature param - */ - - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external { - require(owner != address(0), 'INVALID_OWNER'); - //solium-disable-next-line - require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); - uint256 currentValidNonce = _nonces[owner]; - bytes32 digest = keccak256( - abi.encodePacked( - '\x19\x01', - DOMAIN_SEPARATOR, - keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline)) - ) - ); - - require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); - unchecked { - // does not make sense to check because it's not realistic to reach uint256.max in nonce - _nonces[owner] = currentValidNonce + 1; - } - _approve(owner, spender, value); - } - - /** - * @dev returns the revision of the implementation contract - */ - function getRevision() internal pure override returns (uint256) { - return REVISION; - } -} diff --git a/certora/scripts/verifyBgdSpec.sh b/certora/scripts/verifyBgdSpec.sh index 4e4eacf..7ddf709 100755 --- a/certora/scripts/verifyBgdSpec.sh +++ b/certora/scripts/verifyBgdSpec.sh @@ -9,6 +9,6 @@ certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ --solc solc8.13 \ --optimistic_loop \ --send_only \ - --staging \ +# --staging \ --msg "AaveTokenV3:bgdSpec.spec $1" \ No newline at end of file diff --git a/certora/specs/bgdSpec.spec b/certora/specs/bgdSpec.spec index f4878ec..c738122 100644 --- a/certora/specs/bgdSpec.spec +++ b/certora/specs/bgdSpec.spec @@ -549,4 +549,22 @@ rule vpTransferWhenBothAreDelegating(address alice, address bob, address charlie assert bobPowerAfter == bobPowerBefore; assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - normalize(amount); assert bobDelegatePowerAfter == bobDelegatePowerBefore + normalize (amount); -} \ No newline at end of file +} + +/*** + +aliceDelegate before: 0x2cd68cfcc800 = 49300000000000 +bobDelegate before: 0x63ced5b7b800 = 109740000000000 + +transfer: alice->bob 246 + +aliceDelegate after: 0x2cd68cfcc800 = 49300000000000 +bobDelegate after: 0x63cc81abd400 = 109730000000000 + +0x143ecf6488f6, delegatorBalanceAfter=0x143ecf648800 + +balbefore: 0x569c76526c00 = 95230000000000 = 9523 +balAfter:0x569c76526b0a = 95229999999754 = 9522 + + +*/ \ No newline at end of file From 1a71906551208776f2e6f1105ba6407b532f9cec Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Tue, 19 Jul 2022 14:36:41 +0300 Subject: [PATCH 22/58] rebase from small-loss-precision branch --- certora/harness/AaveTokenV3Harness.sol | 94 +++++++++++++++++--------- certora/specs/bgdSpec.spec | 10 ++- 2 files changed, 69 insertions(+), 35 deletions(-) diff --git a/certora/harness/AaveTokenV3Harness.sol b/certora/harness/AaveTokenV3Harness.sol index b3a69e6..49378c7 100644 --- a/certora/harness/AaveTokenV3Harness.sol +++ b/certora/harness/AaveTokenV3Harness.sol @@ -6,14 +6,12 @@ import {IGovernancePowerDelegationToken} from '../../src/interfaces/IGovernanceP import {BaseAaveTokenV2} from '../../src/BaseAaveTokenV2.sol'; contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { - - mapping(address => address) internal _votingDelegateeV2; mapping(address => address) internal _propositionDelegateeV2; - // @dev we assume that for the governance system 18 decimals of precision is not needed, + /// @dev we assume that for the governance system 18 decimals of precision is not needed, // by this constant we reduce it by 10, to 8 decimals - uint256 public constant DELEGATOR_POWER_SCALE_FACTOR = 1e10; + uint256 public constant POWER_SCALE_FACTOR = 1e10; bytes32 public constant DELEGATE_BY_TYPE_TYPEHASH = keccak256( @@ -149,35 +147,42 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { } /** - * @dev Changing one of delegated governance powers of delegatee depending on the delegator balance change - * @param delegatorBalanceBefore delegator balance before operation - * @param delegatorBalanceAfter delegator balance after operation + * @dev Modifies the delegated power of a `delegatee` account by type (VOTING, PROPOSITION). + * Passing the impact on the delegation of `delegatee` account before and after to reduce conditionals and not lose + * any precision. + * @param impactOnDelegationBefore how much impact a balance of another account had over the delegation of a `delegatee` + * before an action. + * For example, if the action is a delegation from one account to another, the impact before the action will be 0. + * @param impactOnDelegationAfter how much impact a balance of another account will have over the delegation of a `delegatee` + * after an action. + * For example, if the action is a delegation from one account to another, the impact after the action will be the whole balance + * of the account changing the delegatee. * @param delegatee the user whom delegated governance power will be changed * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) **/ function _governancePowerTransferByType( - uint104 delegatorBalanceBefore, - uint104 delegatorBalanceAfter, + uint104 impactOnDelegationBefore, + uint104 impactOnDelegationAfter, address delegatee, GovernancePowerType delegationType ) internal { if (delegatee == address(0)) return; - if (delegatorBalanceBefore == delegatorBalanceAfter) return; + if (impactOnDelegationBefore == impactOnDelegationAfter) return; - // To make delegated balance fit into uint72 we're decreasing precision of delegated balance by DELEGATOR_POWER_SCALE_FACTOR - uint72 delegatorBalanceBefore72 = uint72(delegatorBalanceBefore / DELEGATOR_POWER_SCALE_FACTOR); - uint72 delegatorBalanceAfter72 = uint72(delegatorBalanceAfter / DELEGATOR_POWER_SCALE_FACTOR); + // To make delegated balance fit into uint72 we're decreasing precision of delegated balance by POWER_SCALE_FACTOR + uint72 impactOnDelegationBefore72 = uint72(impactOnDelegationBefore / POWER_SCALE_FACTOR); + uint72 impactOnDelegationAfter72 = uint72(impactOnDelegationAfter / POWER_SCALE_FACTOR); if (delegationType == GovernancePowerType.VOTING) { _balances[delegatee].delegatedVotingBalance = _balances[delegatee].delegatedVotingBalance - - delegatorBalanceBefore72 + - delegatorBalanceAfter72; + impactOnDelegationBefore72 + + impactOnDelegationAfter72; } else { _balances[delegatee].delegatedPropositionBalance = _balances[delegatee].delegatedPropositionBalance - - delegatorBalanceBefore72 + - delegatorBalanceAfter72; + impactOnDelegationBefore72 + + impactOnDelegationAfter72; } } @@ -224,50 +229,73 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { if (to != address(0)) { DelegationAwareBalance memory toUserState = _balances[to]; uint104 toBalanceBefore = toUserState.balance; - toUserState.balance = toBalanceBefore + uint104(amount); // TODO: check overflow? + toUserState.balance = toBalanceBefore + uint104(amount); _balances[to] = toUserState; - if (toUserState.delegatingVoting || toUserState.delegatingProposition) { - _delegationMove(to, toUserState, toUserState.balance, toBalanceBefore, MathUtils.plus); + if (toUserState.delegationState != DelegationState.NO_DELEGATION) { + _governancePowerTransferByType( + toUserState.balance, + toBalanceBefore, + _getDelegateeByType(to, toUserState, GovernancePowerType.VOTING), + GovernancePowerType.VOTING + ); + _governancePowerTransferByType( + toUserState.balance, + toBalanceBefore, + _getDelegateeByType(to, toUserState, GovernancePowerType.PROPOSITION), + GovernancePowerType.PROPOSITION + ); } } } /** - * @dev extracting and returning delegated governance power(Voting or Proposition) from user state + * @dev Extracts from state and returns delegated governance power (Voting, Proposition) * @param userState the current state of a user * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) **/ function _getDelegatedPowerByType( DelegationAwareBalance memory userState, GovernancePowerType delegationType - ) internal pure returns (uint72) { + ) internal pure returns (uint256) { return - delegationType == GovernancePowerType.VOTING - ? userState.delegatedVotingBalance - : userState.delegatedPropositionBalance; + POWER_SCALE_FACTOR * + ( + delegationType == GovernancePowerType.VOTING + ? userState.delegatedVotingBalance + : userState.delegatedPropositionBalance + ); } /** - * @dev extracts from user state and returning delegatee by type of governance power(Voting or Proposition) - * @param user delegator + * @dev Extracts from state and returns the delegatee of a delegator by type of governance power (Voting, Proposition) + * - If the delegator doesn't have any delegatee, returns address(0) + * @param delegator delegator * @param userState the current state of a user * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) **/ function _getDelegateeByType( - address user, + address delegator, DelegationAwareBalance memory userState, GovernancePowerType delegationType ) internal view returns (address) { if (delegationType == GovernancePowerType.VOTING) { - return userState.delegatingVoting ? _votingDelegateeV2[user] : address(0); + return + /// With the & operation, we cover both VOTING_DELEGATED delegation and FULL_POWER_DELEGATED + /// as VOTING_DELEGATED is equivalent to 01 in binary and FULL_POWER_DELEGATED is equivalent to 11 + (uint8(userState.delegationState) & uint8(DelegationState.VOTING_DELEGATED)) != 0 + ? _votingDelegateeV2[delegator] + : address(0); } - return userState.delegatingProposition ? _propositionDelegateeV2[user] : address(0); + return + userState.delegationState >= DelegationState.PROPOSITION_DELEGATED + ? _propositionDelegateeV2[delegator] + : address(0); } /** - * @dev changing user's delegatee address by type of governance power(Voting or Proposition) - * @param user delegator + * @dev Changes user's delegatee address by type of governance power (Voting, Proposition) + * @param delegator delegator * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) * @param _newDelegatee the new delegatee **/ @@ -357,7 +385,7 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { emit DelegateChanged(delegator, delegatee, delegationType); } - /** + /** Harness section - replace struct reads and writes with function calls */ diff --git a/certora/specs/bgdSpec.spec b/certora/specs/bgdSpec.spec index a6ac5a4..57a6c14 100644 --- a/certora/specs/bgdSpec.spec +++ b/certora/specs/bgdSpec.spec @@ -536,6 +536,8 @@ rule vpTransferWhenBothAreDelegating(address alice, address bob, address charlie uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, VOTING_POWER()); uint256 bobDelegatePowerBefore = getPowerCurrent(bobDelegate, VOTING_POWER()); + uint256 aliceBalanceBefore = balanceOf(alice); + uint256 bobBalanceBefore = balanceOf(bob); transferFrom(e, alice, bob, amount); @@ -544,9 +546,13 @@ rule vpTransferWhenBothAreDelegating(address alice, address bob, address charlie uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, VOTING_POWER()); uint256 bobDelegatePowerAfter = getPowerCurrent(bobDelegate, VOTING_POWER()); + uint256 aliceBalanceAfter = balanceOf(alice); + uint256 bobBalanceafter = balanceOf(bob); assert alicePowerAfter == alicePowerBefore; assert bobPowerAfter == bobPowerBefore; - assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - normalize(amount); - assert bobDelegatePowerAfter == bobDelegatePowerBefore + normalize (amount); + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - normalize(aliceBalanceBefore) + + normalize(aliceBalanceAfter); + assert bobDelegatePowerAfter == bobDelegatePowerBefore - normalize(bobBalanceBefore) + + normalize(bobBalanceafter); } From d2e3e2a090b4d41042cec1e185c2771e519437ad Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Wed, 20 Jul 2022 11:56:55 +0300 Subject: [PATCH 23/58] add a setup --- certora/harness/AaveTokenV3Harness.sol | 16 +- certora/harness/BaseAaveTokenHarness.sol | 301 --------------------- certora/harness/BaseAaveTokenV2Harness.sol | 86 ------ certora/scripts/setup.sh | 14 + certora/specs/bgdSpec.spec | 47 +++- certora/specs/setup.spec | 101 ++++++- 6 files changed, 171 insertions(+), 394 deletions(-) delete mode 100644 certora/harness/BaseAaveTokenHarness.sol delete mode 100644 certora/harness/BaseAaveTokenV2Harness.sol create mode 100644 certora/scripts/setup.sh diff --git a/certora/harness/AaveTokenV3Harness.sol b/certora/harness/AaveTokenV3Harness.sol index 49378c7..1e24b2e 100644 --- a/certora/harness/AaveTokenV3Harness.sol +++ b/certora/harness/AaveTokenV3Harness.sol @@ -165,7 +165,7 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { uint104 impactOnDelegationAfter, address delegatee, GovernancePowerType delegationType - ) internal { + ) public { // public instead of internal for testing a particular condition in this function if (delegatee == address(0)) return; if (impactOnDelegationBefore == impactOnDelegationAfter) return; @@ -173,6 +173,16 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { uint72 impactOnDelegationBefore72 = uint72(impactOnDelegationBefore / POWER_SCALE_FACTOR); uint72 impactOnDelegationAfter72 = uint72(impactOnDelegationAfter / POWER_SCALE_FACTOR); + bool testCondition = (delegationType == GovernancePowerType.VOTING + && + _balances[delegatee].delegatedVotingBalance < impactOnDelegationBefore72) + || ( + delegationType == GovernancePowerType.PROPOSITION + && + _balances[delegatee].delegatedPropositionBalance < impactOnDelegationBefore72 + ); + require(!testCondition); + if (delegationType == GovernancePowerType.VOTING) { _balances[delegatee].delegatedVotingBalance = _balances[delegatee].delegatedVotingBalance - @@ -234,14 +244,14 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { if (toUserState.delegationState != DelegationState.NO_DELEGATION) { _governancePowerTransferByType( - toUserState.balance, toBalanceBefore, + toUserState.balance, _getDelegateeByType(to, toUserState, GovernancePowerType.VOTING), GovernancePowerType.VOTING ); _governancePowerTransferByType( - toUserState.balance, toBalanceBefore, + toUserState.balance, _getDelegateeByType(to, toUserState, GovernancePowerType.PROPOSITION), GovernancePowerType.PROPOSITION ); diff --git a/certora/harness/BaseAaveTokenHarness.sol b/certora/harness/BaseAaveTokenHarness.sol deleted file mode 100644 index cb85e8e..0000000 --- a/certora/harness/BaseAaveTokenHarness.sol +++ /dev/null @@ -1,301 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) - -// harness: balances and allowances are public variables - -pragma solidity ^0.8.0; - -import {Context} from '../../lib/openzeppelin-contracts/contracts/utils/Context.sol'; -import {IERC20} from '../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; -import {IERC20Metadata} from '../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; - - -/** - * @dev Implementation of the {IERC20} interface. - * - * This implementation is agnostic to the way tokens are created. This means - * that a supply mechanism has to be added in a derived contract using {_mint}. - * For a generic mechanism see {ERC20PresetMinterPauser}. - * - * TIP: For a detailed writeup see our guide - * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How - * to implement supply mechanisms]. - * - * We have followed general OpenZeppelin Contracts guidelines: functions revert - * instead returning `false` on failure. This behavior is nonetheless - * conventional and does not conflict with the expectations of ERC20 - * applications. - * - * Additionally, an {Approval} event is emitted on calls to {transferFrom}. - * This allows applications to reconstruct the allowance for all accounts just - * by listening to said events. Other implementations of the EIP may not emit - * these events, as it isn't required by the specification. - * - * Finally, the non-standard {decreaseAllowance} and {increaseAllowance} - * functions have been added to mitigate the well-known issues around setting - * allowances. See {IERC20-approve}. - */ -abstract contract BaseAaveToken is Context, IERC20Metadata { - struct DelegationAwareBalance { - uint104 balance; - uint72 delegatedPropositionBalance; - uint72 delegatedVotingBalance; - bool delegatingProposition; - bool delegatingVoting; - } - - mapping(address => DelegationAwareBalance) public _balances; - - mapping(address => mapping(address => uint256)) public _allowances; - - uint256 internal _totalSupply; - - string internal _name; - string internal _symbol; - - // @dev DEPRECATED - // kept for backwards compatibility with old storage layout - uint8 private ______DEPRECATED_OLD_ERC20_DECIMALS; - - /** - * @dev Returns the name of the token. - */ - function name() public view virtual override returns (string memory) { - return _name; - } - - /** - * @dev Returns the symbol of the token, usually a shorter version of the - * name. - */ - function symbol() public view virtual override returns (string memory) { - return _symbol; - } - - /** - * @dev Returns the number of decimals used to get its user representation. - * For example, if `decimals` equals `2`, a balance of `505` tokens should - * be displayed to a user as `5.05` (`505 / 10 ** 2`). - * - * Tokens usually opt for a value of 18, imitating the relationship between - * Ether and Wei. This is the value {ERC20} uses, unless this function is - * overridden; - * - * NOTE: This information is only used for _display_ purposes: it in - * no way affects any of the arithmetic of the contract, including - * {IERC20-balanceOf} and {IERC20-transfer}. - */ - function decimals() public view virtual override returns (uint8) { - return 18; - } - - /** - * @dev See {IERC20-totalSupply}. - */ - function totalSupply() public view virtual override returns (uint256) { - return _totalSupply; - } - - /** - * @dev See {IERC20-balanceOf}. - */ - function balanceOf(address account) public view virtual override returns (uint256) { - return _balances[account].balance; - } - - /** - * @dev See {IERC20-transfer}. - * - * Requirements: - * - * - `to` cannot be the zero address. - * - the caller must have a balance of at least `amount`. - */ - function transfer(address to, uint256 amount) public virtual override returns (bool) { - address owner = _msgSender(); - _transfer(owner, to, amount); - return true; - } - - /** - * @dev See {IERC20-allowance}. - */ - function allowance(address owner, address spender) - public - view - virtual - override - returns (uint256) - { - return _allowances[owner][spender]; - } - - /** - * @dev See {IERC20-approve}. - * - * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on - * `transferFrom`. This is semantically equivalent to an infinite approval. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function approve(address spender, uint256 amount) public virtual override returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, amount); - return true; - } - - /** - * @dev See {IERC20-transferFrom}. - * - * Emits an {Approval} event indicating the updated allowance. This is not - * required by the EIP. See the note at the beginning of {ERC20}. - * - * NOTE: Does not update the allowance if the current allowance - * is the maximum `uint256`. - * - * Requirements: - * - * - `from` and `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - * - the caller must have allowance for ``from``'s tokens of at least - * `amount`. - */ - function transferFrom( - address from, - address to, - uint256 amount - ) public virtual override returns (bool) { - address spender = _msgSender(); - _spendAllowance(from, spender, amount); - _transfer(from, to, amount); - return true; - } - - /** - * @dev Atomically increases the allowance granted to `spender` by the caller. - * - * This is an alternative to {approve} that can be used as a mitigation for - * problems described in {IERC20-approve}. - * - * Emits an {Approval} event indicating the updated allowance. - * - * Requirements: - * - * - `spender` cannot be the zero address. - */ - function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, _allowances[owner][spender] + addedValue); - return true; - } - - /** - * @dev Atomically decreases the allowance granted to `spender` by the caller. - * - * This is an alternative to {approve} that can be used as a mitigation for - * problems described in {IERC20-approve}. - * - * Emits an {Approval} event indicating the updated allowance. - * - * Requirements: - * - * - `spender` cannot be the zero address. - * - `spender` must have allowance for the caller of at least - * `subtractedValue`. - */ - function decreaseAllowance(address spender, uint256 subtractedValue) - public - virtual - returns (bool) - { - address owner = _msgSender(); - uint256 currentAllowance = _allowances[owner][spender]; - require(currentAllowance >= subtractedValue, 'ERC20: decreased allowance below zero'); - unchecked { - _approve(owner, spender, currentAllowance - subtractedValue); - } - - return true; - } - - /** - * @dev Moves `amount` of tokens from `sender` to `recipient`. - * - * This internal function is equivalent to {transfer}, and can be used to - * e.g. implement automatic token fees, slashing mechanisms, etc. - * - * Emits a {Transfer} event. - * - * Requirements: - * - * - `from` cannot be the zero address. - * - `to` cannot be the zero address. - * - `from` must have a balance of at least `amount`. - */ - function _transfer( - address from, - address to, - uint256 amount - ) internal virtual { - require(from != address(0), 'ERC20: transfer from the zero address'); - require(to != address(0), 'ERC20: transfer to the zero address'); - - _transferWithDelegation(from, to, amount); - emit Transfer(from, to, amount); - } - - /** - * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens. - * - * This internal function is equivalent to `approve`, and can be used to - * e.g. set automatic allowances for certain subsystems, etc. - * - * Emits an {Approval} event. - * - * Requirements: - * - * - `owner` cannot be the zero address. - * - `spender` cannot be the zero address. - */ - function _approve( - address owner, - address spender, - uint256 amount - ) internal virtual { - require(owner != address(0), 'ERC20: approve from the zero address'); - require(spender != address(0), 'ERC20: approve to the zero address'); - - _allowances[owner][spender] = amount; - emit Approval(owner, spender, amount); - } - - /** - * @dev Spend `amount` form the allowance of `owner` toward `spender`. - * - * Does not update the allowance amount in case of infinite allowance. - * Revert if not enough allowance is available. - * - * Might emit an {Approval} event. - */ - function _spendAllowance( - address owner, - address spender, - uint256 amount - ) internal virtual { - uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance != type(uint256).max) { - require(currentAllowance >= amount, 'ERC20: insufficient allowance'); - unchecked { - _approve(owner, spender, currentAllowance - amount); - } - } - } - - function _transferWithDelegation( - address from, - address to, - uint256 amount - ) internal virtual {} -} diff --git a/certora/harness/BaseAaveTokenV2Harness.sol b/certora/harness/BaseAaveTokenV2Harness.sol deleted file mode 100644 index 9108728..0000000 --- a/certora/harness/BaseAaveTokenV2Harness.sol +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -// harness: import BaseAaveToken from harness file - -import {VersionedInitializable} from '../../src/utils/VersionedInitializable.sol'; - -import {BaseAaveToken} from './BaseAaveTokenHarness.sol'; - -abstract contract BaseAaveTokenV2 is BaseAaveToken, VersionedInitializable { - /// @dev owner => next valid nonce to submit with permit() - mapping(address => uint256) public _nonces; - - ///////// @dev DEPRECATED from AaveToken v1 ////////////////////////// - //////// kept for backwards compatibility with old storage layout //// - uint256[3] private ______DEPRECATED_FROM_AAVE_V1; - ///////// @dev END OF DEPRECATED from AaveToken v1 ////////////////////////// - - bytes32 public DOMAIN_SEPARATOR; - - ///////// @dev DEPRECATED from AaveToken v2 ////////////////////////// - //////// kept for backwards compatibility with old storage layout //// - uint256[4] private ______DEPRECATED_FROM_AAVE_V2; - ///////// @dev END OF DEPRECATED from AaveToken v2 ////////////////////////// - - bytes public constant EIP712_REVISION = bytes('1'); - bytes32 internal constant EIP712_DOMAIN = - keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'); - bytes32 public constant PERMIT_TYPEHASH = - keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'); - - uint256 public constant REVISION = 3; // TODO: CHECK, but most probably was 2 before - - /** - * @dev initializes the contract upon assignment to the InitializableAdminUpgradeabilityProxy - */ - function initialize() external initializer {} - - /** - * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md - * @param owner the owner of the funds - * @param spender the spender - * @param value the amount - * @param deadline the deadline timestamp, type(uint256).max for no deadline - * @param v signature param - * @param s signature param - * @param r signature param - */ - - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external { - require(owner != address(0), 'INVALID_OWNER'); - //solium-disable-next-line - require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); - uint256 currentValidNonce = _nonces[owner]; - bytes32 digest = keccak256( - abi.encodePacked( - '\x19\x01', - DOMAIN_SEPARATOR, - keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline)) - ) - ); - - require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); - unchecked { - // does not make sense to check because it's not realistic to reach uint256.max in nonce - _nonces[owner] = currentValidNonce + 1; - } - _approve(owner, spender, value); - } - - /** - * @dev returns the revision of the implementation contract - */ - function getRevision() internal pure override returns (uint256) { - return REVISION; - } -} diff --git a/certora/scripts/setup.sh b/certora/scripts/setup.sh new file mode 100644 index 0000000..64d17ef --- /dev/null +++ b/certora/scripts/setup.sh @@ -0,0 +1,14 @@ +if [[ "$1" ]] +then + RULE="--rule $1" +fi + +certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ + --verify AaveTokenV3:certora/specs/setup.spec \ + --rule $1 \ + --solc solc8.13 \ + --optimistic_loop \ + --send_only \ +# --staging \ + --msg "AaveTokenV3:setup.spec $1" + \ No newline at end of file diff --git a/certora/specs/bgdSpec.spec b/certora/specs/bgdSpec.spec index 57a6c14..aa67110 100644 --- a/certora/specs/bgdSpec.spec +++ b/certora/specs/bgdSpec.spec @@ -21,6 +21,8 @@ methods{ getDelegatingVoting(address user) returns (bool) envfree getVotingDelegate(address user) returns (address) envfree getPropositionDelegate(address user) returns (address) envfree + + _governancePowerTransferByType(uint104, uint104, address, uint8) } definition VOTING_POWER() returns uint8 = 0; @@ -547,12 +549,51 @@ rule vpTransferWhenBothAreDelegating(address alice, address bob, address charlie uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, VOTING_POWER()); uint256 bobDelegatePowerAfter = getPowerCurrent(bobDelegate, VOTING_POWER()); uint256 aliceBalanceAfter = balanceOf(alice); - uint256 bobBalanceafter = balanceOf(bob); + uint256 bobBalanceAfter = balanceOf(bob); assert alicePowerAfter == alicePowerBefore; assert bobPowerAfter == bobPowerBefore; assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - normalize(aliceBalanceBefore) + normalize(aliceBalanceAfter); - assert bobDelegatePowerAfter == bobDelegatePowerBefore - normalize(bobBalanceBefore) - + normalize(bobBalanceafter); + + uint256 normalizedBalanceBefore = normalize(bobBalanceBefore); + uint256 normalizedBalanceAfter = normalize(bobBalanceAfter); + uint256 delta = bobBalanceAfter - bobBalanceBefore; + assert bobDelegatePowerAfter == bobDelegatePowerBefore - normalizedBalanceBefore + normalizedBalanceAfter; + // assert bobDelegatePowerAfter == bobDelegatePowerBefore - normalize(delta); } + +rule test_governancePowerTransferByType(uint104 impactBefore, uint104 impactAfter, address delegatee, uint8 type) { + env e; + require type == 0 || type == 1; + + _governancePowerTransferByType@withrevert(e, impactBefore, impactAfter, delegatee, type); + assert !lastReverted; +} + + +/*** + + +bobDelegate power before: 0x30d4ad60c400 = 53690000000000 +bob balance before: 0x2540c00cb = 10000007371 + +transfer alice -> bob 0x6fc238f34 = 29999992628 + +bob balance after: 0x9502f8fff = 39999999999 +bobDelegate power after: 0x30d00548fc00 = 53670000000000 + +expected: 0x30d25954e000 = 53680000000000 +0x30d955788c00 + + +delta: 0x1001d1bf7ff = 1099999999999 + +1. bobDelegate power before: 0x30d4ad60c400 = 53690000000000 +2. bobDelegate power after: 0x30d00548fc00 = 53670000000000 + +3. bobDelegatePowerBefore-normalize(bobBalanceBefore)+intnormalize(bobBalanceafter) = +53690000000000 - normalize(10000007371) + normalize(39999999999) = +53690000000000 - 10000000000 + 30000000000 = 53710000000000 + +*/ \ No newline at end of file diff --git a/certora/specs/setup.spec b/certora/specs/setup.spec index f3c54f9..12354aa 100644 --- a/certora/specs/setup.spec +++ b/certora/specs/setup.spec @@ -1 +1,100 @@ -// unit test, invariant, parametric test, ghost + hook, documentation \ No newline at end of file +/** + + Setup for writing rules for Aave Token v3 + +*/ + +/** + Public methods from the AaveTokenV3Harness.sol +*/ + +methods{ + totalSupply() returns (uint256) envfree + balanceOf(address addr) returns (uint256) envfree + transfer(address to, uint256 amount) returns (bool) + transferFrom(address from, address to, uint256 amount) returns (bool) + delegate(address delegatee) + metaDelegate(address, address, uint256, uint8, bytes32, bytes32) + getPowerCurrent(address user, uint8 delegationType) returns (uint256) envfree + getBalance(address user) returns (uint104) envfree + getDelegatedPropositionBalance(address user) returns (uint72) envfree + getDelegatedVotingBalance(address user) returns (uint72) envfree + getDelegatingProposition(address user) returns (bool) envfree + getDelegatingVoting(address user) returns (bool) envfree + getVotingDelegate(address user) returns (address) envfree + getPropositionDelegate(address user) returns (address) envfree +} + +/** + + Constants + +*/ + +definition VOTING_POWER() returns uint8 = 0; +definition PROPOSITION_POWER() returns uint8 = 1; +definition DELEGATED_POWER_DIVIDER() returns uint256 = 10^10; +definition MAX_DELEGATED_BALANCE() returns uint256 = 16000000 * 10^18 / DELEGATED_POWER_DIVIDER(); + +/** + + Function that normalizes (removes 10 least significant digits) a given param + +*/ + +function normalize(uint256 amount) returns uint256 { + return to_uint256(amount / DELEGATED_POWER_DIVIDER() * DELEGATED_POWER_DIVIDER()); +} + +/** + + Testing correctness of delegate(). An example of a unit test + +*/ + +rule delegateCorrectness(address bob) { + env e; + // delegate not to self or to zero + require bob != e.msg.sender && bob != 0; + + uint256 bobDelegatedBalance = getDelegatedVotingBalance(bob); + // avoid unrealistic delegated balance + require(bobDelegatedBalance < MAX_DELEGATED_BALANCE()); + + // verify that the sender doesn't already delegate to bob + address delegateBefore = getVotingDelegate(e.msg.sender); + require delegateBefore != bob; + + uint256 bobVotingPowerBefore = getPowerCurrent(bob, VOTING_POWER()); + uint256 delegatorBalance = balanceOf(e.msg.sender); + + delegate(e, bob); + + address delegateAfter = getVotingDelegate(e.msg.sender); + // test the delegate indeed has changed to bob + assert delegateAfter == bob; + + uint256 bobVotingPowerAfter = getPowerCurrent(bob, VOTING_POWER()); + + // test the delegate's new voting power + assert bobVotingPowerAfter == bobVotingPowerBefore + normalize(delegatorBalance); +} + + +rule votingDelegateChanges(address alice, method f) { + env e; + calldataarg args; + + address aliceDelegateBefore = getVotingDelegate(alice); + + f(e, args); + + address aliceDelegateAfter = getVotingDelegate(alice); + + // only these four function may change the delegate of an address + assert aliceDelegateAfter != aliceDelegateBefore => + f.selector == delegate(address).selector || + f.selector == delegateByType(address,uint8).selector || + f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector || + f.selector == metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32).selector; +} \ No newline at end of file From e5e039f657bef962526e4865cce5b18d8df83b61 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Mon, 25 Jul 2022 12:54:03 +0300 Subject: [PATCH 24/58] add erc20 spec --- certora/harness/AaveTokenV3Harness.sol | 16 +- certora/harness/AaveTokenV3Harness_old.sol | 433 --------------- certora/harness/BaseAaveTokenHarness.sol | 161 ++++++ certora/harness/BaseAaveTokenV2Harness.sol | 82 +++ certora/scripts/erc20.sh | 14 + certora/scripts/setup.sh | 5 +- certora/scripts/verifyGeneral.sh | 14 + certora/specs/base.spec | 42 ++ certora/specs/bgdSpec.spec | 111 +--- certora/specs/erc20.spec | 580 +++++++++++++++++++++ certora/specs/general.spec | 45 ++ certora/specs/setup.spec | 39 +- 12 files changed, 999 insertions(+), 543 deletions(-) delete mode 100644 certora/harness/AaveTokenV3Harness_old.sol create mode 100644 certora/harness/BaseAaveTokenHarness.sol create mode 100644 certora/harness/BaseAaveTokenV2Harness.sol create mode 100644 certora/scripts/erc20.sh create mode 100644 certora/scripts/verifyGeneral.sh create mode 100644 certora/specs/base.spec create mode 100644 certora/specs/erc20.spec create mode 100644 certora/specs/general.spec diff --git a/certora/harness/AaveTokenV3Harness.sol b/certora/harness/AaveTokenV3Harness.sol index 1e24b2e..68b5939 100644 --- a/certora/harness/AaveTokenV3Harness.sol +++ b/certora/harness/AaveTokenV3Harness.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import {VersionedInitializable} from '../../src/utils/VersionedInitializable.sol'; import {IGovernancePowerDelegationToken} from '../../src/interfaces/IGovernancePowerDelegationToken.sol'; -import {BaseAaveTokenV2} from '../../src/BaseAaveTokenV2.sol'; +import {BaseAaveTokenV2} from './BaseAaveTokenV2Harness.sol'; contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { mapping(address => address) internal _votingDelegateeV2; @@ -173,16 +173,6 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { uint72 impactOnDelegationBefore72 = uint72(impactOnDelegationBefore / POWER_SCALE_FACTOR); uint72 impactOnDelegationAfter72 = uint72(impactOnDelegationAfter / POWER_SCALE_FACTOR); - bool testCondition = (delegationType == GovernancePowerType.VOTING - && - _balances[delegatee].delegatedVotingBalance < impactOnDelegationBefore72) - || ( - delegationType == GovernancePowerType.PROPOSITION - && - _balances[delegatee].delegatedPropositionBalance < impactOnDelegationBefore72 - ); - require(!testCondition); - if (delegationType == GovernancePowerType.VOTING) { _balances[delegatee].delegatedVotingBalance = _balances[delegatee].delegatedVotingBalance - @@ -441,6 +431,10 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { return _propositionDelegateeV2[user]; } + function getDelegationState(address user) view public returns (DelegationState) { + return _balances[user].delegationState; + } + /** diff --git a/certora/harness/AaveTokenV3Harness_old.sol b/certora/harness/AaveTokenV3Harness_old.sol deleted file mode 100644 index 0c621e8..0000000 --- a/certora/harness/AaveTokenV3Harness_old.sol +++ /dev/null @@ -1,433 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import {VersionedInitializable} from '../../src/utils/VersionedInitializable.sol'; - -import {IGovernancePowerDelegationToken} from '../../src/interfaces/IGovernancePowerDelegationToken.sol'; -import {BaseAaveTokenV2} from './BaseAaveTokenV2Harness.sol'; - -contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { - - - mapping(address => address) internal _votingDelegateeV2; - mapping(address => address) internal _propositionDelegateeV2; - - uint256 public constant DELEGATED_POWER_DIVIDER = 10**10; - - bytes32 public constant DELEGATE_BY_TYPE_TYPEHASH = - keccak256( - 'DelegateByType(address delegator,address delegatee,GovernancePowerType delegationType,uint256 nonce,uint256 deadline)' - ); - bytes32 public constant DELEGATE_TYPEHASH = - keccak256('Delegate(address delegator,address delegatee,uint256 nonce,uint256 deadline)'); - - /** - Harness section - replace struct reads and writes with function calls - */ - -// struct DelegationAwareBalance { -// uint104 balance; -// uint72 delegatedPropositionBalance; -// uint72 delegatedVotingBalance; -// bool delegatingProposition; -// bool delegatingVoting; -// } - - function _setBalance(address user, uint104 balance) internal { - _balances[user].balance = balance; - } - - function getBalance(address user) view public returns (uint104) { - return _balances[user].balance; - } - - function _setDelegatedPropositionBalance(address user, uint72 dpb) internal { - _balances[user].delegatedPropositionBalance = dpb; - } - - function getDelegatedPropositionBalance(address user) view public returns (uint72) { - return _balances[user].delegatedPropositionBalance; - } - - function _setDelegatedVotingBalance(address user, uint72 dvb) internal { - _balances[user].delegatedVotingBalance = dvb; - } - - function getDelegatedVotingBalance(address user) view public returns (uint72) { - return _balances[user].delegatedVotingBalance; - } - - function _setDelegatingProposition(address user, bool _delegating) internal { - _balances[user].delegatingProposition = _delegating; - } - - function getDelegatingProposition(address user) view public returns (bool) { - return _balances[user].delegatingProposition; - } - - function _setDelegatingVoting(address user, bool _delegating) internal { - _balances[user].delegatingVoting = _delegating; - } - - function getDelegatingVoting(address user) view public returns (bool) { - return _balances[user].delegatingVoting; - } - - function getVotingDelegate(address user) view public returns (address) { - return _votingDelegateeV2[user]; - } - - function getPropositionDelegate(address user) view public returns (address) { - return _propositionDelegateeV2[user]; - } - - - - /** - End of harness section - */ - - /** - * @dev changing one of delegated governance powers of delegatee depending on the delegator balance change - * @param userBalanceBefore delegator balance before operation - * @param userBalanceAfter delegator balance after operation - * @param delegatee the user whom delegated governance power will be changed - * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) - * @param operation math operation which will be applied depends on increasing or decreasing of the delegator balance (plus, minus) - **/ - function _delegationMoveByType( - uint104 userBalanceBefore, - uint104 userBalanceAfter, - address delegatee, - GovernancePowerType delegationType, - function(uint72, uint72) returns (uint72) operation - ) internal { - if (delegatee == address(0)) return; - - // FIXING A PRECISION ISSUE HERE - - // @dev to make delegated balance fit into uin72 we're decreasing precision of delegated balance by DELEGATED_POWER_DIVIDER - // uint72 delegationDelta = uint72( - // (userBalanceBefore / DELEGATED_POWER_DIVIDER) - (userBalanceAfter / DELEGATED_POWER_DIVIDER) - // ); - uint72 delegationDelta = uint72((userBalanceBefore - userBalanceAfter) / DELEGATED_POWER_DIVIDER); - if (delegationDelta == 0) return; - - if (delegationType == GovernancePowerType.VOTING) { - _balances[delegatee].delegatedVotingBalance = operation( - _balances[delegatee].delegatedVotingBalance, - delegationDelta - ); - //TODO: emit DelegatedPowerChanged maybe; - } else { - _balances[delegatee].delegatedPropositionBalance = operation( - _balances[delegatee].delegatedPropositionBalance, - delegationDelta - ); - //TODO: emit DelegatedPowerChanged maybe; - } - } - - /** - * @dev changing one of governance power(Voting and Proposition) of delegatees depending on the delegator balance change - * @param user delegator - * @param userState the current state of the delegator - * @param balanceBefore delegator balance before operation - * @param balanceAfter delegator balance after operation - * @param operation math operation which will be applied depends on increasing or decreasing of the delegator balance (plus, minus) - **/ - function _delegationMove( - address user, - DelegationAwareBalance memory userState, - uint104 balanceBefore, - uint104 balanceAfter, - function(uint72, uint72) returns (uint72) operation - ) internal { - _delegationMoveByType( - balanceBefore, - balanceAfter, - _getDelegateeByType(user, userState, GovernancePowerType.VOTING), - GovernancePowerType.VOTING, - operation - ); - _delegationMoveByType( - balanceBefore, - balanceAfter, - _getDelegateeByType(user, userState, GovernancePowerType.PROPOSITION), - GovernancePowerType.PROPOSITION, - operation - ); - } - - /** - * @dev performs all state changes related to balance transfer and corresponding delegation changes - * @param from token sender - * @param to token recipient - * @param amount amount of tokens sent - **/ - function _transferWithDelegation( - address from, - address to, - uint256 amount - ) internal override { - if (from == to) { - return; - } - - if (from != address(0)) { - DelegationAwareBalance memory fromUserState = _balances[from]; - require(fromUserState.balance >= amount, 'ERC20: transfer amount exceeds balance'); - - uint104 fromBalanceAfter; - unchecked { - //TODO: in general we don't need to check cast to uint104 because we know that it's less then balance from require - fromBalanceAfter = fromUserState.balance - uint104(amount); - } - _balances[from].balance = fromBalanceAfter; - if (fromUserState.delegatingProposition || fromUserState.delegatingVoting) - _delegationMove( - from, - fromUserState, - fromUserState.balance, - fromBalanceAfter, - MathUtils.minus - ); - } - - if (to != address(0)) { - DelegationAwareBalance memory toUserState = _balances[to]; - uint104 toBalanceBefore = toUserState.balance; - toUserState.balance = toBalanceBefore + uint104(amount); // TODO: check overflow? - _balances[to] = toUserState; - - if (toUserState.delegatingVoting || toUserState.delegatingProposition) { - _delegationMove(to, toUserState, toUserState.balance, toBalanceBefore, MathUtils.plus); - } - } - } - - /** - * @dev extracting and returning delegated governance power(Voting or Proposition) from user state - * @param userState the current state of a user - * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) - **/ - function _getDelegatedPowerByType( - DelegationAwareBalance memory userState, - GovernancePowerType delegationType - ) internal pure returns (uint72) { - return - delegationType == GovernancePowerType.VOTING - ? userState.delegatedVotingBalance - : userState.delegatedPropositionBalance; - } - - /** - * @dev extracts from user state and returning delegatee by type of governance power(Voting or Proposition) - * @param user delegator - * @param userState the current state of a user - * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) - **/ - function _getDelegateeByType( - address user, - DelegationAwareBalance memory userState, - GovernancePowerType delegationType - ) internal view returns (address) { - if (delegationType == GovernancePowerType.VOTING) { - return userState.delegatingVoting ? _votingDelegateeV2[user] : address(0); - } - return userState.delegatingProposition ? _propositionDelegateeV2[user] : address(0); - } - - /** - * @dev changing user's delegatee address by type of governance power(Voting or Proposition) - * @param user delegator - * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) - * @param _newDelegatee the new delegatee - **/ - function _updateDelegateeByType( - address user, - GovernancePowerType delegationType, - address _newDelegatee - ) internal { - address newDelegatee = _newDelegatee == user ? address(0) : _newDelegatee; - if (delegationType == GovernancePowerType.VOTING) { - _votingDelegateeV2[user] = newDelegatee; - } else { - _propositionDelegateeV2[user] = newDelegatee; - } - } - - /** - * @dev updates the specific flag which signaling about existence of delegation of governance power(Voting or Proposition) - * @param userState a user state to change - * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) - * @param willDelegate next state of delegation - **/ - function _updateDelegationFlagByType( - DelegationAwareBalance memory userState, - GovernancePowerType delegationType, - bool willDelegate - ) internal pure returns (DelegationAwareBalance memory) { - if (delegationType == GovernancePowerType.VOTING) { - userState.delegatingVoting = willDelegate; - } else { - userState.delegatingProposition = willDelegate; - } - return userState; - } - - /** - * @dev delegates the specific power to a delegatee - * @param user delegator - * @param _delegatee the user which delegated power has changed - * @param delegationType the type of delegation (VOTING, PROPOSITION) - **/ - function _delegateByType( - address user, - address _delegatee, - GovernancePowerType delegationType - ) internal { - //we consider to 0x0 as delegation to self - address delegatee = _delegatee == user ? address(0) : _delegatee; - - DelegationAwareBalance memory userState = _balances[user]; - address currentDelegatee = _getDelegateeByType(user, userState, delegationType); - if (delegatee == currentDelegatee) return; - - bool delegatingNow = currentDelegatee != address(0); - bool willDelegateAfter = delegatee != address(0); - - if (delegatingNow) { - _delegationMoveByType( - userState.balance, - 0, - currentDelegatee, - delegationType, - MathUtils.minus - ); - } - if (willDelegateAfter) { - _updateDelegateeByType(user, delegationType, delegatee); - _delegationMoveByType(userState.balance, 0, delegatee, delegationType, MathUtils.plus); - } - - if (willDelegateAfter != delegatingNow) { - _balances[user] = _updateDelegationFlagByType(userState, delegationType, willDelegateAfter); - } - - emit DelegateChanged(user, delegatee, delegationType); - } - - /// @inheritdoc IGovernancePowerDelegationToken - function delegateByType(address delegatee, GovernancePowerType delegationType) - external - virtual - override - { - _delegateByType(msg.sender, delegatee, delegationType); - } - - /// @inheritdoc IGovernancePowerDelegationToken - function delegate(address delegatee) external override { - _delegateByType(msg.sender, delegatee, GovernancePowerType.VOTING); - _delegateByType(msg.sender, delegatee, GovernancePowerType.PROPOSITION); - } - - /// @inheritdoc IGovernancePowerDelegationToken - function getDelegateeByType(address delegator, GovernancePowerType delegationType) - external - view - override - returns (address) - { - return _getDelegateeByType(delegator, _balances[delegator], delegationType); - } - - /// @inheritdoc IGovernancePowerDelegationToken - function getPowerCurrent(address user, GovernancePowerType delegationType) - external - view - override - returns (uint256) - { - DelegationAwareBalance memory userState = _balances[user]; - uint256 userOwnPower = (delegationType == GovernancePowerType.VOTING && - !userState.delegatingVoting) || - (delegationType == GovernancePowerType.PROPOSITION && !userState.delegatingProposition) - ? _balances[user].balance - : 0; - uint256 userDelegatedPower = _getDelegatedPowerByType(userState, delegationType) * - DELEGATED_POWER_DIVIDER; - return userOwnPower + userDelegatedPower; - } - - /// @inheritdoc IGovernancePowerDelegationToken - function metaDelegateByType( - address delegator, - address delegatee, - GovernancePowerType delegationType, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external override { - require(delegator != address(0), 'INVALID_OWNER'); - //solium-disable-next-line - require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); - uint256 currentValidNonce = _nonces[delegator]; - bytes32 digest = keccak256( - abi.encodePacked( - '\x19\x01', - DOMAIN_SEPARATOR, - keccak256( - abi.encode( - DELEGATE_BY_TYPE_TYPEHASH, - delegator, - delegatee, - delegationType, - currentValidNonce, - deadline - ) - ) - ) - ); - - require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); - unchecked { - // does not make sense to check because it's not realistic to reach uint256.max in nonce - _nonces[delegator] = currentValidNonce + 1; - } - _delegateByType(delegator, delegatee, delegationType); - } - - /// @inheritdoc IGovernancePowerDelegationToken - function metaDelegate( - address delegator, - address delegatee, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external override { - require(delegator != address(0), 'INVALID_OWNER'); - //solium-disable-next-line - require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); - uint256 currentValidNonce = _nonces[delegator]; - bytes32 digest = keccak256( - abi.encodePacked( - '\x19\x01', - DOMAIN_SEPARATOR, - keccak256(abi.encode(DELEGATE_TYPEHASH, delegator, delegatee, currentValidNonce, deadline)) - ) - ); - - require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); - unchecked { - // does not make sense to check because it's not realistic to reach uint256.max in nonce - _nonces[delegator] = currentValidNonce + 1; - } - _delegateByType(delegator, delegatee, GovernancePowerType.VOTING); - _delegateByType(delegator, delegatee, GovernancePowerType.PROPOSITION); - } -} diff --git a/certora/harness/BaseAaveTokenHarness.sol b/certora/harness/BaseAaveTokenHarness.sol new file mode 100644 index 0000000..bcc17fa --- /dev/null +++ b/certora/harness/BaseAaveTokenHarness.sol @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Context} from '../../lib/openzeppelin-contracts/contracts/utils/Context.sol'; +import {IERC20} from '../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; +import {IERC20Metadata} from '../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; + +// Inspired by OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) +abstract contract BaseAaveToken is Context, IERC20Metadata { + enum DelegationState { + NO_DELEGATION, + VOTING_DELEGATED, + PROPOSITION_DELEGATED, + FULL_POWER_DELEGATED + } + + struct DelegationAwareBalance { + uint104 balance; + uint72 delegatedPropositionBalance; + uint72 delegatedVotingBalance; + DelegationState delegationState; + } + + mapping(address => DelegationAwareBalance) public _balances; + + mapping(address => mapping(address => uint256)) public _allowances; + + uint256 internal _totalSupply; + + string internal _name; + string internal _symbol; + + // @dev DEPRECATED + // kept for backwards compatibility with old storage layout + uint8 private ______DEPRECATED_OLD_ERC20_DECIMALS; + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + function decimals() public view virtual override returns (uint8) { + return 18; + } + + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account].balance; + } + + function transfer(address to, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, amount); + return true; + } + + function allowance(address owner, address spender) + public + view + virtual + override + returns (uint256) + { + return _allowances[owner][spender]; + } + + function approve(address spender, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, amount); + return true; + } + + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual override returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, _allowances[owner][spender] + addedValue); + return true; + } + + function decreaseAllowance(address spender, uint256 subtractedValue) + public + virtual + returns (bool) + { + address owner = _msgSender(); + uint256 currentAllowance = _allowances[owner][spender]; + require(currentAllowance >= subtractedValue, 'ERC20: decreased allowance below zero'); + unchecked { + _approve(owner, spender, currentAllowance - subtractedValue); + } + + return true; + } + + function _transfer( + address from, + address to, + uint256 amount + ) internal virtual { + require(from != address(0), 'ERC20: transfer from the zero address'); + require(to != address(0), 'ERC20: transfer to the zero address'); + + _transferWithDelegation(from, to, amount); + emit Transfer(from, to, amount); + } + + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual { + require(owner != address(0), 'ERC20: approve from the zero address'); + require(spender != address(0), 'ERC20: approve to the zero address'); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + function _spendAllowance( + address owner, + address spender, + uint256 amount + ) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require(currentAllowance >= amount, 'ERC20: insufficient allowance'); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } + + function _transferWithDelegation( + address from, + address to, + uint256 amount + ) internal virtual {} +} diff --git a/certora/harness/BaseAaveTokenV2Harness.sol b/certora/harness/BaseAaveTokenV2Harness.sol new file mode 100644 index 0000000..7904063 --- /dev/null +++ b/certora/harness/BaseAaveTokenV2Harness.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {VersionedInitializable} from '../../src/utils/VersionedInitializable.sol'; +import {BaseAaveToken} from './BaseAaveTokenHarness.sol'; + +abstract contract BaseAaveTokenV2 is BaseAaveToken, VersionedInitializable { + /// @dev owner => next valid nonce to submit with permit() + mapping(address => uint256) public _nonces; + + ///////// @dev DEPRECATED from AaveToken v1 ////////////////////////// + //////// kept for backwards compatibility with old storage layout //// + uint256[3] private ______DEPRECATED_FROM_AAVE_V1; + ///////// @dev END OF DEPRECATED from AaveToken v1 ////////////////////////// + + bytes32 public DOMAIN_SEPARATOR; + + ///////// @dev DEPRECATED from AaveToken v2 ////////////////////////// + //////// kept for backwards compatibility with old storage layout //// + uint256[4] private ______DEPRECATED_FROM_AAVE_V2; + ///////// @dev END OF DEPRECATED from AaveToken v2 ////////////////////////// + + bytes public constant EIP712_REVISION = bytes('1'); + bytes32 internal constant EIP712_DOMAIN = + keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'); + bytes32 public constant PERMIT_TYPEHASH = + keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'); + + uint256 public constant REVISION = 3; + + /** + * @dev initializes the contract upon assignment to the InitializableAdminUpgradeabilityProxy + */ + function initialize() external initializer {} + + /** + * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md + * @param owner the owner of the funds + * @param spender the spender + * @param value the amount + * @param deadline the deadline timestamp, type(uint256).max for no deadline + * @param v signature param + * @param s signature param + * @param r signature param + */ + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external { + require(owner != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _nonces[owner]; + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline)) + ) + ); + + require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + unchecked { + // does not make sense to check because it's not realistic to reach uint256.max in nonce + _nonces[owner] = currentValidNonce + 1; + } + _approve(owner, spender, value); + } + + /** + * @dev returns the revision of the implementation contract + */ + function getRevision() internal pure override returns (uint256) { + return REVISION; + } +} diff --git a/certora/scripts/erc20.sh b/certora/scripts/erc20.sh new file mode 100644 index 0000000..ceeef50 --- /dev/null +++ b/certora/scripts/erc20.sh @@ -0,0 +1,14 @@ +if [[ "$1" ]] +then + RULE="--rule $1" +fi + +certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ + --verify AaveTokenV3:certora/specs/erc20.spec \ + $RULE \ + --solc solc8.13 \ + --optimistic_loop \ + --send_only \ + --staging \ + --msg "AaveTokenV3:erc20.spec $1" + \ No newline at end of file diff --git a/certora/scripts/setup.sh b/certora/scripts/setup.sh index 64d17ef..6e7f0a5 100644 --- a/certora/scripts/setup.sh +++ b/certora/scripts/setup.sh @@ -5,10 +5,9 @@ fi certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ --verify AaveTokenV3:certora/specs/setup.spec \ - --rule $1 \ + $RULE \ --solc solc8.13 \ --optimistic_loop \ --send_only \ -# --staging \ + --staging \ --msg "AaveTokenV3:setup.spec $1" - \ No newline at end of file diff --git a/certora/scripts/verifyGeneral.sh b/certora/scripts/verifyGeneral.sh new file mode 100644 index 0000000..fdb4ba1 --- /dev/null +++ b/certora/scripts/verifyGeneral.sh @@ -0,0 +1,14 @@ +if [[ "$1" ]] +then + RULE="--rule $1" +fi + +certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ + --verify AaveTokenV3:certora/specs/general.spec \ + $RULE \ + --solc solc8.13 \ + --optimistic_loop \ + --send_only \ + --staging \ + --msg "AaveTokenV3:general.spec $1" + \ No newline at end of file diff --git a/certora/specs/base.spec b/certora/specs/base.spec new file mode 100644 index 0000000..08fb846 --- /dev/null +++ b/certora/specs/base.spec @@ -0,0 +1,42 @@ +methods { + totalSupply() returns (uint256) envfree + balanceOf(address) returns (uint256) envfree + allowance(address,address) returns (uint256) envfree + increaseAllowance(address, uint256) + decreaseAllowance(address, uint256) + transfer(address,uint256) + transferFrom(address,address,uint256) + permit(address,address,uint256,uint256,uint8,bytes32,bytes32) + + delegate(address delegatee) + metaDelegate(address, address, uint256, uint8, bytes32, bytes32) + getPowerCurrent(address user, uint8 delegationType) returns (uint256) envfree + + getBalance(address user) returns (uint104) envfree + getDelegatedPropositionBalance(address user) returns (uint72) envfree + getDelegatedVotingBalance(address user) returns (uint72) envfree + getDelegatingProposition(address user) returns (bool) envfree + getDelegatingVoting(address user) returns (bool) envfree + getVotingDelegate(address user) returns (address) envfree + getPropositionDelegate(address user) returns (address) envfree + getDelegationState(address user) returns (uint8) envfree +} + +definition VOTING_POWER() returns uint8 = 0; +definition PROPOSITION_POWER() returns uint8 = 1; +definition DELEGATED_POWER_DIVIDER() returns uint256 = 10^10; + +function normalize(uint256 amount) returns uint256 { + return to_uint256(amount / DELEGATED_POWER_DIVIDER() * DELEGATED_POWER_DIVIDER()); +} + +function validDelegationState(address user) returns bool { + return getDelegationState(user) < 4; +} + +function validAmount(uint256 amt) returns bool { + return amt < AAVE_MAX_SUPPLY(); +} + +definition AAVE_MAX_SUPPLY() returns uint256 = 16000000 * 10^18; +definition SCALED_MAX_SUPPLY() returns uint256 = AAVE_MAX_SUPPLY() / DELEGATED_POWER_DIVIDER(); \ No newline at end of file diff --git a/certora/specs/bgdSpec.spec b/certora/specs/bgdSpec.spec index aa67110..e466e08 100644 --- a/certora/specs/bgdSpec.spec +++ b/certora/specs/bgdSpec.spec @@ -1,87 +1,5 @@ -// using DelegationAwareBalance from "./BaseAaveToken.sol"; - -// issues: -// for enum, just use 0 (voting) and 1 (proposition) or local definition -// for struct use harness that replaces reads and writes with solidity functions - -methods{ - totalSupply() returns (uint256) envfree - balanceOf(address addr) returns (uint256) envfree - transfer(address to, uint256 amount) returns (bool) - transferFrom(address from, address to, uint256 amount) returns (bool) - - delegate(address delegatee) - metaDelegate(address, address, uint256, uint8, bytes32, bytes32) - getPowerCurrent(address user, uint8 delegationType) returns (uint256) envfree - - getBalance(address user) returns (uint104) envfree - getDelegatedPropositionBalance(address user) returns (uint72) envfree - getDelegatedVotingBalance(address user) returns (uint72) envfree - getDelegatingProposition(address user) returns (bool) envfree - getDelegatingVoting(address user) returns (bool) envfree - getVotingDelegate(address user) returns (address) envfree - getPropositionDelegate(address user) returns (address) envfree - - _governancePowerTransferByType(uint104, uint104, address, uint8) -} - -definition VOTING_POWER() returns uint8 = 0; -definition PROPOSITION_POWER() returns uint8 = 1; -definition DELEGATED_POWER_DIVIDER() returns uint256 = 10^10; - -function normalize(uint256 amount) returns uint256 { - return to_uint256(amount / DELEGATED_POWER_DIVIDER() * DELEGATED_POWER_DIVIDER()); -} +import "base.spec" -// for test - it shouldnt pass -// invariant ZeroAddressNoDelegation() -// getPowerCurrent(0, 0) == 0 && getPowerCurrent(0, 1) == 0 - -// The total power (of one type) of all users in the system is less or equal than -// the sum of balances of all AAVE holders (totalSupply of AAVE token) - -// accumulator for a sum of proposition voting power -ghost mathint sumDelegatedProposition { - init_state axiom sumDelegatedProposition == 0; -} - -ghost mathint sumBalances { - init_state axiom sumBalances == 0; -} - -/* - update proposition balance on each store - */ -hook Sstore _balances[KEY address user].delegatedPropositionBalance uint72 balance - (uint72 old_balance) STORAGE { - sumDelegatedProposition = sumDelegatedProposition + to_mathint(balance) - to_mathint(old_balance); - } - -// try to rewrite using power.spec in aave-tokenv2 customer code -hook Sstore _balances[KEY address user].balance uint104 balance - (uint104 old_balance) STORAGE { - sumBalances = sumBalances + to_mathint(balance) - to_mathint(old_balance); - } - -invariant sumDelegatedPropositionCorrectness() sumDelegatedProposition <= sumBalances { - // fails - preserved transfer(address to, uint256 amount) with (env e) - { - require(balanceOf(e.msg.sender) + balanceOf(to)) < totalSupply(); - } - preserved transferFrom(address from, address to, uint256 amount) with (env e) - { - require(balanceOf(from) + balanceOf(to)) < totalSupply(); - } -} - -invariant nonDelegatingBalance(address user) - !getDelegatingProposition(user) => balanceOf(user) == getDelegatedPropositionBalance(user) { - preserved transfer(address to, uint256 amount) with (env e) - { - require(getVotingDelegate(to) != user); - } - } rule totalSupplyCorrectness(method f) { @@ -360,12 +278,6 @@ rule vpTransferWhenOnlyOneIsDelegating(address alice, address bob, address charl assert charliePowerBefore == charliePowerAfter; } -/** -before: 133160000000000 -amount: 30900000000001 -after: 102250000000000 - -*/ rule ppTransferWhenOnlyOneIsDelegating(address alice, address bob, address charlie, uint256 amount) { env e; @@ -596,4 +508,23 @@ delta: 0x1001d1bf7ff = 1099999999999 53690000000000 - normalize(10000007371) + normalize(39999999999) = 53690000000000 - 10000000000 + 30000000000 = 53710000000000 -*/ \ No newline at end of file +*/ + + +rule votingDelegateChanges(address alice, method f) { + env e; + calldataarg args; + + address aliceDelegateBefore = getVotingDelegate(alice); + + f(e, args); + + address aliceDelegateAfter = getVotingDelegate(alice); + + // only these four function may change the delegate of an address + assert aliceDelegateAfter != aliceDelegateBefore => + f.selector == delegate(address).selector || + f.selector == delegateByType(address,uint8).selector || + f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector || + f.selector == metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32).selector; +} \ No newline at end of file diff --git a/certora/specs/erc20.spec b/certora/specs/erc20.spec new file mode 100644 index 0000000..eeefd2f --- /dev/null +++ b/certora/specs/erc20.spec @@ -0,0 +1,580 @@ +import "base.spec" + +function doesntChangeBalance(method f) returns bool { + return f.selector != transfer(address,uint256).selector && + f.selector != transferFrom(address,address,uint256).selector; +} + +/* + @Rule + + + @Description: + Verify that there is no fee on transferFrom() (like potentially on USDT) + + @Formula: + { + balances[bob] = y + allowance(alice, msg.sender) >= amount + } + < + transferFrom(alice, bob, amount) + > + { + balances[bob] = y + amount + } + + @Notes: + + + @Link: + +*/ +rule noFeeOnTransferFrom(address alice, address bob, uint256 amount) { + env e; + calldataarg args; + require alice != bob; + require allowance(alice, e.msg.sender) >= amount; + uint256 balanceBefore = balanceOf(bob); + + transferFrom(e, alice, bob, amount); + + uint256 balanceAfter = balanceOf(bob); + assert balanceAfter == balanceBefore + amount; +} + +/* + @Rule + + @Description: + Verify that there is no fee on transfer() (like potentially on USDT) + + @Formula: + { + balances[bob] = y + balances[msg.sender] >= amount + } + < + transfer(bob, amount) + > + { + balances[bob] = y + amount + } + + @Notes: + + @Link: + + +*/ +rule noFeeOnTransfer(address bob, uint256 amount) { + env e; + calldataarg args; + require bob != e.msg.sender; + uint256 balanceSenderBefore = balanceOf(e.msg.sender); + uint256 balanceBefore = balanceOf(bob); + + transfer(e, bob, amount); + + uint256 balanceAfter = balanceOf(bob); + uint256 balanceSenderAfter = balanceOf(e.msg.sender); + assert balanceAfter == balanceBefore + amount; +} + +/* + @Rule + + + @Description: + Token transfer works correctly. Balances are updated if not reverted. + If reverted then the transfer amount was too high, or the recipient is 0. + + @Formula: + { + balanceFromBefore = balanceOf(msg.sender) + balanceToBefore = balanceOf(to) + } + < + transfer(to, amount) + > + { + lastReverted => to = 0 || amount > balanceOf(msg.sender) + !lastReverted => balanceOf(to) = balanceToBefore + amount && + balanceOf(msg.sender) = balanceFromBefore - amount + } + + @Notes: + This rule fails on tokens with a blacklist function, like USDC and USDT. + The prover finds a counterexample of a reverted transfer to a blacklisted address or a transfer in a paused state. + + @Link: + +*/ +rule transferCorrect(address to, uint256 amount) { + env e; + require e.msg.value == 0 && e.msg.sender != 0; + uint256 fromBalanceBefore = balanceOf(e.msg.sender); + uint256 toBalanceBefore = balanceOf(to); + require fromBalanceBefore + toBalanceBefore < AAVE_MAX_SUPPLY(); + + address v_delegateTo = getVotingDelegate(to); + require getDelegatedVotingBalance(v_delegateTo) < SCALED_MAX_SUPPLY() - amount / DELEGATED_POWER_DIVIDER(); + address p_delegateTo = getPropositionDelegate(to); + require getDelegatedPropositionBalance(p_delegateTo) < SCALED_MAX_SUPPLY() - amount / DELEGATED_POWER_DIVIDER(); + + require validDelegationState(e.msg.sender) && validDelegationState(to); + + transfer@withrevert(e, to, amount); + bool reverted = lastReverted; + if (!reverted) { + if (e.msg.sender == to) { + assert balanceOf(e.msg.sender) == fromBalanceBefore; + } else { + assert balanceOf(e.msg.sender) == fromBalanceBefore - amount; + assert balanceOf(to) == toBalanceBefore + amount; + } + } else { + assert amount > fromBalanceBefore || to == 0; + } +} + +/* + @Rule + + + @Description: + Test that transferFrom works correctly. Balances are updated if not reverted. + If reverted, it means the transfer amount was too high, or the recipient is 0 + + @Formula: + { + balanceFromBefore = balanceOf(from) + balanceToBefore = balanceOf(to) + } + < + transferFrom(from, to, amount) + >tr + { + lastreverted => to = 0 || amount > balanceOf(from) + !lastreverted => balanceOf(to) = balanceToBefore + amount && + balanceOf(from) = balanceFromBefore - amount + } + + @Notes: + This rule fails on tokens with a blacklist and or pause function, like USDC and USDT. + The prover finds a counterexample of a reverted transfer to a blacklisted address or a transfer in a paused state. + + @Link: + +*/ + +rule transferFromCorrect(address from, address to, uint256 amount) { + env e; + require e.msg.value == 0; + uint256 fromBalanceBefore = balanceOf(from); + uint256 toBalanceBefore = balanceOf(to); + uint256 allowanceBefore = allowance(from, e.msg.sender); + require fromBalanceBefore + toBalanceBefore < AAVE_MAX_SUPPLY(); + + transferFrom(e, from, to, amount); + + assert from != to => + balanceOf(from) == fromBalanceBefore - amount && + balanceOf(to) == toBalanceBefore + amount && + (allowance(from, e.msg.sender) == allowanceBefore - amount || + allowance(from, e.msg.sender) == max_uint256); +} + +/* + @Rule + + @Description: + transferFrom should revert if and only if the amount is too high or the recipient is 0. + + @Formula: + { + allowanceBefore = allowance(alice, bob) + fromBalanceBefore = balanceOf(alice) + } + < + transferFrom(alice, bob, amount) + > + { + lastReverted <=> allowanceBefore < amount || amount > fromBalanceBefore || to = 0 + } + + @Notes: + Fails on tokens with pause/blacklist functions, like USDC. + + @Link: + +*/ +// TODO: fix rule failure +rule transferFromReverts(address from, address to, uint256 amount) { + env e; + uint256 allowanceBefore = allowance(from, e.msg.sender); + uint256 fromBalanceBefore = balanceOf(from); + require from != 0 && e.msg.sender != 0 && from != to; + require e.msg.value == 0; + require fromBalanceBefore + balanceOf(to) < AAVE_MAX_SUPPLY(); + require validDelegationState(from) && validDelegationState(to); + + uint256 votingFromDelegateBalance = getDelegatedVotingBalance(getVotingDelegate(from)); + uint256 propFromDelegateBalance = getDelegatedVotingBalance(getPropositionDelegate(from)); + + require votingFromDelegateBalance >= amount / DELEGATED_POWER_DIVIDER(); + require propFromDelegateBalance >= amount / DELEGATED_POWER_DIVIDER(); + + uint256 votingToDelegateBalance = getDelegatedVotingBalance(getVotingDelegate(to)); + uint256 propToDelegateBalance = getDelegatedVotingBalance(getPropositionDelegate(to)); + + require votingToDelegateBalance < SCALED_MAX_SUPPLY() - amount / DELEGATED_POWER_DIVIDER(); + address p_delegateTo = getPropositionDelegate(to); + require propToDelegateBalance < SCALED_MAX_SUPPLY() - amount / DELEGATED_POWER_DIVIDER(); + require validAmount(amount); + + transferFrom@withrevert(e, from, to, amount); + + assert lastReverted <=> (allowanceBefore < amount || amount > fromBalanceBefore || to == 0); +} + + +/* + @Rule + + @Description: + Balance of address 0 is always 0 + + @Formula: + { balanceOf[0] = 0 } + + @Notes: + + + @Link: + +*/ +invariant ZeroAddressNoBalance() + balanceOf(0) == 0 + + +/* + @Rule + + @Description: + Contract calls don't change token total supply. + + @Formula: + { + supplyBefore = totalSupply() + } + < f(e, args)> + { + supplyAfter = totalSupply() + supplyBefore == supplyAfter + } + + @Notes: + This rule should fail for any token that has functions that change totalSupply(), like mint() and burn(). + It's still important to run the rule and see if it fails in functions that _aren't_ supposed to modify totalSupply() + + @Link: + +*/ +rule NoChangeTotalSupply(method f) { + // require f.selector != burn(uint256).selector && f.selector != mint(address, uint256).selector; + uint256 totalSupplyBefore = totalSupply(); + env e; + calldataarg args; + f(e, args); + assert totalSupply() == totalSupplyBefore; +} + +/* + The two rules cover the same ground as NoChangeTotalSupply. + + The split into two rules is in order to make the burn/mint features of a tested token even more obvious +*/ +rule noBurningTokens(method f) { + uint256 totalSupplyBefore = totalSupply(); + env e; + calldataarg args; + f(e, args); + assert totalSupply() >= totalSupplyBefore; +} + +rule noMintingTokens(method f) { + uint256 totalSupplyBefore = totalSupply(); + env e; + calldataarg args; + f(e, args); + assert totalSupply() <= totalSupplyBefore; +} + +/* + @Rule + + @Description: + Allowance changes correctly as a result of calls to approve, transfer, increaseAllowance, decreaseAllowance + + @Formula: + { + allowanceBefore = allowance(from, spender) + } + < + f(e, args) + > + { + f.selector = approve(spender, amount) => allowance(from, spender) = amount + f.selector = transferFrom(from, spender, amount) => allowance(from, spender) = allowanceBefore - amount + f.selector = decreaseAllowance(spender, delta) => allowance(from, spender) = allowanceBefore - delta + f.selector = increaseAllowance(spender, delta) => allowance(from, spender) = allowanceBefore + delta + generic f.selector => allowance(from, spender) == allowanceBefore + } + + @Notes: + Some ERC20 tokens have functions like permit() that change allowance via a signature. + The rule will fail on such functions. + + @Link: + +*/ +rule ChangingAllowance(method f, address from, address spender) { + uint256 allowanceBefore = allowance(from, spender); + env e; + if (f.selector == approve(address, uint256).selector) { + address spender_; + uint256 amount; + approve(e, spender_, amount); + if (from == e.msg.sender && spender == spender_) { + assert allowance(from, spender) == amount; + } else { + assert allowance(from, spender) == allowanceBefore; + } + } else if (f.selector == transferFrom(address,address,uint256).selector) { + address from_; + address to; + address amount; + transferFrom(e, from_, to, amount); + uint256 allowanceAfter = allowance(from, spender); + if (from == from_ && spender == e.msg.sender) { + assert from == to || allowanceBefore == max_uint256 || allowanceAfter == allowanceBefore - amount; + } else { + assert allowance(from, spender) == allowanceBefore; + } + } else if (f.selector == decreaseAllowance(address, uint256).selector) { + address spender_; + uint256 amount; + require amount <= allowanceBefore; + decreaseAllowance(e, spender_, amount); + if (from == e.msg.sender && spender == spender_) { + assert allowance(from, spender) == allowanceBefore - amount; + } else { + assert allowance(from, spender) == allowanceBefore; + } + } else if (f.selector == increaseAllowance(address, uint256).selector) { + address spender_; + uint256 amount; + require amount + allowanceBefore < max_uint256; + increaseAllowance(e, spender_, amount); + if (from == e.msg.sender && spender == spender_) { + assert allowance(from, spender) == allowanceBefore + amount; + } else { + assert allowance(from, spender) == allowanceBefore; + } + } + else + { + calldataarg args; + f(e, args); + assert allowance(from, spender) == allowanceBefore || + f.selector == permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector; + } +} + +/* + @Rule + + @Description: + Transfer from a to b doesn't change the sum of their balances + + @Formula: + { + balancesBefore = balanceOf(msg.sender) + balanceOf(b) + } + < + transfer(b, amount) + > + { + balancesBefore == balanceOf(msg.sender) + balanceOf(b) + } + + @Notes: + + @Link: + +*/ +rule TransferSumOfFromAndToBalancesStaySame(address to, uint256 amount) { + env e; + mathint sum = balanceOf(e.msg.sender) + balanceOf(to); + require sum < max_uint256; + transfer(e, to, amount); + assert balanceOf(e.msg.sender) + balanceOf(to) == sum; +} + +/* + @Rule + + @Description: + Transfer using transferFrom() from a to b doesn't change the sum of their balances + + @Formula: + { + balancesBefore = balanceOf(a) + balanceOf(b) + } + < + transferFrom(a, b) + > + { + balancesBefore == balanceOf(a) + balanceOf(b) + } + + @Notes: + + @Link: + +*/ +rule TransferFromSumOfFromAndToBalancesStaySame(address from, address to, uint256 amount) { + env e; + mathint sum = balanceOf(from) + balanceOf(to); + require sum < max_uint256; + transferFrom(e, from, to, amount); + assert balanceOf(from) + balanceOf(to) == sum; +} + +/* + @Rule + + @Description: + Transfer from msg.sender to alice doesn't change the balance of other addresses + + @Formula: + { + balanceBefore = balanceOf(bob) + } + < + transfer(alice, amount) + > + { + balanceOf(bob) == balanceBefore + } + + @Notes: + + @Link: + +*/ +rule TransferDoesntChangeOtherBalance(address to, uint256 amount, address other) { + env e; + require other != e.msg.sender; + require other != to && other != currentContract; + uint256 balanceBefore = balanceOf(other); + transfer(e, to, amount); + assert balanceBefore == balanceOf(other); +} + +/* + @Rule + + @Description: + Transfer from alice to bob using transferFrom doesn't change the balance of other addresses + + @Formula: + { + balanceBefore = balanceOf(charlie) + } + < + transferFrom(alice, bob, amount) + > + { + balanceOf(charlie) = balanceBefore + } + + @Notes: + + @Link: + +*/ +rule TransferFromDoesntChangeOtherBalance(address from, address to, uint256 amount, address other) { + env e; + require other != from; + require other != to; + uint256 balanceBefore = balanceOf(other); + transferFrom(e, from, to, amount); + assert balanceBefore == balanceOf(other); +} + +/* + @Rule + + @Description: + Balance of an address, who is not a sender or a recipient in transfer functions, doesn't decrease + as a result of contract calls + + @Formula: + { + balanceBefore = balanceOf(charlie) + } + < + f(e, args) + > + { + f.selector != transfer && f.selector != transferFrom => balanceOf(charlie) == balanceBefore + } + + @Notes: + USDC token has functions like transferWithAuthorization that use a signed message for allowance. + FTT token has a burnFrom that lets an approved spender to burn owner's token. + Certora prover finds these counterexamples to this rule. + In general, the rule will fail on all functions other than transfer/transferFrom that change a balance of an address. + + @Link: + +*/ +rule OtherBalanceOnlyGoesUp(address other, method f) { + env e; + require other != currentContract; + uint256 balanceBefore = balanceOf(other); + + if (f.selector == transferFrom(address, address, uint256).selector) { + address from; + address to; + uint256 amount; + require(other != from); + require balanceOf(from) + balanceBefore < max_uint256; + transferFrom(e, from, to, amount); + } else if (f.selector == transfer(address, uint256).selector) { + require other != e.msg.sender; + require balanceOf(e.msg.sender) + balanceBefore < max_uint256; + calldataarg args; + f(e, args); + } else { + require other != e.msg.sender; + calldataarg args; + f(e, args); + } + + assert balanceOf(other) >= balanceBefore; +} + +rule noRebasing(method f, address alice) { + env e; + calldataarg args; + + require doesntChangeBalance(f); + + uint256 balanceBefore = balanceOf(alice); + f(e, args); + uint256 balanceAfter = balanceOf(alice); + assert balanceBefore == balanceAfter; +} diff --git a/certora/specs/general.spec b/certora/specs/general.spec new file mode 100644 index 0000000..2abcd70 --- /dev/null +++ b/certora/specs/general.spec @@ -0,0 +1,45 @@ +import "base.spec" + +ghost mathint sumDelegatedProposition { + init_state axiom sumDelegatedProposition == 0; +} + +ghost mathint sumBalances { + init_state axiom sumBalances == 0; +} + +/* + update proposition balance on each store + */ +hook Sstore _balances[KEY address user].delegatedPropositionBalance uint72 balance + (uint72 old_balance) STORAGE { + sumDelegatedProposition = sumDelegatedProposition + to_mathint(balance) - to_mathint(old_balance); + } + +// try to rewrite using power.spec in aave-tokenv2 customer code +hook Sstore _balances[KEY address user].balance uint104 balance + (uint104 old_balance) STORAGE { + sumBalances = sumBalances + to_mathint(balance) - to_mathint(old_balance); + } + + + +invariant sumDelegatedPropositionCorrectness() sumDelegatedProposition <= sumBalances { + // fails + preserved transfer(address to, uint256 amount) with (env e) + { + require(balanceOf(e.msg.sender) + balanceOf(to)) < totalSupply(); + } + preserved transferFrom(address from, address to, uint256 amount) with (env e) + { + require(balanceOf(from) + balanceOf(to)) < totalSupply(); + } +} + +invariant nonDelegatingBalance(address user) + !getDelegatingProposition(user) => balanceOf(user) == getDelegatedPropositionBalance(user) { + preserved transfer(address to, uint256 amount) with (env e) + { + require(getVotingDelegate(to) != user); + } + } \ No newline at end of file diff --git a/certora/specs/setup.spec b/certora/specs/setup.spec index 12354aa..79d207a 100644 --- a/certora/specs/setup.spec +++ b/certora/specs/setup.spec @@ -30,15 +30,20 @@ methods{ Constants */ - +// GovernancyType enum from the token contract definition VOTING_POWER() returns uint8 = 0; definition PROPOSITION_POWER() returns uint8 = 1; + definition DELEGATED_POWER_DIVIDER() returns uint256 = 10^10; + +// 16000000 * 10^18 is the maximum supply of the AAVE token definition MAX_DELEGATED_BALANCE() returns uint256 = 16000000 * 10^18 / DELEGATED_POWER_DIVIDER(); /** - Function that normalizes (removes 10 least significant digits) a given param + Function that normalizes (removes 10 least significant digits) a given param. + It mirrors the way delegated balances are stored in the token contract. Since the delegated + balance is stored as a uint72 variable, delegated amounts (uint256()) are normalized. */ @@ -70,16 +75,21 @@ rule delegateCorrectness(address bob) { delegate(e, bob); - address delegateAfter = getVotingDelegate(e.msg.sender); // test the delegate indeed has changed to bob + address delegateAfter = getVotingDelegate(e.msg.sender); assert delegateAfter == bob; - uint256 bobVotingPowerAfter = getPowerCurrent(bob, VOTING_POWER()); - // test the delegate's new voting power + uint256 bobVotingPowerAfter = getPowerCurrent(bob, VOTING_POWER()); assert bobVotingPowerAfter == bobVotingPowerBefore + normalize(delegatorBalance); } +/** + + Verify that only delegate functions can change someone's delegate. + An example for a parametric rule. + +*/ rule votingDelegateChanges(address alice, method f) { env e; @@ -97,4 +107,21 @@ rule votingDelegateChanges(address alice, method f) { f.selector == delegateByType(address,uint8).selector || f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector || f.selector == metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32).selector; -} \ No newline at end of file +} + +/** + + A ghost variable that tracks the sum of all addresses' balances + +*/ +ghost mathint sumBalances { + init_state axiom sumBalances == 0; +} + +hook Sstore _balances[KEY address user].balance uint104 balance + (uint104 old_balance) STORAGE { + sumBalances = sumBalances - to_mathint(old_balance) + to_mathint(balance); + } + +invariant totalSupplyEqualsBalances() + sumBalances == totalSupply() \ No newline at end of file From 258e337afc72a0ae3eedcc6837a01b3eab990d1d Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Tue, 26 Jul 2022 18:57:28 +0300 Subject: [PATCH 25/58] finalize delegate.spec --- certora/harness/AaveTokenV3Harness.sol | 23 +- certora/scripts/setup.sh | 13 - .../{verifyBgdSpec.sh => verifyDelegate.sh} | 6 +- certora/specs/base.spec | 3 +- certora/specs/{bgdSpec.spec => delegate.spec} | 472 ++++++++++++------ certora/specs/general.spec | 115 ++++- certora/specs/setup.spec | 127 ----- 7 files changed, 446 insertions(+), 313 deletions(-) delete mode 100644 certora/scripts/setup.sh rename certora/scripts/{verifyBgdSpec.sh => verifyDelegate.sh} (71%) rename certora/specs/{bgdSpec.spec => delegate.spec} (59%) delete mode 100644 certora/specs/setup.spec diff --git a/certora/harness/AaveTokenV3Harness.sol b/certora/harness/AaveTokenV3Harness.sol index 68b5939..578e363 100644 --- a/certora/harness/AaveTokenV3Harness.sol +++ b/certora/harness/AaveTokenV3Harness.sol @@ -6,6 +6,12 @@ import {IGovernancePowerDelegationToken} from '../../src/interfaces/IGovernanceP import {BaseAaveTokenV2} from './BaseAaveTokenV2Harness.sol'; contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { + + // Harness + uint256 public totalDelegatedPropositionBalance; + uint256 public totalDelegatedVotingBalance; + // End Harness + mapping(address => address) internal _votingDelegateeV2; mapping(address => address) internal _propositionDelegateeV2; @@ -165,7 +171,7 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { uint104 impactOnDelegationAfter, address delegatee, GovernancePowerType delegationType - ) public { // public instead of internal for testing a particular condition in this function + ) internal { if (delegatee == address(0)) return; if (impactOnDelegationBefore == impactOnDelegationAfter) return; @@ -173,16 +179,23 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { uint72 impactOnDelegationBefore72 = uint72(impactOnDelegationBefore / POWER_SCALE_FACTOR); uint72 impactOnDelegationAfter72 = uint72(impactOnDelegationAfter / POWER_SCALE_FACTOR); + uint72 temp; if (delegationType == GovernancePowerType.VOTING) { + temp = _balances[delegatee].delegatedVotingBalance; _balances[delegatee].delegatedVotingBalance = _balances[delegatee].delegatedVotingBalance - impactOnDelegationBefore72 + impactOnDelegationAfter72; + totalDelegatedVotingBalance = totalDelegatedVotingBalance - temp + + _balances[delegatee].delegatedVotingBalance; } else { + temp = _balances[delegatee].delegatedPropositionBalance; _balances[delegatee].delegatedPropositionBalance = _balances[delegatee].delegatedPropositionBalance - impactOnDelegationBefore72 + impactOnDelegationAfter72; + totalDelegatedPropositionBalance = totalDelegatedPropositionBalance - temp + + _balances[delegatee].delegatedPropositionBalance; } } @@ -389,14 +402,6 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { Harness section - replace struct reads and writes with function calls */ -// struct DelegationAwareBalance { -// uint104 balance; -// uint72 delegatedPropositionBalance; -// uint72 delegatedVotingBalance; -// bool delegatingProposition; -// bool delegatingVoting; -// } - function getBalance(address user) view public returns (uint104) { return _balances[user].balance; diff --git a/certora/scripts/setup.sh b/certora/scripts/setup.sh deleted file mode 100644 index 6e7f0a5..0000000 --- a/certora/scripts/setup.sh +++ /dev/null @@ -1,13 +0,0 @@ -if [[ "$1" ]] -then - RULE="--rule $1" -fi - -certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ - --verify AaveTokenV3:certora/specs/setup.spec \ - $RULE \ - --solc solc8.13 \ - --optimistic_loop \ - --send_only \ - --staging \ - --msg "AaveTokenV3:setup.spec $1" diff --git a/certora/scripts/verifyBgdSpec.sh b/certora/scripts/verifyDelegate.sh similarity index 71% rename from certora/scripts/verifyBgdSpec.sh rename to certora/scripts/verifyDelegate.sh index 7ddf709..a138b20 100755 --- a/certora/scripts/verifyBgdSpec.sh +++ b/certora/scripts/verifyDelegate.sh @@ -4,11 +4,11 @@ then fi certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ - --verify AaveTokenV3:certora/specs/bgdSpec.spec \ - --rule $1 \ + --verify AaveTokenV3:certora/specs/delegate.spec \ + $RULE \ --solc solc8.13 \ --optimistic_loop \ --send_only \ -# --staging \ + --staging \ --msg "AaveTokenV3:bgdSpec.spec $1" \ No newline at end of file diff --git a/certora/specs/base.spec b/certora/specs/base.spec index 08fb846..13c18ef 100644 --- a/certora/specs/base.spec +++ b/certora/specs/base.spec @@ -9,7 +9,8 @@ methods { permit(address,address,uint256,uint256,uint8,bytes32,bytes32) delegate(address delegatee) - metaDelegate(address, address, uint256, uint8, bytes32, bytes32) + metaDelegate(address,address,uint256,uint8,bytes32,bytes32) + metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32) getPowerCurrent(address user, uint8 delegationType) returns (uint256) envfree getBalance(address user) returns (uint104) envfree diff --git a/certora/specs/bgdSpec.spec b/certora/specs/delegate.spec similarity index 59% rename from certora/specs/bgdSpec.spec rename to certora/specs/delegate.spec index e466e08..ba1708a 100644 --- a/certora/specs/bgdSpec.spec +++ b/certora/specs/delegate.spec @@ -1,100 +1,29 @@ -import "base.spec" - - - -rule totalSupplyCorrectness(method f) { - env e; - calldataarg args; - - require sumBalances == to_mathint(totalSupply()); - f(e, args); - assert sumBalances == to_mathint(totalSupply()); -} - -// doesn't work cause we can start with a state in which an address can have delegated balance field -// larger than total supply. -// rule sumDelegatedPropositionCorrect(method f) { -// env e; -// calldataarg args; - -// uint256 supplyBefore = totalSupply(); -// require sumDelegatedProposition <= supplyBefore; -// f(e, args); -// uint256 supplyAfter = totalSupply(); -// assert sumDelegatedProposition <= supplyAfter; -// } - - -rule transferUnitTest() { - env e; - address to; - uint256 amount; - require(to != e.msg.sender); - - uint256 powerToBefore = getPowerCurrent(to, VOTING_POWER()); - uint256 powerSenderBefore = getPowerCurrent(e.msg.sender, VOTING_POWER()); - transfer(e, to, amount); - uint256 powerToAfter = getPowerCurrent(to, VOTING_POWER()); - - assert powerToAfter == powerToBefore + powerSenderBefore; -} - -// for non delegating address -rule votingPowerEqualsBalance(address user) { - uint256 userBalance = balanceOf(user); - require(!getDelegatingProposition(user)); - require(!getDelegatingVoting(user)); - assert userBalance == getDelegatedPropositionBalance(user) && userBalance == getDelegatedVotingBalance(user); -} - -// Verify that the voting delegation balances update correctly -// probably a scaling issue -rule tokenTransferCorrectnessVoting(address from, address to, uint256 amount) { - env e; - - require(from != 0 && to != 0); - - uint256 balanceFromBefore = balanceOf(from); - uint256 balanceToBefore = balanceOf(to); - - address fromDelegate = getVotingDelegate(from); - address toDelegate = getVotingDelegate(to); - - uint256 powerFromDelegateBefore = getPowerCurrent(fromDelegate, VOTING_POWER()); - uint256 powerToDelegateBefore = getPowerCurrent(toDelegate, VOTING_POWER()); - bool isDelegatingVotingFromBefore = getDelegatingVoting(from); - bool isDelegatingVotingToBefore = getDelegatingVoting(to); +/* + This is a specification file for the verification of verification + features of AaveTokenV3.sol smart contract using the Certora prover. + For more information, visit: https://www.certora.com/ - // non reverting path - transferFrom(e, from, to, amount); + This file is run with scripts/verifyDelegate.sh + On a version with some small code modifications AaveTokenV3Harness.sol - uint256 balanceFromAfter = balanceOf(from); - uint256 balanceToAfter = balanceOf(to); - - address fromDelegateAfter = getVotingDelegate(from); - address toDelegateAfter = getVotingDelegate(to); +*/ - uint256 powerFromDelegateAfter = getPowerCurrent(fromDelegateAfter, VOTING_POWER()); - uint256 powerToDelegateAfter = getPowerCurrent(toDelegateAfter, VOTING_POWER()); +import "base.spec" - bool isDelegatingVotingFromAfter = getDelegatingVoting(from); - bool isDelegatingVotingToAfter = getDelegatingVoting(to); - assert fromDelegateAfter == toDelegateAfter => powerFromDelegateBefore == powerFromDelegateAfter; +/* + @Rule - assert isDelegatingVotingFromBefore => - powerFromDelegateAfter - powerFromDelegateBefore == amount || - (fromDelegateAfter == toDelegateAfter && powerFromDelegateBefore == powerFromDelegateAfter); - assert isDelegatingVotingToBefore => - powerToDelegateAfter - powerToDelegateBefore == amount || - (fromDelegateAfter == toDelegateAfter && powerToDelegateBefore == powerToDelegateAfter); + @Description: + If an account is not receiving delegation of power (one type) from anybody, + and that account is not delegating that power to anybody, the power of that account + must be equal to its token balance. -} + @Note: -// If an account is not receiving delegation of power (one type) from anybody, -// and that account is not delegating that power to anybody, the power of that account -// must be equal to its AAVE balance. + @Link: +*/ rule powerWhenNotDelegating(address account) { uint256 balance = balanceOf(account); @@ -110,22 +39,22 @@ rule powerWhenNotDelegating(address account) { assert dpb == 0 && !isDelegatingProposition => propositionPower == balance; } -// wrong, user may delegate to himself/0 and the flag will be set true -rule selfDelegationCorrectness(address account) { - bool isDelegatingVoting = getDelegatingVoting(account); - bool isDelegatingProposition = getDelegatingProposition(account); - address votingDelegate = getVotingDelegate(account); - address propositionDelegate = getPropositionDelegate(account); - - assert votingDelegate == 0 || votingDelegate == account => isDelegatingVoting == false; - assert propositionDelegate == 0 || propositionDelegate == account => isDelegatingProposition == false; - -} /** Account1 and account2 are not delegating power */ +/* + @Rule + + @Description: + Verify correct voting power on token transfers, when both accounts are not delegating + + @Note: + + @Link: +*/ + rule vpTransferWhenBothNotDelegating(address alice, address bob, address charlie, uint256 amount) { env e; require alice != bob && bob != charlie && alice != charlie; @@ -133,6 +62,7 @@ rule vpTransferWhenBothNotDelegating(address alice, address bob, address charlie bool isAliceDelegatingVoting = getDelegatingVoting(alice); bool isBobDelegatingVoting = getDelegatingVoting(bob); + // both accounts are not delegating require !isAliceDelegatingVoting && !isBobDelegatingVoting; uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); @@ -150,16 +80,23 @@ rule vpTransferWhenBothNotDelegating(address alice, address bob, address charlie assert charliePowerAfter == charliePowerBefore; } +/* + @Rule + + @Description: + Verify correct proposition power on token transfers, when both accounts are not delegating + + @Note: + + @Link: +*/ rule ppTransferWhenBothNotDelegating(address alice, address bob, address charlie, uint256 amount) { env e; require alice != bob && bob != charlie && alice != charlie; bool isAliceDelegatingProposition = getDelegatingProposition(alice); - // bool isAliceDelegatingProposition = getDelegatedProposition(alice); - bool isBobDelegatingProposition = getDelegatingProposition(bob); - // bool isBobDelegatingProposition = getDelegatedProposition(bob); require !isAliceDelegatingProposition && !isBobDelegatingProposition; @@ -178,6 +115,18 @@ rule ppTransferWhenBothNotDelegating(address alice, address bob, address charlie assert charliePowerAfter == charliePowerBefore; } +/* + @Rule + + @Description: + Verify correct voting power after Alice delegates to Bob, when + both accounts were not delegating + + @Note: + + @Link: +*/ + rule vpDelegateWhenBothNotDelegating(address alice, address bob, address charlie) { env e; require alice == e.msg.sender; @@ -208,6 +157,18 @@ rule vpDelegateWhenBothNotDelegating(address alice, address bob, address charlie assert charliePowerAfter == charliePowerBefore; } +/* + @Rule + + @Description: + Verify correct proposition power after Alice delegates to Bob, when + both accounts were not delegating + + @Note: + + @Link: +*/ + rule ppDelegateWhenBothNotDelegating(address alice, address bob, address charlie) { env e; require alice == e.msg.sender; @@ -242,7 +203,17 @@ rule ppDelegateWhenBothNotDelegating(address alice, address bob, address charlie Account1 is delegating power to delegatee1, account2 is not delegating power to anybody */ -// token transfer from alice to bob +/* + @Rule + + @Description: + Verify correct voting power after a token transfer from Alice to Bob, when + Alice was delegating and Bob wasn't + + @Note: + + @Link: +*/ rule vpTransferWhenOnlyOneIsDelegating(address alice, address bob, address charlie, uint256 amount) { env e; @@ -262,6 +233,7 @@ rule vpTransferWhenOnlyOneIsDelegating(address alice, address bob, address charl uint256 bobPowerBefore = getPowerCurrent(bob, VOTING_POWER()); uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, VOTING_POWER()); + uint256 aliceBalanceBefore = balanceOf(alice); transferFrom(e, alice, bob, amount); @@ -269,15 +241,26 @@ rule vpTransferWhenOnlyOneIsDelegating(address alice, address bob, address charl uint256 bobPowerAfter = getPowerCurrent(bob, VOTING_POWER()); uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, VOTING_POWER()); + uint256 aliceBalanceAfter = balanceOf(alice); - // still zero assert alicePowerBefore == alicePowerAfter; assert aliceDelegatePowerAfter == - aliceDelegatePowerBefore - normalize(amount); + aliceDelegatePowerBefore - normalize(aliceBalanceBefore) + normalize(aliceBalanceAfter); assert bobPowerAfter == bobPowerBefore + amount; assert charliePowerBefore == charliePowerAfter; } +/* + @Rule + + @Description: + Verify correct proposition power after a token transfer from Alice to Bob, when + Alice was delegating and Bob wasn't + + @Note: + + @Link: +*/ rule ppTransferWhenOnlyOneIsDelegating(address alice, address bob, address charlie, uint256 amount) { env e; @@ -286,6 +269,7 @@ rule ppTransferWhenOnlyOneIsDelegating(address alice, address bob, address charl bool isAliceDelegatingProposition = getDelegatingProposition(alice); bool isBobDelegatingProposition = getDelegatingProposition(bob); address aliceDelegate = getPropositionDelegate(alice); + require aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != bob && aliceDelegate != charlie; require isAliceDelegatingProposition && !isBobDelegatingProposition; @@ -296,6 +280,7 @@ rule ppTransferWhenOnlyOneIsDelegating(address alice, address bob, address charl uint256 bobPowerBefore = getPowerCurrent(bob, PROPOSITION_POWER()); uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + uint256 aliceBalanceBefore = balanceOf(alice); transferFrom(e, alice, bob, amount); @@ -303,18 +288,28 @@ rule ppTransferWhenOnlyOneIsDelegating(address alice, address bob, address charl uint256 bobPowerAfter = getPowerCurrent(bob, PROPOSITION_POWER()); uint256 charliePowerAfter = getPowerCurrent(charlie, PROPOSITION_POWER()); uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + uint256 aliceBalanceAfter = balanceOf(alice); // still zero assert alicePowerBefore == alicePowerAfter; - // this is the equation in the properties.md, but it's wrong when amount == 10 ^ 10 - // assert aliceDelegatePowerAfter == - // aliceDelegatePowerBefore - (amount / DELEGATED_POWER_DIVIDER() * DELEGATED_POWER_DIVIDER()); - assert aliceDelegatePowerAfter == aliceDelegatePowerBefore; + assert aliceDelegatePowerAfter == + aliceDelegatePowerBefore - normalize(aliceBalanceBefore) + normalize(aliceBalanceAfter); assert bobPowerAfter == bobPowerBefore + amount; assert charliePowerBefore == charliePowerAfter; } -// After account1 will stop delegating his power to delegatee1 + +/* + @Rule + + @Description: + Verify correct voting power after Alice stops delegating, when + Alice was delegating and Bob wasn't + + @Note: + + @Link: +*/ rule vpStopDelegatingWhenOnlyOneIsDelegating(address alice, address charlie) { env e; require alice != charlie; @@ -323,7 +318,7 @@ rule vpStopDelegatingWhenOnlyOneIsDelegating(address alice, address charlie) { bool isAliceDelegatingVoting = getDelegatingVoting(alice); address aliceDelegate = getVotingDelegate(alice); - require isAliceDelegatingVoting; + require isAliceDelegatingVoting && aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != charlie; uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); @@ -336,10 +331,21 @@ rule vpStopDelegatingWhenOnlyOneIsDelegating(address alice, address charlie) { uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, VOTING_POWER()); assert alicePowerAfter == alicePowerBefore + balanceOf(alice); - assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - balanceOf(alice); + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - normalize(balanceOf(alice)); assert charliePowerAfter == charliePowerBefore; } +/* + @Rule + + @Description: + Verify correct proposition power after Alice stops delegating, when + Alice was delegating and Bob wasn't + + @Note: + + @Link: +*/ rule ppStopDelegatingWhenOnlyOneIsDelegating(address alice, address charlie) { env e; require alice != charlie; @@ -348,7 +354,7 @@ rule ppStopDelegatingWhenOnlyOneIsDelegating(address alice, address charlie) { bool isAliceDelegatingProposition = getDelegatingProposition(alice); address aliceDelegate = getPropositionDelegate(alice); - require isAliceDelegatingProposition; + require isAliceDelegatingProposition && aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != charlie; uint256 alicePowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); @@ -361,10 +367,20 @@ rule ppStopDelegatingWhenOnlyOneIsDelegating(address alice, address charlie) { uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); assert alicePowerAfter == alicePowerBefore + balanceOf(alice); - assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - balanceOf(alice); + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - normalize(balanceOf(alice)); assert charliePowerAfter == charliePowerBefore; } +/* + @Rule + + @Description: + Verify correct voting power after Alice delegates + + @Note: + + @Link: +*/ rule vpChangeDelegateWhenOnlyOneIsDelegating(address alice, address delegate2, address charlie) { env e; require alice != charlie && alice != delegate2 && charlie != delegate2; @@ -372,7 +388,8 @@ rule vpChangeDelegateWhenOnlyOneIsDelegating(address alice, address delegate2, a bool isAliceDelegatingVoting = getDelegatingVoting(alice); address aliceDelegate = getVotingDelegate(alice); - require aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != delegate2 && delegate2 != 0; + require aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != delegate2 && + delegate2 != 0 && delegate2 != charlie && aliceDelegate != charlie; require isAliceDelegatingVoting; @@ -396,7 +413,58 @@ rule vpChangeDelegateWhenOnlyOneIsDelegating(address alice, address delegate2, a assert charliePowerAfter == charliePowerBefore; } -// Account1 not delegating power to anybody, account2 is delegating power to delegatee2 +/* + @Rule + + @Description: + Verify correct proposition power after Alice delegates + + @Note: + + @Link: +*/ +rule ppChangeDelegateWhenOnlyOneIsDelegating(address alice, address delegate2, address charlie) { + env e; + require alice != charlie && alice != delegate2 && charlie != delegate2; + require alice == e.msg.sender; + + bool isAliceDelegatingVoting = getDelegatingProposition(alice); + address aliceDelegate = getPropositionDelegate(alice); + require aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != delegate2 && + delegate2 != 0 && delegate2 != charlie && aliceDelegate != charlie; + + require isAliceDelegatingVoting; + + uint256 alicePowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + uint256 delegate2PowerBefore = getPowerCurrent(delegate2, PROPOSITION_POWER()); + + delegate(e, delegate2); + + uint256 alicePowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + uint256 delegate2PowerAfter = getPowerCurrent(delegate2, PROPOSITION_POWER()); + address aliceDelegateAfter = getPropositionDelegate(alice); + + assert alicePowerBefore == alicePowerAfter; + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - normalize(balanceOf(alice)); + assert delegate2PowerAfter == delegate2PowerBefore + normalize(balanceOf(alice)); + assert aliceDelegateAfter == delegate2; + assert charliePowerAfter == charliePowerBefore; +} + +/* + @Rule + + @Description: + Verify correct voting power after Alice transfers to Bob, when only Bob was delegating + + @Note: + + @Link: +*/ rule vpOnlyAccount2IsDelegating(address alice, address bob, address charlie, uint256 amount) { env e; @@ -414,6 +482,7 @@ rule vpOnlyAccount2IsDelegating(address alice, address bob, address charlie, uin require bobPowerBefore == 0; uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); uint256 bobDelegatePowerBefore = getPowerCurrent(bobDelegate, VOTING_POWER()); + uint256 bobBalanceBefore = balanceOf(bob); transferFrom(e, alice, bob, amount); @@ -421,17 +490,71 @@ rule vpOnlyAccount2IsDelegating(address alice, address bob, address charlie, uin uint256 bobPowerAfter = getPowerCurrent(bob, VOTING_POWER()); uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); uint256 bobDelegatePowerAfter = getPowerCurrent(bobDelegate, VOTING_POWER()); + uint256 bobBalanceAfter = balanceOf(bob); + + assert alicePowerAfter == alicePowerBefore - amount; + assert bobPowerAfter == 0; + assert bobDelegatePowerAfter == bobDelegatePowerBefore - normalize(bobBalanceBefore) + normalize(bobBalanceAfter); + + assert charliePowerAfter == charliePowerBefore; +} + +/* + @Rule + + @Description: + Verify correct proposition power after Alice transfers to Bob, when only Bob was delegating + + @Note: + + @Link: +*/ + +rule ppOnlyAccount2IsDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegating = getDelegatingProposition(alice); + bool isBobDelegating = getDelegatingProposition(bob); + address bobDelegate = getPropositionDelegate(bob); + require bobDelegate != bob && bobDelegate != 0 && bobDelegate != alice && bobDelegate != charlie; + + require !isAliceDelegating && isBobDelegating; + + uint256 alicePowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, PROPOSITION_POWER()); + require bobPowerBefore == 0; + uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 bobDelegatePowerBefore = getPowerCurrent(bobDelegate, PROPOSITION_POWER()); + uint256 bobBalanceBefore = balanceOf(bob); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 bobDelegatePowerAfter = getPowerCurrent(bobDelegate, PROPOSITION_POWER()); + uint256 bobBalanceAfter = balanceOf(bob); assert alicePowerAfter == alicePowerBefore - amount; assert bobPowerAfter == 0; - assert bobDelegatePowerAfter == bobDelegatePowerBefore + normalize(amount); + assert bobDelegatePowerAfter == bobDelegatePowerBefore - normalize(bobBalanceBefore) + normalize(bobBalanceAfter); assert charliePowerAfter == charliePowerBefore; } -//add for proposition -// Account1 is delegating power to delegatee1, account2 is delegating power to delegatee2 +/* + @Rule + + @Description: + Verify correct voting power after Alice transfers to Bob, when both Alice + and Bob were delegating + + @Note: + + @Link: +*/ rule vpTransferWhenBothAreDelegating(address alice, address bob, address charlie, uint256 amount) { env e; require alice != bob && bob != charlie && alice != charlie; @@ -470,61 +593,116 @@ rule vpTransferWhenBothAreDelegating(address alice, address bob, address charlie uint256 normalizedBalanceBefore = normalize(bobBalanceBefore); uint256 normalizedBalanceAfter = normalize(bobBalanceAfter); - uint256 delta = bobBalanceAfter - bobBalanceBefore; assert bobDelegatePowerAfter == bobDelegatePowerBefore - normalizedBalanceBefore + normalizedBalanceAfter; - // assert bobDelegatePowerAfter == bobDelegatePowerBefore - normalize(delta); } -rule test_governancePowerTransferByType(uint104 impactBefore, uint104 impactAfter, address delegatee, uint8 type) { - env e; - require type == 0 || type == 1; - - _governancePowerTransferByType@withrevert(e, impactBefore, impactAfter, delegatee, type); - assert !lastReverted; -} +/* + @Rule + @Description: + Verify correct proposition power after Alice transfers to Bob, when both Alice + and Bob were delegating -/*** + @Note: + @Link: +*/ +rule ppTransferWhenBothAreDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; -bobDelegate power before: 0x30d4ad60c400 = 53690000000000 -bob balance before: 0x2540c00cb = 10000007371 + bool isAliceDelegating = getDelegatingProposition(alice); + bool isBobDelegating = getDelegatingProposition(bob); + require isAliceDelegating && isBobDelegating; + address aliceDelegate = getPropositionDelegate(alice); + address bobDelegate = getPropositionDelegate(bob); + require aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != bob && aliceDelegate != charlie; + require bobDelegate != bob && bobDelegate != 0 && bobDelegate != alice && bobDelegate != charlie; + require aliceDelegate != bobDelegate; -transfer alice -> bob 0x6fc238f34 = 29999992628 + uint256 alicePowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + uint256 bobDelegatePowerBefore = getPowerCurrent(bobDelegate, PROPOSITION_POWER()); + uint256 aliceBalanceBefore = balanceOf(alice); + uint256 bobBalanceBefore = balanceOf(bob); -bob balance after: 0x9502f8fff = 39999999999 -bobDelegate power after: 0x30d00548fc00 = 53670000000000 + transferFrom(e, alice, bob, amount); -expected: 0x30d25954e000 = 53680000000000 -0x30d955788c00 - + uint256 alicePowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + uint256 bobDelegatePowerAfter = getPowerCurrent(bobDelegate, PROPOSITION_POWER()); + uint256 aliceBalanceAfter = balanceOf(alice); + uint256 bobBalanceAfter = balanceOf(bob); -delta: 0x1001d1bf7ff = 1099999999999 + assert alicePowerAfter == alicePowerBefore; + assert bobPowerAfter == bobPowerBefore; + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - normalize(aliceBalanceBefore) + + normalize(aliceBalanceAfter); -1. bobDelegate power before: 0x30d4ad60c400 = 53690000000000 -2. bobDelegate power after: 0x30d00548fc00 = 53670000000000 + uint256 normalizedBalanceBefore = normalize(bobBalanceBefore); + uint256 normalizedBalanceAfter = normalize(bobBalanceAfter); + assert bobDelegatePowerAfter == bobDelegatePowerBefore - normalizedBalanceBefore + normalizedBalanceAfter; +} -3. bobDelegatePowerBefore-normalize(bobBalanceBefore)+intnormalize(bobBalanceafter) = -53690000000000 - normalize(10000007371) + normalize(39999999999) = -53690000000000 - 10000000000 + 30000000000 = 53710000000000 +/* + @Rule -*/ + @Description: + Verify that an account's delegate changes only as a result of a call to + the delegation functions + @Note: + @Link: +*/ rule votingDelegateChanges(address alice, method f) { env e; calldataarg args; - address aliceDelegateBefore = getVotingDelegate(alice); + address aliceVotingDelegateBefore = getVotingDelegate(alice); + address alicePropDelegateBefore = getPropositionDelegate(alice); f(e, args); - address aliceDelegateAfter = getVotingDelegate(alice); + address aliceVotingDelegateAfter = getVotingDelegate(alice); + address alicePropDelegateAfter = getPropositionDelegate(alice); // only these four function may change the delegate of an address - assert aliceDelegateAfter != aliceDelegateBefore => + assert aliceVotingDelegateAfter != aliceVotingDelegateBefore || alicePropDelegateBefore != alicePropDelegateAfter => f.selector == delegate(address).selector || f.selector == delegateByType(address,uint8).selector || f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector || f.selector == metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32).selector; +} + + +/* + @Rule + + @Description: + Verify that only delegate() and metaDelegate() may change both voting and + proposition delegates of an account at once. + + @Note: + + @Link: +*/ +rule delegationTypeIndependence(address who, method f) filtered { f -> !f.isView } { + address _delegateeV = getVotingDelegate(who); + address _delegateeP = getPropositionDelegate(who); + + env e; + calldataarg arg; + f(e, arg); + + address delegateeV_ = getVotingDelegate(who); + address delegateeP_ = getPropositionDelegate(who); + assert (_delegateeV == delegateeV_ || _delegateeP == delegateeP_) || ( + f.selector == delegate(address).selector || + f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector + ), "one delegatee type stays the same, unless delegate or delegateBySig was called"; } \ No newline at end of file diff --git a/certora/specs/general.spec b/certora/specs/general.spec index 2abcd70..f60d6a1 100644 --- a/certora/specs/general.spec +++ b/certora/specs/general.spec @@ -1,5 +1,10 @@ import "base.spec" +methods { + totalDelegatedVotingBalance() returns (uint256) envfree + totalDelegatedPropositionBalance() returns (uint256) envfree +} + ghost mathint sumDelegatedProposition { init_state axiom sumDelegatedProposition == 0; } @@ -13,7 +18,9 @@ ghost mathint sumBalances { */ hook Sstore _balances[KEY address user].delegatedPropositionBalance uint72 balance (uint72 old_balance) STORAGE { - sumDelegatedProposition = sumDelegatedProposition + to_mathint(balance) - to_mathint(old_balance); + sumDelegatedProposition = + sumDelegatedProposition + to_mathint(balance) * DELEGATED_POWER_DIVIDER() - to_mathint(old_balance) * + DELEGATED_POWER_DIVIDER(); } // try to rewrite using power.spec in aave-tokenv2 customer code @@ -22,24 +29,106 @@ hook Sstore _balances[KEY address user].balance uint104 balance sumBalances = sumBalances + to_mathint(balance) - to_mathint(old_balance); } +hook Sload uint104 balance _balances[KEY address user].balance STORAGE { + require balance <= sumBalances; +} - -invariant sumDelegatedPropositionCorrectness() sumDelegatedProposition <= sumBalances { - // fails - preserved transfer(address to, uint256 amount) with (env e) - { - require(balanceOf(e.msg.sender) + balanceOf(to)) < totalSupply(); - } - preserved transferFrom(address from, address to, uint256 amount) with (env e) - { - require(balanceOf(from) + balanceOf(to)) < totalSupply(); - } +hook Sload uint72 balance _balances[KEY address user].delegatedPropositionBalance STORAGE { + require balance <= sumDelegatedProposition; } + +invariant sumDelegatedPropositionCorrectness() totalDelegatedPropositionBalance() <= totalSupply() + invariant nonDelegatingBalance(address user) !getDelegatingProposition(user) => balanceOf(user) == getDelegatedPropositionBalance(user) { preserved transfer(address to, uint256 amount) with (env e) { require(getVotingDelegate(to) != user); } - } \ No newline at end of file + } + +rule sumDelegatedBalances(method f) { + env e; + calldataarg args; + + uint256 total = totalSupply(); + mathint sumBalancesGhost = sumBalances; + require totalSupply() <= AAVE_MAX_SUPPLY(); + // proved elsewhere + require sumBalances == totalSupply(); + uint256 sumBalancesBefore = totalDelegatedPropositionBalance(); + require sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost; + if (f.selector == delegate(address).selector) { + address delegatee; + uint256 balance = balanceOf(e.msg.sender); + bool isDelegating = getDelegatingProposition(e.msg.sender); + require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - balance; + delegate(e, delegatee); + } else if (f.selector == delegateByType(address,uint8).selector) { + address delegatee; + uint256 balance = balanceOf(e.msg.sender); + bool isDelegating = getDelegatingProposition(e.msg.sender); + require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - balance; + delegateByType(e, delegatee, PROPOSITION_POWER()); + } else if (f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector) { + address delegator; address delegatee; uint256 deadline; uint8 v; bytes32 r; bytes32 s; + uint256 balance = balanceOf(delegator); + bool isDelegating = getDelegatingProposition(delegator); + require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - balance; + metaDelegate(e, delegator, delegatee, deadline, v, r, s); + } else if (f.selector == metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32).selector) { + address delegator; address delegatee; uint256 deadline; uint8 v; bytes32 r; bytes32 s; + uint256 balance = balanceOf(delegator); + bool isDelegating = getDelegatingProposition(delegator); + require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - balance; + // metaDelegate by Proposition type (1) + metaDelegateByType(e, delegator, delegatee, PROPOSITION_POWER(), deadline, v, r, s); + } else if (f.selector == transfer(address,uint256).selector) { + address to; + uint256 amount; + uint256 balance = balanceOf(e.msg.sender); + require balance < AAVE_MAX_SUPPLY(); + bool isDelegating = getDelegatingProposition(e.msg.sender); + address toDelegate = getPropositionDelegate(to); + require toDelegate != 0; + uint256 toDelegatePower = getPowerCurrent(toDelegate, PROPOSITION_POWER()); + + require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - amount; + require isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() >= balance + toDelegatePower; + transfer(e, to, amount); + } else if (f.selector == transferFrom(address,address,uint256).selector) { + address to; + address from; + uint256 amount; + uint256 balanceFromBefore = balanceOf(from); + uint256 balanceToBefore = balanceOf(to); + require balanceFromBefore <= AAVE_MAX_SUPPLY(); + bool isDelegating = getDelegatingProposition(from); + address delegateFrom = getPropositionDelegate(from); + uint256 votingPowerFromDelegateBefore = getPowerCurrent(delegateFrom, PROPOSITION_POWER()); + address delegateTo = getPropositionDelegate(to); + uint256 votingPowerToDelegateBefore = getPowerCurrent(delegateTo, PROPOSITION_POWER()); + require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - amount; + require isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() >= balanceFromBefore; + transferFrom(e, from, to, amount); + uint256 balanceFromAfter = balanceOf(from); + uint256 balanceToAfter = balanceOf(to); + uint256 votingPowerFromDelegateAfter = getPowerCurrent(delegateFrom, PROPOSITION_POWER()); + uint256 votingPowerToDelegateAfter = getPowerCurrent(delegateTo, PROPOSITION_POWER()); + uint256 normAmount = normalize(amount); + uint256 votingPowerDelta = votingPowerToDelegateAfter - votingPowerToDelegateBefore; + assert isDelegating => votingPowerDelta == normAmount || delegateFrom == delegateTo; + } + else { + f(e, args); + } + uint256 sumBalancesAfter = totalDelegatedPropositionBalance(); + assert sumBalancesAfter * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost; + +} + +// TODO: separate rules for transfers, see that the sum of balances stays the same + +// sum of power for two addresses involved in f, doesn't change. +rule sumPowerCurrent(method f) \ No newline at end of file diff --git a/certora/specs/setup.spec b/certora/specs/setup.spec deleted file mode 100644 index 79d207a..0000000 --- a/certora/specs/setup.spec +++ /dev/null @@ -1,127 +0,0 @@ -/** - - Setup for writing rules for Aave Token v3 - -*/ - -/** - Public methods from the AaveTokenV3Harness.sol -*/ - -methods{ - totalSupply() returns (uint256) envfree - balanceOf(address addr) returns (uint256) envfree - transfer(address to, uint256 amount) returns (bool) - transferFrom(address from, address to, uint256 amount) returns (bool) - delegate(address delegatee) - metaDelegate(address, address, uint256, uint8, bytes32, bytes32) - getPowerCurrent(address user, uint8 delegationType) returns (uint256) envfree - getBalance(address user) returns (uint104) envfree - getDelegatedPropositionBalance(address user) returns (uint72) envfree - getDelegatedVotingBalance(address user) returns (uint72) envfree - getDelegatingProposition(address user) returns (bool) envfree - getDelegatingVoting(address user) returns (bool) envfree - getVotingDelegate(address user) returns (address) envfree - getPropositionDelegate(address user) returns (address) envfree -} - -/** - - Constants - -*/ -// GovernancyType enum from the token contract -definition VOTING_POWER() returns uint8 = 0; -definition PROPOSITION_POWER() returns uint8 = 1; - -definition DELEGATED_POWER_DIVIDER() returns uint256 = 10^10; - -// 16000000 * 10^18 is the maximum supply of the AAVE token -definition MAX_DELEGATED_BALANCE() returns uint256 = 16000000 * 10^18 / DELEGATED_POWER_DIVIDER(); - -/** - - Function that normalizes (removes 10 least significant digits) a given param. - It mirrors the way delegated balances are stored in the token contract. Since the delegated - balance is stored as a uint72 variable, delegated amounts (uint256()) are normalized. - -*/ - -function normalize(uint256 amount) returns uint256 { - return to_uint256(amount / DELEGATED_POWER_DIVIDER() * DELEGATED_POWER_DIVIDER()); -} - -/** - - Testing correctness of delegate(). An example of a unit test - -*/ - -rule delegateCorrectness(address bob) { - env e; - // delegate not to self or to zero - require bob != e.msg.sender && bob != 0; - - uint256 bobDelegatedBalance = getDelegatedVotingBalance(bob); - // avoid unrealistic delegated balance - require(bobDelegatedBalance < MAX_DELEGATED_BALANCE()); - - // verify that the sender doesn't already delegate to bob - address delegateBefore = getVotingDelegate(e.msg.sender); - require delegateBefore != bob; - - uint256 bobVotingPowerBefore = getPowerCurrent(bob, VOTING_POWER()); - uint256 delegatorBalance = balanceOf(e.msg.sender); - - delegate(e, bob); - - // test the delegate indeed has changed to bob - address delegateAfter = getVotingDelegate(e.msg.sender); - assert delegateAfter == bob; - - // test the delegate's new voting power - uint256 bobVotingPowerAfter = getPowerCurrent(bob, VOTING_POWER()); - assert bobVotingPowerAfter == bobVotingPowerBefore + normalize(delegatorBalance); -} - -/** - - Verify that only delegate functions can change someone's delegate. - An example for a parametric rule. - -*/ - -rule votingDelegateChanges(address alice, method f) { - env e; - calldataarg args; - - address aliceDelegateBefore = getVotingDelegate(alice); - - f(e, args); - - address aliceDelegateAfter = getVotingDelegate(alice); - - // only these four function may change the delegate of an address - assert aliceDelegateAfter != aliceDelegateBefore => - f.selector == delegate(address).selector || - f.selector == delegateByType(address,uint8).selector || - f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector || - f.selector == metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32).selector; -} - -/** - - A ghost variable that tracks the sum of all addresses' balances - -*/ -ghost mathint sumBalances { - init_state axiom sumBalances == 0; -} - -hook Sstore _balances[KEY address user].balance uint104 balance - (uint104 old_balance) STORAGE { - sumBalances = sumBalances - to_mathint(old_balance) + to_mathint(balance); - } - -invariant totalSupplyEqualsBalances() - sumBalances == totalSupply() \ No newline at end of file From cff0a2ff2bf2d634eae532dcfd16f5c3dce63a06 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Wed, 27 Jul 2022 17:46:58 +0300 Subject: [PATCH 26/58] refactor harness --- certora/harness/AaveTokenV3Harness.sol | 448 --------------------- certora/harness/AaveTokenV3HarnessNew.sol | 43 ++ certora/harness/BaseAaveTokenHarness.sol | 161 -------- certora/harness/BaseAaveTokenV2Harness.sol | 82 ---- certora/scripts/verifyDelegate.sh | 4 +- 5 files changed, 45 insertions(+), 693 deletions(-) delete mode 100644 certora/harness/AaveTokenV3Harness.sol create mode 100644 certora/harness/AaveTokenV3HarnessNew.sol delete mode 100644 certora/harness/BaseAaveTokenHarness.sol delete mode 100644 certora/harness/BaseAaveTokenV2Harness.sol diff --git a/certora/harness/AaveTokenV3Harness.sol b/certora/harness/AaveTokenV3Harness.sol deleted file mode 100644 index 578e363..0000000 --- a/certora/harness/AaveTokenV3Harness.sol +++ /dev/null @@ -1,448 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {VersionedInitializable} from '../../src/utils/VersionedInitializable.sol'; -import {IGovernancePowerDelegationToken} from '../../src/interfaces/IGovernancePowerDelegationToken.sol'; -import {BaseAaveTokenV2} from './BaseAaveTokenV2Harness.sol'; - -contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { - - // Harness - uint256 public totalDelegatedPropositionBalance; - uint256 public totalDelegatedVotingBalance; - // End Harness - - mapping(address => address) internal _votingDelegateeV2; - mapping(address => address) internal _propositionDelegateeV2; - - /// @dev we assume that for the governance system 18 decimals of precision is not needed, - // by this constant we reduce it by 10, to 8 decimals - uint256 public constant POWER_SCALE_FACTOR = 1e10; - - bytes32 public constant DELEGATE_BY_TYPE_TYPEHASH = - keccak256( - 'DelegateByType(address delegator,address delegatee,GovernancePowerType delegationType,uint256 nonce,uint256 deadline)' - ); - bytes32 public constant DELEGATE_TYPEHASH = - keccak256('Delegate(address delegator,address delegatee,uint256 nonce,uint256 deadline)'); - - /// @inheritdoc IGovernancePowerDelegationToken - function delegateByType(address delegatee, GovernancePowerType delegationType) - external - virtual - override - { - _delegateByType(msg.sender, delegatee, delegationType); - } - - /// @inheritdoc IGovernancePowerDelegationToken - function delegate(address delegatee) external override { - _delegateByType(msg.sender, delegatee, GovernancePowerType.VOTING); - _delegateByType(msg.sender, delegatee, GovernancePowerType.PROPOSITION); - } - - /// @inheritdoc IGovernancePowerDelegationToken - function getDelegateeByType(address delegator, GovernancePowerType delegationType) - external - view - override - returns (address) - { - return _getDelegateeByType(delegator, _balances[delegator], delegationType); - } - - /// @inheritdoc IGovernancePowerDelegationToken - function getDelegates(address delegator) external view override returns (address, address) { - DelegationAwareBalance memory delegatorBalance = _balances[delegator]; - return ( - _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.VOTING), - _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.PROPOSITION) - ); - } - - /// @inheritdoc IGovernancePowerDelegationToken - function getPowerCurrent(address user, GovernancePowerType delegationType) - public - view - override - returns (uint256) - { - DelegationAwareBalance memory userState = _balances[user]; - uint256 userOwnPower = uint8(userState.delegationState) & (uint8(delegationType) + 1) == 0 - ? _balances[user].balance - : 0; - uint256 userDelegatedPower = _getDelegatedPowerByType(userState, delegationType); - return userOwnPower + userDelegatedPower; - } - - /// @inheritdoc IGovernancePowerDelegationToken - function getPowersCurrent(address user) external view override returns (uint256, uint256) { - return ( - getPowerCurrent(user, GovernancePowerType.VOTING), - getPowerCurrent(user, GovernancePowerType.PROPOSITION) - ); - } - - /// @inheritdoc IGovernancePowerDelegationToken - function metaDelegateByType( - address delegator, - address delegatee, - GovernancePowerType delegationType, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external override { - require(delegator != address(0), 'INVALID_OWNER'); - //solium-disable-next-line - require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); - uint256 currentValidNonce = _nonces[delegator]; - bytes32 digest = keccak256( - abi.encodePacked( - '\x19\x01', - DOMAIN_SEPARATOR, - keccak256( - abi.encode( - DELEGATE_BY_TYPE_TYPEHASH, - delegator, - delegatee, - delegationType, - currentValidNonce, - deadline - ) - ) - ) - ); - - require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); - unchecked { - // Does not make sense to check because it's not realistic to reach uint256.max in nonce - _nonces[delegator] = currentValidNonce + 1; - } - _delegateByType(delegator, delegatee, delegationType); - } - - /// @inheritdoc IGovernancePowerDelegationToken - function metaDelegate( - address delegator, - address delegatee, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external override { - require(delegator != address(0), 'INVALID_OWNER'); - //solium-disable-next-line - require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); - uint256 currentValidNonce = _nonces[delegator]; - bytes32 digest = keccak256( - abi.encodePacked( - '\x19\x01', - DOMAIN_SEPARATOR, - keccak256(abi.encode(DELEGATE_TYPEHASH, delegator, delegatee, currentValidNonce, deadline)) - ) - ); - - require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); - unchecked { - // does not make sense to check because it's not realistic to reach uint256.max in nonce - _nonces[delegator] = currentValidNonce + 1; - } - _delegateByType(delegator, delegatee, GovernancePowerType.VOTING); - _delegateByType(delegator, delegatee, GovernancePowerType.PROPOSITION); - } - - /** - * @dev Modifies the delegated power of a `delegatee` account by type (VOTING, PROPOSITION). - * Passing the impact on the delegation of `delegatee` account before and after to reduce conditionals and not lose - * any precision. - * @param impactOnDelegationBefore how much impact a balance of another account had over the delegation of a `delegatee` - * before an action. - * For example, if the action is a delegation from one account to another, the impact before the action will be 0. - * @param impactOnDelegationAfter how much impact a balance of another account will have over the delegation of a `delegatee` - * after an action. - * For example, if the action is a delegation from one account to another, the impact after the action will be the whole balance - * of the account changing the delegatee. - * @param delegatee the user whom delegated governance power will be changed - * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) - **/ - function _governancePowerTransferByType( - uint104 impactOnDelegationBefore, - uint104 impactOnDelegationAfter, - address delegatee, - GovernancePowerType delegationType - ) internal { - if (delegatee == address(0)) return; - if (impactOnDelegationBefore == impactOnDelegationAfter) return; - - // To make delegated balance fit into uint72 we're decreasing precision of delegated balance by POWER_SCALE_FACTOR - uint72 impactOnDelegationBefore72 = uint72(impactOnDelegationBefore / POWER_SCALE_FACTOR); - uint72 impactOnDelegationAfter72 = uint72(impactOnDelegationAfter / POWER_SCALE_FACTOR); - - uint72 temp; - if (delegationType == GovernancePowerType.VOTING) { - temp = _balances[delegatee].delegatedVotingBalance; - _balances[delegatee].delegatedVotingBalance = - _balances[delegatee].delegatedVotingBalance - - impactOnDelegationBefore72 + - impactOnDelegationAfter72; - totalDelegatedVotingBalance = totalDelegatedVotingBalance - temp + - _balances[delegatee].delegatedVotingBalance; - } else { - temp = _balances[delegatee].delegatedPropositionBalance; - _balances[delegatee].delegatedPropositionBalance = - _balances[delegatee].delegatedPropositionBalance - - impactOnDelegationBefore72 + - impactOnDelegationAfter72; - totalDelegatedPropositionBalance = totalDelegatedPropositionBalance - temp + - _balances[delegatee].delegatedPropositionBalance; - } - } - - /** - * @dev performs all state changes related to balance transfer and corresponding delegation changes - * @param from token sender - * @param to token recipient - * @param amount amount of tokens sent - **/ - function _transferWithDelegation( - address from, - address to, - uint256 amount - ) internal override { - if (from == to) { - return; - } - - if (from != address(0)) { - DelegationAwareBalance memory fromUserState = _balances[from]; - require(fromUserState.balance >= amount, 'ERC20: transfer amount exceeds balance'); - - uint104 fromBalanceAfter; - unchecked { - fromBalanceAfter = fromUserState.balance - uint104(amount); - } - _balances[from].balance = fromBalanceAfter; - if (fromUserState.delegationState != DelegationState.NO_DELEGATION) { - _governancePowerTransferByType( - fromUserState.balance, - fromBalanceAfter, - _getDelegateeByType(from, fromUserState, GovernancePowerType.VOTING), - GovernancePowerType.VOTING - ); - _governancePowerTransferByType( - fromUserState.balance, - fromBalanceAfter, - _getDelegateeByType(from, fromUserState, GovernancePowerType.PROPOSITION), - GovernancePowerType.PROPOSITION - ); - } - } - - if (to != address(0)) { - DelegationAwareBalance memory toUserState = _balances[to]; - uint104 toBalanceBefore = toUserState.balance; - toUserState.balance = toBalanceBefore + uint104(amount); - _balances[to] = toUserState; - - if (toUserState.delegationState != DelegationState.NO_DELEGATION) { - _governancePowerTransferByType( - toBalanceBefore, - toUserState.balance, - _getDelegateeByType(to, toUserState, GovernancePowerType.VOTING), - GovernancePowerType.VOTING - ); - _governancePowerTransferByType( - toBalanceBefore, - toUserState.balance, - _getDelegateeByType(to, toUserState, GovernancePowerType.PROPOSITION), - GovernancePowerType.PROPOSITION - ); - } - } - } - - /** - * @dev Extracts from state and returns delegated governance power (Voting, Proposition) - * @param userState the current state of a user - * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) - **/ - function _getDelegatedPowerByType( - DelegationAwareBalance memory userState, - GovernancePowerType delegationType - ) internal pure returns (uint256) { - return - POWER_SCALE_FACTOR * - ( - delegationType == GovernancePowerType.VOTING - ? userState.delegatedVotingBalance - : userState.delegatedPropositionBalance - ); - } - - /** - * @dev Extracts from state and returns the delegatee of a delegator by type of governance power (Voting, Proposition) - * - If the delegator doesn't have any delegatee, returns address(0) - * @param delegator delegator - * @param userState the current state of a user - * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) - **/ - function _getDelegateeByType( - address delegator, - DelegationAwareBalance memory userState, - GovernancePowerType delegationType - ) internal view returns (address) { - if (delegationType == GovernancePowerType.VOTING) { - return - /// With the & operation, we cover both VOTING_DELEGATED delegation and FULL_POWER_DELEGATED - /// as VOTING_DELEGATED is equivalent to 01 in binary and FULL_POWER_DELEGATED is equivalent to 11 - (uint8(userState.delegationState) & uint8(DelegationState.VOTING_DELEGATED)) != 0 - ? _votingDelegateeV2[delegator] - : address(0); - } - return - userState.delegationState >= DelegationState.PROPOSITION_DELEGATED - ? _propositionDelegateeV2[delegator] - : address(0); - } - - /** - * @dev Changes user's delegatee address by type of governance power (Voting, Proposition) - * @param delegator delegator - * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) - * @param _newDelegatee the new delegatee - **/ - function _updateDelegateeByType( - address delegator, - GovernancePowerType delegationType, - address _newDelegatee - ) internal { - address newDelegatee = _newDelegatee == delegator ? address(0) : _newDelegatee; - if (delegationType == GovernancePowerType.VOTING) { - _votingDelegateeV2[delegator] = newDelegatee; - } else { - _propositionDelegateeV2[delegator] = newDelegatee; - } - } - - /** - * @dev Updates the specific flag which signaling about existence of delegation of governance power (Voting, Proposition) - * @param userState a user state to change - * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) - * @param willDelegate next state of delegation - **/ - function _updateDelegationFlagByType( - DelegationAwareBalance memory userState, - GovernancePowerType delegationType, - bool willDelegate - ) internal pure returns (DelegationAwareBalance memory) { - if (willDelegate) { - // Because GovernancePowerType starts from 0, we should add 1 first, then we apply bitwise OR - userState.delegationState = DelegationState( - uint8(userState.delegationState) | (uint8(delegationType) + 1) - ); - } else { - // First bitwise NEGATION, ie was 01, after XOR with 11 will be 10, - // then bitwise AND, which means it will keep only another delegation type if it exists - userState.delegationState = DelegationState( - uint8(userState.delegationState) & - ((uint8(delegationType) + 1) ^ uint8(DelegationState.FULL_POWER_DELEGATED)) - ); - } - return userState; - } - - /** - * @dev This is the equivalent of an ERC20 transfer(), but for a power type: an atomic transfer of a balance (power). - * When needed, it decreases the power of the `delegator` and when needed, it increases the power of the `delegatee` - * @param delegator delegator - * @param _delegatee the user which delegated power will change - * @param delegationType the type of delegation (VOTING, PROPOSITION) - **/ - function _delegateByType( - address delegator, - address _delegatee, - GovernancePowerType delegationType - ) internal { - // Here we unify the property that delegating power to address(0) == delegating power to yourself == no delegation - // So from now on, not being delegating is (exclusively) that delegatee == address(0) - address delegatee = _delegatee == delegator ? address(0) : _delegatee; - - // We read the whole struct before validating delegatee, because in the optimistic case - // (_delegatee != currentDelegatee) we will reuse userState in the rest of the function - DelegationAwareBalance memory delegatorState = _balances[delegator]; - address currentDelegatee = _getDelegateeByType(delegator, delegatorState, delegationType); - if (delegatee == currentDelegatee) return; - - bool delegatingNow = currentDelegatee != address(0); - bool willDelegateAfter = delegatee != address(0); - - if (delegatingNow) { - _governancePowerTransferByType(delegatorState.balance, 0, currentDelegatee, delegationType); - } - - if (willDelegateAfter) { - _governancePowerTransferByType(0, delegatorState.balance, delegatee, delegationType); - } - - _updateDelegateeByType(delegator, delegationType, delegatee); - - if (willDelegateAfter != delegatingNow) { - _balances[delegator] = _updateDelegationFlagByType( - delegatorState, - delegationType, - willDelegateAfter - ); - } - - emit DelegateChanged(delegator, delegatee, delegationType); - } - - /** - Harness section - replace struct reads and writes with function calls - */ - - - function getBalance(address user) view public returns (uint104) { - return _balances[user].balance; - } - - function getDelegatedPropositionBalance(address user) view public returns (uint72) { - return _balances[user].delegatedPropositionBalance; - } - - - function getDelegatedVotingBalance(address user) view public returns (uint72) { - return _balances[user].delegatedVotingBalance; - } - - - function getDelegatingProposition(address user) view public returns (bool) { - return _balances[user].delegationState == DelegationState.PROPOSITION_DELEGATED || - _balances[user].delegationState == DelegationState.FULL_POWER_DELEGATED; - } - - - function getDelegatingVoting(address user) view public returns (bool) { - return _balances[user].delegationState == DelegationState.VOTING_DELEGATED || - _balances[user].delegationState == DelegationState.FULL_POWER_DELEGATED; - } - - function getVotingDelegate(address user) view public returns (address) { - return _votingDelegateeV2[user]; - } - - function getPropositionDelegate(address user) view public returns (address) { - return _propositionDelegateeV2[user]; - } - - function getDelegationState(address user) view public returns (DelegationState) { - return _balances[user].delegationState; - } - - - - /** - End of harness section - */ -} diff --git a/certora/harness/AaveTokenV3HarnessNew.sol b/certora/harness/AaveTokenV3HarnessNew.sol new file mode 100644 index 0000000..939d691 --- /dev/null +++ b/certora/harness/AaveTokenV3HarnessNew.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveTokenV3} from "../../src/AaveTokenV3.sol"; + +contract AaveTokenV3Harness is AaveTokenV3 { + function getBalance(address user) view public returns (uint104) { + return _balances[user].balance; + } + + function getDelegatedPropositionBalance(address user) view public returns (uint72) { + return _balances[user].delegatedPropositionBalance; + } + + + function getDelegatedVotingBalance(address user) view public returns (uint72) { + return _balances[user].delegatedVotingBalance; + } + + + function getDelegatingProposition(address user) view public returns (bool) { + return _balances[user].delegationState == DelegationState.PROPOSITION_DELEGATED || + _balances[user].delegationState == DelegationState.FULL_POWER_DELEGATED; + } + + + function getDelegatingVoting(address user) view public returns (bool) { + return _balances[user].delegationState == DelegationState.VOTING_DELEGATED || + _balances[user].delegationState == DelegationState.FULL_POWER_DELEGATED; + } + + function getVotingDelegate(address user) view public returns (address) { + return _votingDelegateeV2[user]; + } + + function getPropositionDelegate(address user) view public returns (address) { + return _propositionDelegateeV2[user]; + } + + function getDelegationState(address user) view public returns (DelegationState) { + return _balances[user].delegationState; + } +} \ No newline at end of file diff --git a/certora/harness/BaseAaveTokenHarness.sol b/certora/harness/BaseAaveTokenHarness.sol deleted file mode 100644 index bcc17fa..0000000 --- a/certora/harness/BaseAaveTokenHarness.sol +++ /dev/null @@ -1,161 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {Context} from '../../lib/openzeppelin-contracts/contracts/utils/Context.sol'; -import {IERC20} from '../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; -import {IERC20Metadata} from '../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; - -// Inspired by OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) -abstract contract BaseAaveToken is Context, IERC20Metadata { - enum DelegationState { - NO_DELEGATION, - VOTING_DELEGATED, - PROPOSITION_DELEGATED, - FULL_POWER_DELEGATED - } - - struct DelegationAwareBalance { - uint104 balance; - uint72 delegatedPropositionBalance; - uint72 delegatedVotingBalance; - DelegationState delegationState; - } - - mapping(address => DelegationAwareBalance) public _balances; - - mapping(address => mapping(address => uint256)) public _allowances; - - uint256 internal _totalSupply; - - string internal _name; - string internal _symbol; - - // @dev DEPRECATED - // kept for backwards compatibility with old storage layout - uint8 private ______DEPRECATED_OLD_ERC20_DECIMALS; - - /** - * @dev Returns the name of the token. - */ - function name() public view virtual override returns (string memory) { - return _name; - } - - /** - * @dev Returns the symbol of the token, usually a shorter version of the - * name. - */ - function symbol() public view virtual override returns (string memory) { - return _symbol; - } - - function decimals() public view virtual override returns (uint8) { - return 18; - } - - function totalSupply() public view virtual override returns (uint256) { - return _totalSupply; - } - - function balanceOf(address account) public view virtual override returns (uint256) { - return _balances[account].balance; - } - - function transfer(address to, uint256 amount) public virtual override returns (bool) { - address owner = _msgSender(); - _transfer(owner, to, amount); - return true; - } - - function allowance(address owner, address spender) - public - view - virtual - override - returns (uint256) - { - return _allowances[owner][spender]; - } - - function approve(address spender, uint256 amount) public virtual override returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, amount); - return true; - } - - function transferFrom( - address from, - address to, - uint256 amount - ) public virtual override returns (bool) { - address spender = _msgSender(); - _spendAllowance(from, spender, amount); - _transfer(from, to, amount); - return true; - } - - function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, _allowances[owner][spender] + addedValue); - return true; - } - - function decreaseAllowance(address spender, uint256 subtractedValue) - public - virtual - returns (bool) - { - address owner = _msgSender(); - uint256 currentAllowance = _allowances[owner][spender]; - require(currentAllowance >= subtractedValue, 'ERC20: decreased allowance below zero'); - unchecked { - _approve(owner, spender, currentAllowance - subtractedValue); - } - - return true; - } - - function _transfer( - address from, - address to, - uint256 amount - ) internal virtual { - require(from != address(0), 'ERC20: transfer from the zero address'); - require(to != address(0), 'ERC20: transfer to the zero address'); - - _transferWithDelegation(from, to, amount); - emit Transfer(from, to, amount); - } - - function _approve( - address owner, - address spender, - uint256 amount - ) internal virtual { - require(owner != address(0), 'ERC20: approve from the zero address'); - require(spender != address(0), 'ERC20: approve to the zero address'); - - _allowances[owner][spender] = amount; - emit Approval(owner, spender, amount); - } - - function _spendAllowance( - address owner, - address spender, - uint256 amount - ) internal virtual { - uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance != type(uint256).max) { - require(currentAllowance >= amount, 'ERC20: insufficient allowance'); - unchecked { - _approve(owner, spender, currentAllowance - amount); - } - } - } - - function _transferWithDelegation( - address from, - address to, - uint256 amount - ) internal virtual {} -} diff --git a/certora/harness/BaseAaveTokenV2Harness.sol b/certora/harness/BaseAaveTokenV2Harness.sol deleted file mode 100644 index 7904063..0000000 --- a/certora/harness/BaseAaveTokenV2Harness.sol +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {VersionedInitializable} from '../../src/utils/VersionedInitializable.sol'; -import {BaseAaveToken} from './BaseAaveTokenHarness.sol'; - -abstract contract BaseAaveTokenV2 is BaseAaveToken, VersionedInitializable { - /// @dev owner => next valid nonce to submit with permit() - mapping(address => uint256) public _nonces; - - ///////// @dev DEPRECATED from AaveToken v1 ////////////////////////// - //////// kept for backwards compatibility with old storage layout //// - uint256[3] private ______DEPRECATED_FROM_AAVE_V1; - ///////// @dev END OF DEPRECATED from AaveToken v1 ////////////////////////// - - bytes32 public DOMAIN_SEPARATOR; - - ///////// @dev DEPRECATED from AaveToken v2 ////////////////////////// - //////// kept for backwards compatibility with old storage layout //// - uint256[4] private ______DEPRECATED_FROM_AAVE_V2; - ///////// @dev END OF DEPRECATED from AaveToken v2 ////////////////////////// - - bytes public constant EIP712_REVISION = bytes('1'); - bytes32 internal constant EIP712_DOMAIN = - keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'); - bytes32 public constant PERMIT_TYPEHASH = - keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'); - - uint256 public constant REVISION = 3; - - /** - * @dev initializes the contract upon assignment to the InitializableAdminUpgradeabilityProxy - */ - function initialize() external initializer {} - - /** - * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md - * @param owner the owner of the funds - * @param spender the spender - * @param value the amount - * @param deadline the deadline timestamp, type(uint256).max for no deadline - * @param v signature param - * @param s signature param - * @param r signature param - */ - - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external { - require(owner != address(0), 'INVALID_OWNER'); - //solium-disable-next-line - require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); - uint256 currentValidNonce = _nonces[owner]; - bytes32 digest = keccak256( - abi.encodePacked( - '\x19\x01', - DOMAIN_SEPARATOR, - keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline)) - ) - ); - - require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); - unchecked { - // does not make sense to check because it's not realistic to reach uint256.max in nonce - _nonces[owner] = currentValidNonce + 1; - } - _approve(owner, spender, value); - } - - /** - * @dev returns the revision of the implementation contract - */ - function getRevision() internal pure override returns (uint256) { - return REVISION; - } -} diff --git a/certora/scripts/verifyDelegate.sh b/certora/scripts/verifyDelegate.sh index a138b20..2e0b66d 100755 --- a/certora/scripts/verifyDelegate.sh +++ b/certora/scripts/verifyDelegate.sh @@ -3,8 +3,8 @@ then RULE="--rule $1" fi -certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ - --verify AaveTokenV3:certora/specs/delegate.spec \ +certoraRun certora/harness/AaveTokenV3HarnessNew.sol:AaveTokenV3Harness \ + --verify AaveTokenV3Harness:certora/specs/delegate.spec \ $RULE \ --solc solc8.13 \ --optimistic_loop \ From 0fafd3423b7962f9af86649ab0beb49c728e36d0 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Thu, 28 Jul 2022 11:25:57 +0300 Subject: [PATCH 27/58] refactor harness --- .../{AaveTokenV3HarnessNew.sol => AaveTokenV3Harness.sol} | 0 certora/scripts/verifyDelegate.sh | 4 ++-- certora/specs/delegate.spec | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) rename certora/harness/{AaveTokenV3HarnessNew.sol => AaveTokenV3Harness.sol} (100%) diff --git a/certora/harness/AaveTokenV3HarnessNew.sol b/certora/harness/AaveTokenV3Harness.sol similarity index 100% rename from certora/harness/AaveTokenV3HarnessNew.sol rename to certora/harness/AaveTokenV3Harness.sol diff --git a/certora/scripts/verifyDelegate.sh b/certora/scripts/verifyDelegate.sh index 2e0b66d..48d5059 100755 --- a/certora/scripts/verifyDelegate.sh +++ b/certora/scripts/verifyDelegate.sh @@ -3,12 +3,12 @@ then RULE="--rule $1" fi -certoraRun certora/harness/AaveTokenV3HarnessNew.sol:AaveTokenV3Harness \ +certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3Harness \ --verify AaveTokenV3Harness:certora/specs/delegate.spec \ $RULE \ --solc solc8.13 \ --optimistic_loop \ --send_only \ --staging \ - --msg "AaveTokenV3:bgdSpec.spec $1" + --msg "AaveTokenV3Harness:delegate.spec $1" \ No newline at end of file diff --git a/certora/specs/delegate.spec b/certora/specs/delegate.spec index ba1708a..2564384 100644 --- a/certora/specs/delegate.spec +++ b/certora/specs/delegate.spec @@ -705,4 +705,5 @@ rule delegationTypeIndependence(address who, method f) filtered { f -> !f.isView f.selector == delegate(address).selector || f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector ), "one delegatee type stays the same, unless delegate or delegateBySig was called"; -} \ No newline at end of file +} + From 3f73a329701425251c2efe98942c025fae5a516e Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Thu, 28 Jul 2022 11:34:17 +0300 Subject: [PATCH 28/58] update AaveTokenV3 from main --- certora/scripts/run.sh | 7 +++++++ src/AaveTokenV3.sol | 47 +++++++++++++++++++++++------------------- 2 files changed, 33 insertions(+), 21 deletions(-) create mode 100755 certora/scripts/run.sh diff --git a/certora/scripts/run.sh b/certora/scripts/run.sh new file mode 100755 index 0000000..03cffd3 --- /dev/null +++ b/certora/scripts/run.sh @@ -0,0 +1,7 @@ +certoraRun src/AaveTokenV3.sol:AaveTokenV3 \ + --verify AaveTokenV3:certora/specs/complexity.spec \ + --solc solc8.13 \ + --optimistic_loop \ + --staging \ + --msg "AaveTokenV3 complexity check" + \ No newline at end of file diff --git a/src/AaveTokenV3.sol b/src/AaveTokenV3.sol index 94c5a46..aac67ee 100644 --- a/src/AaveTokenV3.sol +++ b/src/AaveTokenV3.sol @@ -6,14 +6,12 @@ import {IGovernancePowerDelegationToken} from './interfaces/IGovernancePowerDele import {BaseAaveTokenV2} from './BaseAaveTokenV2.sol'; contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { - - mapping(address => address) internal _votingDelegateeV2; mapping(address => address) internal _propositionDelegateeV2; - // @dev we assume that for the governance system 18 decimals of precision is not needed, + /// @dev we assume that for the governance system 18 decimals of precision is not needed, // by this constant we reduce it by 10, to 8 decimals - uint256 public constant DELEGATOR_POWER_SCALE_FACTOR = 1e10; + uint256 public constant POWER_SCALE_FACTOR = 1e10; bytes32 public constant DELEGATE_BY_TYPE_TYPEHASH = keccak256( @@ -149,35 +147,42 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { } /** - * @dev Changing one of delegated governance powers of delegatee depending on the delegator balance change - * @param delegatorBalanceBefore delegator balance before operation - * @param delegatorBalanceAfter delegator balance after operation + * @dev Modifies the delegated power of a `delegatee` account by type (VOTING, PROPOSITION). + * Passing the impact on the delegation of `delegatee` account before and after to reduce conditionals and not lose + * any precision. + * @param impactOnDelegationBefore how much impact a balance of another account had over the delegation of a `delegatee` + * before an action. + * For example, if the action is a delegation from one account to another, the impact before the action will be 0. + * @param impactOnDelegationAfter how much impact a balance of another account will have over the delegation of a `delegatee` + * after an action. + * For example, if the action is a delegation from one account to another, the impact after the action will be the whole balance + * of the account changing the delegatee. * @param delegatee the user whom delegated governance power will be changed * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) **/ function _governancePowerTransferByType( - uint104 delegatorBalanceBefore, - uint104 delegatorBalanceAfter, + uint104 impactOnDelegationBefore, + uint104 impactOnDelegationAfter, address delegatee, GovernancePowerType delegationType ) internal { if (delegatee == address(0)) return; - if (delegatorBalanceBefore == delegatorBalanceAfter) return; + if (impactOnDelegationBefore == impactOnDelegationAfter) return; - // To make delegated balance fit into uint72 we're decreasing precision of delegated balance by DELEGATOR_POWER_SCALE_FACTOR - uint72 delegatorBalanceBefore72 = uint72(delegatorBalanceBefore / DELEGATOR_POWER_SCALE_FACTOR); - uint72 delegatorBalanceAfter72 = uint72(delegatorBalanceAfter / DELEGATOR_POWER_SCALE_FACTOR); + // To make delegated balance fit into uint72 we're decreasing precision of delegated balance by POWER_SCALE_FACTOR + uint72 impactOnDelegationBefore72 = uint72(impactOnDelegationBefore / POWER_SCALE_FACTOR); + uint72 impactOnDelegationAfter72 = uint72(impactOnDelegationAfter / POWER_SCALE_FACTOR); if (delegationType == GovernancePowerType.VOTING) { _balances[delegatee].delegatedVotingBalance = _balances[delegatee].delegatedVotingBalance - - delegatorBalanceBefore72 + - delegatorBalanceAfter72; + impactOnDelegationBefore72 + + impactOnDelegationAfter72; } else { _balances[delegatee].delegatedPropositionBalance = _balances[delegatee].delegatedPropositionBalance - - delegatorBalanceBefore72 + - delegatorBalanceAfter72; + impactOnDelegationBefore72 + + impactOnDelegationAfter72; } } @@ -229,14 +234,14 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { if (toUserState.delegationState != DelegationState.NO_DELEGATION) { _governancePowerTransferByType( - toUserState.balance, toBalanceBefore, + toUserState.balance, _getDelegateeByType(to, toUserState, GovernancePowerType.VOTING), GovernancePowerType.VOTING ); _governancePowerTransferByType( - toUserState.balance, toBalanceBefore, + toUserState.balance, _getDelegateeByType(to, toUserState, GovernancePowerType.PROPOSITION), GovernancePowerType.PROPOSITION ); @@ -254,7 +259,7 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { GovernancePowerType delegationType ) internal pure returns (uint256) { return - DELEGATOR_POWER_SCALE_FACTOR * + POWER_SCALE_FACTOR * ( delegationType == GovernancePowerType.VOTING ? userState.delegatedVotingBalance @@ -379,4 +384,4 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { emit DelegateChanged(delegator, delegatee, delegationType); } -} +} \ No newline at end of file From b937ed40d01037b70f87baac431592afb4be80c2 Mon Sep 17 00:00:00 2001 From: Yura Date: Fri, 29 Jul 2022 11:31:28 +0300 Subject: [PATCH 29/58] gitignore --- .gitignore | 2 + package-lock.json | 289 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 291 insertions(+) create mode 100644 package-lock.json diff --git a/.gitignore b/.gitignore index 4f0ad61..ca9a9a1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ out/ .certora_config/ .last_confs/ .certora_* +node_modules/ +.env diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..76bdc76 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,289 @@ +{ + "name": "aave-token-v3", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "devDependencies": { + "prettier": "^2.7.1", + "prettier-plugin-solidity": "^1.0.0-beta.19" + } + }, + "node_modules/@solidity-parser/parser": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.3.tgz", + "integrity": "sha512-29g2SZ29HtsqA58pLCtopI1P/cPy5/UAzlcAXO6T/CNJimG6yA8kx4NaseMyJULiC+TEs02Y9/yeHzClqoA0hw==", + "dev": true, + "dependencies": { + "antlr4ts": "^0.5.0-alpha.4" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/antlr4ts": { + "version": "0.5.0-alpha.4", + "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", + "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.1.0.tgz", + "integrity": "sha512-xAEnNCT3w2Tg6MA7ly6QqYJvEoY1tm9iIjJ3yMKK9JPlWuRHAMoe5iETwQnx3M9TVbFMfsrBgWKR+IsmswwNjg==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-solidity": { + "version": "1.0.0-dev.23", + "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.0.0-dev.23.tgz", + "integrity": "sha512-440/jZzvtDJcqtoRCQiigo1DYTPAZ85pjNg7gvdd+Lds6QYgID8RyOdygmudzHdFmV2UfENt//A8tzx7iS58GA==", + "dev": true, + "dependencies": { + "@solidity-parser/parser": "^0.14.3", + "emoji-regex": "^10.1.0", + "escape-string-regexp": "^4.0.0", + "semver": "^7.3.7", + "solidity-comments-extractor": "^0.0.7", + "string-width": "^4.2.3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "prettier": "^2.3.0" + } + }, + "node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/solidity-comments-extractor": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz", + "integrity": "sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw==", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + }, + "dependencies": { + "@solidity-parser/parser": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.3.tgz", + "integrity": "sha512-29g2SZ29HtsqA58pLCtopI1P/cPy5/UAzlcAXO6T/CNJimG6yA8kx4NaseMyJULiC+TEs02Y9/yeHzClqoA0hw==", + "dev": true, + "requires": { + "antlr4ts": "^0.5.0-alpha.4" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "antlr4ts": { + "version": "0.5.0-alpha.4", + "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", + "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", + "dev": true + }, + "emoji-regex": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.1.0.tgz", + "integrity": "sha512-xAEnNCT3w2Tg6MA7ly6QqYJvEoY1tm9iIjJ3yMKK9JPlWuRHAMoe5iETwQnx3M9TVbFMfsrBgWKR+IsmswwNjg==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true + }, + "prettier-plugin-solidity": { + "version": "1.0.0-dev.23", + "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.0.0-dev.23.tgz", + "integrity": "sha512-440/jZzvtDJcqtoRCQiigo1DYTPAZ85pjNg7gvdd+Lds6QYgID8RyOdygmudzHdFmV2UfENt//A8tzx7iS58GA==", + "dev": true, + "requires": { + "@solidity-parser/parser": "^0.14.3", + "emoji-regex": "^10.1.0", + "escape-string-regexp": "^4.0.0", + "semver": "^7.3.7", + "solidity-comments-extractor": "^0.0.7", + "string-width": "^4.2.3" + } + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "solidity-comments-extractor": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz", + "integrity": "sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + } + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } +} From 097b44f3299a8b68fd3a8f5e81b17f039d3b41cf Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Sun, 31 Jul 2022 11:28:37 +0300 Subject: [PATCH 30/58] install test --- certora/scripts/erc20.sh | 4 ++-- certora/specs/erc20.spec | 32 +++++++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/certora/scripts/erc20.sh b/certora/scripts/erc20.sh index ceeef50..9f0aeac 100644 --- a/certora/scripts/erc20.sh +++ b/certora/scripts/erc20.sh @@ -3,8 +3,8 @@ then RULE="--rule $1" fi -certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ - --verify AaveTokenV3:certora/specs/erc20.spec \ +certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3Harness \ + --verify AaveTokenV3Harness:certora/specs/erc20.spec \ $RULE \ --solc solc8.13 \ --optimistic_loop \ diff --git a/certora/specs/erc20.spec b/certora/specs/erc20.spec index eeefd2f..ca7dfd4 100644 --- a/certora/specs/erc20.spec +++ b/certora/specs/erc20.spec @@ -5,6 +5,8 @@ function doesntChangeBalance(method f) returns bool { f.selector != transferFrom(address,address,uint256).selector; } + + /* @Rule @@ -110,19 +112,43 @@ rule noFeeOnTransfer(address bob, uint256 amount) { @Link: */ + + +// for this rule need to prove that delegatedBalance of a delegate is >= any delegator balance + +// what if v_delegateFrom == v_delegateTo? + rule transferCorrect(address to, uint256 amount) { env e; require e.msg.value == 0 && e.msg.sender != 0; uint256 fromBalanceBefore = balanceOf(e.msg.sender); uint256 toBalanceBefore = balanceOf(to); - require fromBalanceBefore + toBalanceBefore < AAVE_MAX_SUPPLY(); + require fromBalanceBefore + toBalanceBefore < AAVE_MAX_SUPPLY() / 100; + // proven elsewhere address v_delegateTo = getVotingDelegate(to); - require getDelegatedVotingBalance(v_delegateTo) < SCALED_MAX_SUPPLY() - amount / DELEGATED_POWER_DIVIDER(); + mathint dvbTo = getDelegatedVotingBalance(v_delegateTo); + require dvbTo >= balanceOf(to) / DELEGATED_POWER_DIVIDER() && + dvbTo < SCALED_MAX_SUPPLY() - amount / DELEGATED_POWER_DIVIDER(); address p_delegateTo = getPropositionDelegate(to); - require getDelegatedPropositionBalance(p_delegateTo) < SCALED_MAX_SUPPLY() - amount / DELEGATED_POWER_DIVIDER(); + mathint pvbTo = getDelegatedPropositionBalance(p_delegateTo); + require pvbTo >= balanceOf(to) / DELEGATED_POWER_DIVIDER() && + pvbTo < SCALED_MAX_SUPPLY() - amount / DELEGATED_POWER_DIVIDER(); + + // proven elsewhere + address v_delegateFrom = getVotingDelegate(e.msg.sender); + address p_delegateFrom = getPropositionDelegate(e.msg.sender); + mathint dvbFrom = getDelegatedVotingBalance(v_delegateFrom); + mathint pvbFrom = getDelegatedPropositionBalance(p_delegateFrom); + require dvbFrom >= balanceOf(e.msg.sender) / DELEGATED_POWER_DIVIDER(); + require pvbFrom >= balanceOf(e.msg.sender) / DELEGATED_POWER_DIVIDER(); require validDelegationState(e.msg.sender) && validDelegationState(to); + require ! ( (getDelegatingVoting(to) && v_delegateTo == to) || + (getDelegatingProposition(to) && p_delegateTo == to)); + + // for testing this specific scenario + require v_delegateFrom == v_delegateTo && p_delegateFrom != p_delegateTo; transfer@withrevert(e, to, amount); bool reverted = lastReverted; From 429bcd8bb83c7c51c0beaa3497f6897afa00288c Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Sun, 31 Jul 2022 17:24:56 +0300 Subject: [PATCH 31/58] add delegation to zero invariant --- certora/scripts/verifyGeneral.sh | 5 +- certora/specs/erc20.spec | 56 +------- certora/specs/general.spec | 228 ++++++++++++++++--------------- lib/aave-token-v2 | 1 - lib/forge-std | 1 - 5 files changed, 121 insertions(+), 170 deletions(-) delete mode 160000 lib/aave-token-v2 delete mode 160000 lib/forge-std diff --git a/certora/scripts/verifyGeneral.sh b/certora/scripts/verifyGeneral.sh index fdb4ba1..1a03350 100644 --- a/certora/scripts/verifyGeneral.sh +++ b/certora/scripts/verifyGeneral.sh @@ -3,11 +3,12 @@ then RULE="--rule $1" fi -certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ - --verify AaveTokenV3:certora/specs/general.spec \ +certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3Harness \ + --verify AaveTokenV3Harness:certora/specs/general.spec \ $RULE \ --solc solc8.13 \ --optimistic_loop \ + --settings -smt_bitVectorTheory=true \ --send_only \ --staging \ --msg "AaveTokenV3:general.spec $1" diff --git a/certora/specs/erc20.spec b/certora/specs/erc20.spec index ca7dfd4..f017561 100644 --- a/certora/specs/erc20.spec +++ b/certora/specs/erc20.spec @@ -146,6 +146,8 @@ rule transferCorrect(address to, uint256 amount) { require validDelegationState(e.msg.sender) && validDelegationState(to); require ! ( (getDelegatingVoting(to) && v_delegateTo == to) || (getDelegatingProposition(to) && p_delegateTo == to)); + // to not overcomplicate the constraints on dvbTo and dvbFrom + require v_delegateFrom != v_delegateTo; // for testing this specific scenario require v_delegateFrom == v_delegateTo && p_delegateFrom != p_delegateTo; @@ -211,60 +213,6 @@ rule transferFromCorrect(address from, address to, uint256 amount) { allowance(from, e.msg.sender) == max_uint256); } -/* - @Rule - - @Description: - transferFrom should revert if and only if the amount is too high or the recipient is 0. - - @Formula: - { - allowanceBefore = allowance(alice, bob) - fromBalanceBefore = balanceOf(alice) - } - < - transferFrom(alice, bob, amount) - > - { - lastReverted <=> allowanceBefore < amount || amount > fromBalanceBefore || to = 0 - } - - @Notes: - Fails on tokens with pause/blacklist functions, like USDC. - - @Link: - -*/ -// TODO: fix rule failure -rule transferFromReverts(address from, address to, uint256 amount) { - env e; - uint256 allowanceBefore = allowance(from, e.msg.sender); - uint256 fromBalanceBefore = balanceOf(from); - require from != 0 && e.msg.sender != 0 && from != to; - require e.msg.value == 0; - require fromBalanceBefore + balanceOf(to) < AAVE_MAX_SUPPLY(); - require validDelegationState(from) && validDelegationState(to); - - uint256 votingFromDelegateBalance = getDelegatedVotingBalance(getVotingDelegate(from)); - uint256 propFromDelegateBalance = getDelegatedVotingBalance(getPropositionDelegate(from)); - - require votingFromDelegateBalance >= amount / DELEGATED_POWER_DIVIDER(); - require propFromDelegateBalance >= amount / DELEGATED_POWER_DIVIDER(); - - uint256 votingToDelegateBalance = getDelegatedVotingBalance(getVotingDelegate(to)); - uint256 propToDelegateBalance = getDelegatedVotingBalance(getPropositionDelegate(to)); - - require votingToDelegateBalance < SCALED_MAX_SUPPLY() - amount / DELEGATED_POWER_DIVIDER(); - address p_delegateTo = getPropositionDelegate(to); - require propToDelegateBalance < SCALED_MAX_SUPPLY() - amount / DELEGATED_POWER_DIVIDER(); - require validAmount(amount); - - transferFrom@withrevert(e, from, to, amount); - - assert lastReverted <=> (allowanceBefore < amount || amount > fromBalanceBefore || to == 0); -} - - /* @Rule diff --git a/certora/specs/general.spec b/certora/specs/general.spec index f60d6a1..d0ab6d7 100644 --- a/certora/specs/general.spec +++ b/certora/specs/general.spec @@ -1,9 +1,9 @@ import "base.spec" -methods { - totalDelegatedVotingBalance() returns (uint256) envfree - totalDelegatedPropositionBalance() returns (uint256) envfree -} +// methods { +// totalDelegatedVotingBalance() returns (uint256) envfree +// totalDelegatedPropositionBalance() returns (uint256) envfree +// } ghost mathint sumDelegatedProposition { init_state axiom sumDelegatedProposition == 0; @@ -16,119 +16,123 @@ ghost mathint sumBalances { /* update proposition balance on each store */ -hook Sstore _balances[KEY address user].delegatedPropositionBalance uint72 balance - (uint72 old_balance) STORAGE { - sumDelegatedProposition = - sumDelegatedProposition + to_mathint(balance) * DELEGATED_POWER_DIVIDER() - to_mathint(old_balance) * - DELEGATED_POWER_DIVIDER(); - } - -// try to rewrite using power.spec in aave-tokenv2 customer code -hook Sstore _balances[KEY address user].balance uint104 balance - (uint104 old_balance) STORAGE { - sumBalances = sumBalances + to_mathint(balance) - to_mathint(old_balance); - } - -hook Sload uint104 balance _balances[KEY address user].balance STORAGE { - require balance <= sumBalances; -} +// hook Sstore _balances[KEY address user].delegatedPropositionBalance uint72 balance +// (uint72 old_balance) STORAGE { +// sumDelegatedProposition = +// sumDelegatedProposition + to_mathint(balance) * DELEGATED_POWER_DIVIDER() - to_mathint(old_balance) * +// DELEGATED_POWER_DIVIDER(); +// } + +// // try to rewrite using power.spec in aave-tokenv2 customer code +// hook Sstore _balances[KEY address user].balance uint104 balance +// (uint104 old_balance) STORAGE { +// sumBalances = sumBalances + to_mathint(balance) - to_mathint(old_balance); +// } + +// hook Sload uint104 balance _balances[KEY address user].balance STORAGE { +// require balance <= sumBalances; +// } + +// hook Sload uint72 balance _balances[KEY address user].delegatedPropositionBalance STORAGE { +// require balance <= sumDelegatedProposition; +// } -hook Sload uint72 balance _balances[KEY address user].delegatedPropositionBalance STORAGE { - require balance <= sumDelegatedProposition; -} +// MUST: --bitwise operation -invariant sumDelegatedPropositionCorrectness() totalDelegatedPropositionBalance() <= totalSupply() - -invariant nonDelegatingBalance(address user) - !getDelegatingProposition(user) => balanceOf(user) == getDelegatedPropositionBalance(user) { - preserved transfer(address to, uint256 amount) with (env e) - { - require(getVotingDelegate(to) != user); - } - } - -rule sumDelegatedBalances(method f) { - env e; - calldataarg args; - - uint256 total = totalSupply(); - mathint sumBalancesGhost = sumBalances; - require totalSupply() <= AAVE_MAX_SUPPLY(); - // proved elsewhere - require sumBalances == totalSupply(); - uint256 sumBalancesBefore = totalDelegatedPropositionBalance(); - require sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost; - if (f.selector == delegate(address).selector) { - address delegatee; - uint256 balance = balanceOf(e.msg.sender); - bool isDelegating = getDelegatingProposition(e.msg.sender); - require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - balance; - delegate(e, delegatee); - } else if (f.selector == delegateByType(address,uint8).selector) { - address delegatee; - uint256 balance = balanceOf(e.msg.sender); - bool isDelegating = getDelegatingProposition(e.msg.sender); - require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - balance; - delegateByType(e, delegatee, PROPOSITION_POWER()); - } else if (f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector) { - address delegator; address delegatee; uint256 deadline; uint8 v; bytes32 r; bytes32 s; - uint256 balance = balanceOf(delegator); - bool isDelegating = getDelegatingProposition(delegator); - require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - balance; - metaDelegate(e, delegator, delegatee, deadline, v, r, s); - } else if (f.selector == metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32).selector) { - address delegator; address delegatee; uint256 deadline; uint8 v; bytes32 r; bytes32 s; - uint256 balance = balanceOf(delegator); - bool isDelegating = getDelegatingProposition(delegator); - require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - balance; - // metaDelegate by Proposition type (1) - metaDelegateByType(e, delegator, delegatee, PROPOSITION_POWER(), deadline, v, r, s); - } else if (f.selector == transfer(address,uint256).selector) { - address to; - uint256 amount; - uint256 balance = balanceOf(e.msg.sender); - require balance < AAVE_MAX_SUPPLY(); - bool isDelegating = getDelegatingProposition(e.msg.sender); - address toDelegate = getPropositionDelegate(to); - require toDelegate != 0; - uint256 toDelegatePower = getPowerCurrent(toDelegate, PROPOSITION_POWER()); - - require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - amount; - require isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() >= balance + toDelegatePower; - transfer(e, to, amount); - } else if (f.selector == transferFrom(address,address,uint256).selector) { - address to; - address from; - uint256 amount; - uint256 balanceFromBefore = balanceOf(from); - uint256 balanceToBefore = balanceOf(to); - require balanceFromBefore <= AAVE_MAX_SUPPLY(); - bool isDelegating = getDelegatingProposition(from); - address delegateFrom = getPropositionDelegate(from); - uint256 votingPowerFromDelegateBefore = getPowerCurrent(delegateFrom, PROPOSITION_POWER()); - address delegateTo = getPropositionDelegate(to); - uint256 votingPowerToDelegateBefore = getPowerCurrent(delegateTo, PROPOSITION_POWER()); - require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - amount; - require isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() >= balanceFromBefore; - transferFrom(e, from, to, amount); - uint256 balanceFromAfter = balanceOf(from); - uint256 balanceToAfter = balanceOf(to); - uint256 votingPowerFromDelegateAfter = getPowerCurrent(delegateFrom, PROPOSITION_POWER()); - uint256 votingPowerToDelegateAfter = getPowerCurrent(delegateTo, PROPOSITION_POWER()); - uint256 normAmount = normalize(amount); - uint256 votingPowerDelta = votingPowerToDelegateAfter - votingPowerToDelegateBefore; - assert isDelegating => votingPowerDelta == normAmount || delegateFrom == delegateTo; - } - else { - f(e, args); - } - uint256 sumBalancesAfter = totalDelegatedPropositionBalance(); - assert sumBalancesAfter * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost; +invariant delegateCorrectness(address user) + (getVotingDelegate(user) == user || getVotingDelegate(user) == 0) => !getDelegatingVoting(user) + + +// invariant nonDelegatingBalance(address user) +// !getDelegatingProposition(user) => balanceOf(user) == getDelegatedPropositionBalance(user) { +// preserved transfer(address to, uint256 amount) with (env e) +// { +// require(getVotingDelegate(to) != user); +// } +// } + +// rule sumDelegatedBalances(method f) { +// env e; +// calldataarg args; + +// uint256 total = totalSupply(); +// mathint sumBalancesGhost = sumBalances; +// require totalSupply() <= AAVE_MAX_SUPPLY(); +// // proved elsewhere +// require sumBalances == totalSupply(); +// uint256 sumBalancesBefore = totalDelegatedPropositionBalance(); +// require sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost; +// if (f.selector == delegate(address).selector) { +// address delegatee; +// uint256 balance = balanceOf(e.msg.sender); +// bool isDelegating = getDelegatingProposition(e.msg.sender); +// require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - balance; +// delegate(e, delegatee); +// } else if (f.selector == delegateByType(address,uint8).selector) { +// address delegatee; +// uint256 balance = balanceOf(e.msg.sender); +// bool isDelegating = getDelegatingProposition(e.msg.sender); +// require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - balance; +// delegateByType(e, delegatee, PROPOSITION_POWER()); +// } else if (f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector) { +// address delegator; address delegatee; uint256 deadline; uint8 v; bytes32 r; bytes32 s; +// uint256 balance = balanceOf(delegator); +// bool isDelegating = getDelegatingProposition(delegator); +// require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - balance; +// metaDelegate(e, delegator, delegatee, deadline, v, r, s); +// } else if (f.selector == metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32).selector) { +// address delegator; address delegatee; uint256 deadline; uint8 v; bytes32 r; bytes32 s; +// uint256 balance = balanceOf(delegator); +// bool isDelegating = getDelegatingProposition(delegator); +// require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - balance; +// // metaDelegate by Proposition type (1) +// metaDelegateByType(e, delegator, delegatee, PROPOSITION_POWER(), deadline, v, r, s); +// } else if (f.selector == transfer(address,uint256).selector) { +// address to; +// uint256 amount; +// uint256 balance = balanceOf(e.msg.sender); +// require balance < AAVE_MAX_SUPPLY(); +// bool isDelegating = getDelegatingProposition(e.msg.sender); +// address toDelegate = getPropositionDelegate(to); +// require toDelegate != 0; +// uint256 toDelegatePower = getPowerCurrent(toDelegate, PROPOSITION_POWER()); + +// require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - amount; +// require isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() >= balance + toDelegatePower; +// transfer(e, to, amount); +// } else if (f.selector == transferFrom(address,address,uint256).selector) { +// address to; +// address from; +// uint256 amount; +// uint256 balanceFromBefore = balanceOf(from); +// uint256 balanceToBefore = balanceOf(to); +// require balanceFromBefore <= AAVE_MAX_SUPPLY(); +// bool isDelegating = getDelegatingProposition(from); +// address delegateFrom = getPropositionDelegate(from); +// uint256 votingPowerFromDelegateBefore = getPowerCurrent(delegateFrom, PROPOSITION_POWER()); +// address delegateTo = getPropositionDelegate(to); +// uint256 votingPowerToDelegateBefore = getPowerCurrent(delegateTo, PROPOSITION_POWER()); +// require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - amount; +// require isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() >= balanceFromBefore; +// transferFrom(e, from, to, amount); +// uint256 balanceFromAfter = balanceOf(from); +// uint256 balanceToAfter = balanceOf(to); +// uint256 votingPowerFromDelegateAfter = getPowerCurrent(delegateFrom, PROPOSITION_POWER()); +// uint256 votingPowerToDelegateAfter = getPowerCurrent(delegateTo, PROPOSITION_POWER()); +// uint256 normAmount = normalize(amount); +// uint256 votingPowerDelta = votingPowerToDelegateAfter - votingPowerToDelegateBefore; +// assert isDelegating => votingPowerDelta == normAmount || delegateFrom == delegateTo; +// } +// else { +// f(e, args); +// } +// uint256 sumBalancesAfter = totalDelegatedPropositionBalance(); +// assert sumBalancesAfter * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost; -} +// } // TODO: separate rules for transfers, see that the sum of balances stays the same // sum of power for two addresses involved in f, doesn't change. -rule sumPowerCurrent(method f) \ No newline at end of file +// rule sumPowerCurrent(method f) \ No newline at end of file diff --git a/lib/aave-token-v2 b/lib/aave-token-v2 deleted file mode 160000 index 6ebf51d..0000000 --- a/lib/aave-token-v2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6ebf51ddbdfb6ae66de0b4c191b978ef5149a9ce diff --git a/lib/forge-std b/lib/forge-std deleted file mode 160000 index 1680d7f..0000000 --- a/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1680d7fb3e00b7b197a7336e7c88e838c7e6a3ec From 77ebc77bedc2d799976e86820e786b9486e9ead4 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Sun, 31 Jul 2022 18:04:06 +0300 Subject: [PATCH 32/58] update delegate correctness invariant --- certora/specs/general.spec | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/certora/specs/general.spec b/certora/specs/general.spec index d0ab6d7..8000fb7 100644 --- a/certora/specs/general.spec +++ b/certora/specs/general.spec @@ -13,6 +13,40 @@ ghost mathint sumBalances { init_state axiom sumBalances == 0; } + +/* + @Rule + + @Description: + User's delegation flag is switched on iff user is delegating to an address + other than his own own or 0 + + @Notes: + + + @Link: + +*/ +invariant delegateCorrectness(address user) + (getVotingDelegate(user) == user || getVotingDelegate(user) == 0) <=> !getDelegatingVoting(user) + && + (getPropositionDelegate(user) == user || getPropositionDelegate(user) == 0) <=> !getDelegatingProposition(user) + +/* + +Invariant that proves sum of all balances is equal to sum of delegated and +undelegated balances. + +1. Ghost to track delegation state of each acc +2. Ghost to track delegated balances sum +3. Ghost to track undelegated balances sum +4. On each write to balance, check the ghost for delegation state and update either non deleg or deleg +5. On each write to delegation flag, move the balance from deleg to non deleg or the other way around + +*/ + + + /* update proposition balance on each store */ @@ -40,9 +74,6 @@ ghost mathint sumBalances { // MUST: --bitwise operation -invariant delegateCorrectness(address user) - (getVotingDelegate(user) == user || getVotingDelegate(user) == 0) => !getDelegatingVoting(user) - // invariant nonDelegatingBalance(address user) // !getDelegatingProposition(user) => balanceOf(user) == getDelegatedPropositionBalance(user) { From 634118d5e69255653d40fc01bfb0547276078f10 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Mon, 1 Aug 2022 15:02:37 +0300 Subject: [PATCH 33/58] invariant for sum of all balances --- certora/specs/general.spec | 83 ++++++++++++++++++++++++++++++++++++-- src/BaseAaveToken.sol | 3 +- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/certora/specs/general.spec b/certora/specs/general.spec index 8000fb7..177536c 100644 --- a/certora/specs/general.spec +++ b/certora/specs/general.spec @@ -1,10 +1,28 @@ import "base.spec" + // methods { // totalDelegatedVotingBalance() returns (uint256) envfree // totalDelegatedPropositionBalance() returns (uint256) envfree // } +// enum DelegationState { +// NO_DELEGATION, +// VOTING_DELEGATED, +// PROPOSITION_DELEGATED, +// FULL_POWER_DELEGATED +// } + +definition NO_DELEGATION() returns uint = 0; +definition VOTING_DELEGATED() returns uint = 1; +definition PROPOSITION_DELEGATED() returns uint = 2; +definition FULL_POWER_DELEGATED() returns uint = 3; + +// 1 byte +definition DelegationState(uint256 packed) returns uint256 = + (packed & 0xff); + + ghost mathint sumDelegatedProposition { init_state axiom sumDelegatedProposition == 0; } @@ -40,12 +58,71 @@ undelegated balances. 1. Ghost to track delegation state of each acc 2. Ghost to track delegated balances sum 3. Ghost to track undelegated balances sum -4. On each write to balance, check the ghost for delegation state and update either non deleg or deleg -5. On each write to delegation flag, move the balance from deleg to non deleg or the other way around - +4. Ghost to track balances of each account +5. On each write to balance, check the ghost for delegation state and update either non deleg or deleg +6. On each write to delegation flag, move the balance from deleg to non deleg or the other way around */ +// 1. +ghost mapping(address => bool) isDelegatingVoting { + init_state axiom forall address a. isDelegatingVoting[a] == false; +} + +// 2. +ghost mathint sumDelegatedBalances { + init_state axiom sumDelegatedBalances == 0; +} + +// 3. +ghost mathint sumUndelegatedBalances { + init_state axiom sumUndelegatedBalances == 0; +} + +// 4. +ghost mapping(address => uint104) balances { + init_state axiom forall address a. balances[a] == 0; +} + +hook Sstore _balances[KEY address user].(offset 0) uint256 packed (uint64 old_packed) STORAGE { + uint256 old_state = DelegationState(packed); + uint256 new_state = DelegationState(packed); + if ((old_state == NO_DELEGATION() || old_state == PROPOSITION_DELEGATED()) && + (new_state == VOTING_DELEGATED() || new_state == FULL_POWER_DELEGATED())) { + sumUndelegatedBalances = sumUndelegatedBalances - balances[user]; + } + if ((old_state == VOTING_DELEGATED() || old_state == FULL_POWER_DELEGATED()) && + (new_state == NO_DELEGATION() || new_state == PROPOSITION_DELEGATED())) { + sumDelegatedBalances = sumDelegatedBalances - balances[user]; + } +} + + +hook Sstore _balances[KEY address user].balance uint104 balance (uint104 old_balance) STORAGE { + balances[user] = balances[user] - old_balance + balance; + if (isDelegatingVoting[user]) { + sumDelegatedBalances = sumDelegatedBalances + to_mathint(balance) - to_mathint(old_balance); + } else { + sumUndelegatedBalances = sumUndelegatedBalances + to_mathint(balance) - to_mathint(old_balance); + } +} + + +// hook Sstore _balances[KEY address user].delegationState +// ds state (ds old_state) STORAGE { +// if ((old_state == NO_DELEGATION() || old_state == PROPOSITION_DELEGATED()) && +// (state == VOTING_DELEGATED() || state == FULL_POWER_DELEGATED())) { +// sumUndelegatedBalances = sumUndelegatedBalances - balances[user]; +// } +// if ((old_state == VOTING_DELEGATED() || old_state == FULL_POWER_DELEGATED()) && +// (state == NO_DELEGATION() || state == PROPOSITION_DELEGATED())) { +// sumDelegatedBalances = sumDelegatedBalances - balances[user]; +// } +// } + +invariant general() sumDelegatedBalances + sumUndelegatedBalances == totalSupply() + +// hook Sstore /* update proposition balance on each store diff --git a/src/BaseAaveToken.sol b/src/BaseAaveToken.sol index 372e670..f29dc41 100644 --- a/src/BaseAaveToken.sol +++ b/src/BaseAaveToken.sol @@ -14,11 +14,12 @@ abstract contract BaseAaveToken is Context, IERC20Metadata { FULL_POWER_DELEGATED } + // reorder fields to make hooks syntax simpler struct DelegationAwareBalance { + DelegationState delegationState; uint104 balance; uint72 delegatedPropositionBalance; uint72 delegatedVotingBalance; - DelegationState delegationState; } mapping(address => DelegationAwareBalance) internal _balances; From 4ebd9d06792d8768e0d195e506f60cbacccef248 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Mon, 1 Aug 2022 16:13:54 +0300 Subject: [PATCH 34/58] fix invariants --- certora/specs/general.spec | 181 ++++--------------------------------- 1 file changed, 18 insertions(+), 163 deletions(-) diff --git a/certora/specs/general.spec b/certora/specs/general.spec index 177536c..6c81c1b 100644 --- a/certora/specs/general.spec +++ b/certora/specs/general.spec @@ -1,18 +1,5 @@ import "base.spec" - -// methods { -// totalDelegatedVotingBalance() returns (uint256) envfree -// totalDelegatedPropositionBalance() returns (uint256) envfree -// } - -// enum DelegationState { -// NO_DELEGATION, -// VOTING_DELEGATED, -// PROPOSITION_DELEGATED, -// FULL_POWER_DELEGATED -// } - definition NO_DELEGATION() returns uint = 0; definition VOTING_DELEGATED() returns uint = 1; definition PROPOSITION_DELEGATED() returns uint = 2; @@ -46,9 +33,9 @@ ghost mathint sumBalances { */ invariant delegateCorrectness(address user) - (getVotingDelegate(user) == user || getVotingDelegate(user) == 0) <=> !getDelegatingVoting(user) + ((getVotingDelegate(user) == user || getVotingDelegate(user) == 0) <=> !getDelegatingVoting(user)) && - (getPropositionDelegate(user) == user || getPropositionDelegate(user) == 0) <=> !getDelegatingProposition(user) + ((getPropositionDelegate(user) == user || getPropositionDelegate(user) == 0) <=> !getDelegatingProposition(user)) /* @@ -83,164 +70,32 @@ ghost mapping(address => uint104) balances { init_state axiom forall address a. balances[a] == 0; } -hook Sstore _balances[KEY address user].(offset 0) uint256 packed (uint64 old_packed) STORAGE { +hook Sstore _balances[KEY address user].(offset 0) uint256 packed (uint256 old_packed) STORAGE { uint256 old_state = DelegationState(packed); uint256 new_state = DelegationState(packed); - if ((old_state == NO_DELEGATION() || old_state == PROPOSITION_DELEGATED()) && - (new_state == VOTING_DELEGATED() || new_state == FULL_POWER_DELEGATED())) { - sumUndelegatedBalances = sumUndelegatedBalances - balances[user]; - } - if ((old_state == VOTING_DELEGATED() || old_state == FULL_POWER_DELEGATED()) && - (new_state == NO_DELEGATION() || new_state == PROPOSITION_DELEGATED())) { - sumDelegatedBalances = sumDelegatedBalances - balances[user]; - } + bool willDelegate = ((old_state == NO_DELEGATION() || old_state == PROPOSITION_DELEGATED()) && + (new_state == VOTING_DELEGATED() || new_state == FULL_POWER_DELEGATED())); + bool wasDelegating = ((old_state == VOTING_DELEGATED() || old_state == FULL_POWER_DELEGATED()) && + (new_state == NO_DELEGATION() || new_state == PROPOSITION_DELEGATED())); + sumUndelegatedBalances = willDelegate ? sumUndelegatedBalances - balances[user] : sumUndelegatedBalances; + sumUndelegatedBalances = wasDelegating ? sumUndelegatedBalances + balances[user] : sumUndelegatedBalances; + sumDelegatedBalances = willDelegate ? sumDelegatedBalances + balances[user] : sumDelegatedBalances; + sumDelegatedBalances = wasDelegating ? sumDelegatedBalances - balances[user] : sumDelegatedBalances; } hook Sstore _balances[KEY address user].balance uint104 balance (uint104 old_balance) STORAGE { balances[user] = balances[user] - old_balance + balance; - if (isDelegatingVoting[user]) { - sumDelegatedBalances = sumDelegatedBalances + to_mathint(balance) - to_mathint(old_balance); - } else { - sumUndelegatedBalances = sumUndelegatedBalances + to_mathint(balance) - to_mathint(old_balance); - } + sumDelegatedBalances = isDelegatingVoting[user] + ? sumDelegatedBalances + to_mathint(balance) - to_mathint(old_balance) + : sumDelegatedBalances; + sumUndelegatedBalances = !isDelegatingVoting[user] + ? sumUndelegatedBalances + to_mathint(balance) - to_mathint(old_balance) + : sumUndelegatedBalances; } +invariant sumOfBalancesCorrectness() sumDelegatedBalances + sumUndelegatedBalances == totalSupply() -// hook Sstore _balances[KEY address user].delegationState -// ds state (ds old_state) STORAGE { -// if ((old_state == NO_DELEGATION() || old_state == PROPOSITION_DELEGATED()) && -// (state == VOTING_DELEGATED() || state == FULL_POWER_DELEGATED())) { -// sumUndelegatedBalances = sumUndelegatedBalances - balances[user]; -// } -// if ((old_state == VOTING_DELEGATED() || old_state == FULL_POWER_DELEGATED()) && -// (state == NO_DELEGATION() || state == PROPOSITION_DELEGATED())) { -// sumDelegatedBalances = sumDelegatedBalances - balances[user]; -// } -// } - -invariant general() sumDelegatedBalances + sumUndelegatedBalances == totalSupply() - - -// hook Sstore - -/* - update proposition balance on each store - */ -// hook Sstore _balances[KEY address user].delegatedPropositionBalance uint72 balance -// (uint72 old_balance) STORAGE { -// sumDelegatedProposition = -// sumDelegatedProposition + to_mathint(balance) * DELEGATED_POWER_DIVIDER() - to_mathint(old_balance) * -// DELEGATED_POWER_DIVIDER(); -// } - -// // try to rewrite using power.spec in aave-tokenv2 customer code -// hook Sstore _balances[KEY address user].balance uint104 balance -// (uint104 old_balance) STORAGE { -// sumBalances = sumBalances + to_mathint(balance) - to_mathint(old_balance); -// } - -// hook Sload uint104 balance _balances[KEY address user].balance STORAGE { -// require balance <= sumBalances; -// } - -// hook Sload uint72 balance _balances[KEY address user].delegatedPropositionBalance STORAGE { -// require balance <= sumDelegatedProposition; -// } - - -// MUST: --bitwise operation - - -// invariant nonDelegatingBalance(address user) -// !getDelegatingProposition(user) => balanceOf(user) == getDelegatedPropositionBalance(user) { -// preserved transfer(address to, uint256 amount) with (env e) -// { -// require(getVotingDelegate(to) != user); -// } -// } - -// rule sumDelegatedBalances(method f) { -// env e; -// calldataarg args; - -// uint256 total = totalSupply(); -// mathint sumBalancesGhost = sumBalances; -// require totalSupply() <= AAVE_MAX_SUPPLY(); -// // proved elsewhere -// require sumBalances == totalSupply(); -// uint256 sumBalancesBefore = totalDelegatedPropositionBalance(); -// require sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost; -// if (f.selector == delegate(address).selector) { -// address delegatee; -// uint256 balance = balanceOf(e.msg.sender); -// bool isDelegating = getDelegatingProposition(e.msg.sender); -// require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - balance; -// delegate(e, delegatee); -// } else if (f.selector == delegateByType(address,uint8).selector) { -// address delegatee; -// uint256 balance = balanceOf(e.msg.sender); -// bool isDelegating = getDelegatingProposition(e.msg.sender); -// require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - balance; -// delegateByType(e, delegatee, PROPOSITION_POWER()); -// } else if (f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector) { -// address delegator; address delegatee; uint256 deadline; uint8 v; bytes32 r; bytes32 s; -// uint256 balance = balanceOf(delegator); -// bool isDelegating = getDelegatingProposition(delegator); -// require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - balance; -// metaDelegate(e, delegator, delegatee, deadline, v, r, s); -// } else if (f.selector == metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32).selector) { -// address delegator; address delegatee; uint256 deadline; uint8 v; bytes32 r; bytes32 s; -// uint256 balance = balanceOf(delegator); -// bool isDelegating = getDelegatingProposition(delegator); -// require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - balance; -// // metaDelegate by Proposition type (1) -// metaDelegateByType(e, delegator, delegatee, PROPOSITION_POWER(), deadline, v, r, s); -// } else if (f.selector == transfer(address,uint256).selector) { -// address to; -// uint256 amount; -// uint256 balance = balanceOf(e.msg.sender); -// require balance < AAVE_MAX_SUPPLY(); -// bool isDelegating = getDelegatingProposition(e.msg.sender); -// address toDelegate = getPropositionDelegate(to); -// require toDelegate != 0; -// uint256 toDelegatePower = getPowerCurrent(toDelegate, PROPOSITION_POWER()); - -// require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - amount; -// require isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() >= balance + toDelegatePower; -// transfer(e, to, amount); -// } else if (f.selector == transferFrom(address,address,uint256).selector) { -// address to; -// address from; -// uint256 amount; -// uint256 balanceFromBefore = balanceOf(from); -// uint256 balanceToBefore = balanceOf(to); -// require balanceFromBefore <= AAVE_MAX_SUPPLY(); -// bool isDelegating = getDelegatingProposition(from); -// address delegateFrom = getPropositionDelegate(from); -// uint256 votingPowerFromDelegateBefore = getPowerCurrent(delegateFrom, PROPOSITION_POWER()); -// address delegateTo = getPropositionDelegate(to); -// uint256 votingPowerToDelegateBefore = getPowerCurrent(delegateTo, PROPOSITION_POWER()); -// require !isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost - amount; -// require isDelegating => sumBalancesBefore * DELEGATED_POWER_DIVIDER() >= balanceFromBefore; -// transferFrom(e, from, to, amount); -// uint256 balanceFromAfter = balanceOf(from); -// uint256 balanceToAfter = balanceOf(to); -// uint256 votingPowerFromDelegateAfter = getPowerCurrent(delegateFrom, PROPOSITION_POWER()); -// uint256 votingPowerToDelegateAfter = getPowerCurrent(delegateTo, PROPOSITION_POWER()); -// uint256 normAmount = normalize(amount); -// uint256 votingPowerDelta = votingPowerToDelegateAfter - votingPowerToDelegateBefore; -// assert isDelegating => votingPowerDelta == normAmount || delegateFrom == delegateTo; -// } -// else { -// f(e, args); -// } -// uint256 sumBalancesAfter = totalDelegatedPropositionBalance(); -// assert sumBalancesAfter * DELEGATED_POWER_DIVIDER() <= sumBalancesGhost; - -// } - -// TODO: separate rules for transfers, see that the sum of balances stays the same // sum of power for two addresses involved in f, doesn't change. // rule sumPowerCurrent(method f) \ No newline at end of file From 4430e60dba3ebac71b5ec7e35045a29d3ce54514 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Thu, 4 Aug 2022 17:38:38 +0300 Subject: [PATCH 35/58] update general.spec and invariants --- README.md | 12 +- certora/harness/AaveTokenV3HarnessStorage.sol | 45 ++ .../harness/storage_harness/AaveTokenV3.sol | 383 ++++++++++++++++++ .../harness/storage_harness/BaseAaveToken.sol | 162 ++++++++ .../storage_harness/BaseAaveTokenV2.sol | 82 ++++ certora/harness/storage_harness/README.md | 5 + certora/scripts/run.sh | 7 - certora/scripts/runRepro.sh | 15 + certora/scripts/verifyGeneral.sh | 4 +- certora/specs/delegate.spec | 4 +- certora/specs/erc20.spec | 9 + certora/specs/general.spec | 241 ++++++++--- certora/specs/repro.spec | 162 ++++++++ 13 files changed, 1061 insertions(+), 70 deletions(-) create mode 100644 certora/harness/AaveTokenV3HarnessStorage.sol create mode 100644 certora/harness/storage_harness/AaveTokenV3.sol create mode 100644 certora/harness/storage_harness/BaseAaveToken.sol create mode 100644 certora/harness/storage_harness/BaseAaveTokenV2.sol create mode 100644 certora/harness/storage_harness/README.md delete mode 100755 certora/scripts/run.sh create mode 100644 certora/scripts/runRepro.sh create mode 100644 certora/specs/repro.spec diff --git a/README.md b/README.md index bff71bc..7508383 100644 --- a/README.md +++ b/README.md @@ -1 +1,11 @@ -# aave-token-v3 \ No newline at end of file +# aave-token-v3 + +Here the spec has two hooks, one on the balance field and one is using offset 0. + +Failed run when DelegationState is at the end of the struct. + +https://prover.certora.com/output/67509/57057282fda3e2abefca/?anonymousKey=64c0e318e73e5c124906755f7ca1a092e1fda03e + +Successful (but vacuous) run when DelegationState is at the start of the struct. + +https://prover.certora.com/output/67509/1aaa7ae25105510ee937/?anonymousKey=dc6d124db879952c6809203f87dff0aad49d6d36 \ No newline at end of file diff --git a/certora/harness/AaveTokenV3HarnessStorage.sol b/certora/harness/AaveTokenV3HarnessStorage.sol new file mode 100644 index 0000000..fb8f621 --- /dev/null +++ b/certora/harness/AaveTokenV3HarnessStorage.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {AaveTokenV3} from "./storage_harness/AaveTokenV3.sol"; + +contract AaveTokenV3Harness is AaveTokenV3 { + function getBalance(address user) view public returns (uint104) { + return _balances[user].balance; + } + + function getDelegatedPropositionBalance(address user) view public returns (uint72) { + return _balances[user].delegatedPropositionBalance; + } + + + function getDelegatedVotingBalance(address user) view public returns (uint72) { + return _balances[user].delegatedVotingBalance; + } + + + function getDelegatingProposition(address user) view public returns (bool) { + uint8 state = _balances[user].delegationState; + return state == uint8(DelegationState.PROPOSITION_DELEGATED) || + state == uint8(DelegationState.FULL_POWER_DELEGATED); + } + + + function getDelegatingVoting(address user) view public returns (bool) { + uint8 state = _balances[user].delegationState; + return state == uint8(DelegationState.VOTING_DELEGATED) || + state == uint8(DelegationState.FULL_POWER_DELEGATED); + } + + function getVotingDelegate(address user) view public returns (address) { + return _votingDelegateeV2[user]; + } + + function getPropositionDelegate(address user) view public returns (address) { + return _propositionDelegateeV2[user]; + } + + function getDelegationState(address user) view public returns (uint8) { + return _balances[user].delegationState; + } +} \ No newline at end of file diff --git a/certora/harness/storage_harness/AaveTokenV3.sol b/certora/harness/storage_harness/AaveTokenV3.sol new file mode 100644 index 0000000..ffd3928 --- /dev/null +++ b/certora/harness/storage_harness/AaveTokenV3.sol @@ -0,0 +1,383 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {VersionedInitializable} from '../../../src/utils/VersionedInitializable.sol'; +import {IGovernancePowerDelegationToken} from '../../../src/interfaces/IGovernancePowerDelegationToken.sol'; +import {BaseAaveTokenV2} from './BaseAaveTokenV2.sol'; + +contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { + mapping(address => address) internal _votingDelegateeV2; + mapping(address => address) internal _propositionDelegateeV2; + + /// @dev we assume that for the governance system 18 decimals of precision is not needed, + // by this constant we reduce it by 10, to 8 decimals + uint256 public constant POWER_SCALE_FACTOR = 1e10; + + bytes32 public constant DELEGATE_BY_TYPE_TYPEHASH = + keccak256( + 'DelegateByType(address delegator,address delegatee,GovernancePowerType delegationType,uint256 nonce,uint256 deadline)' + ); + bytes32 public constant DELEGATE_TYPEHASH = + keccak256('Delegate(address delegator,address delegatee,uint256 nonce,uint256 deadline)'); + + /// @inheritdoc IGovernancePowerDelegationToken + function delegateByType(address delegatee, GovernancePowerType delegationType) + external + virtual + override + { + _delegateByType(msg.sender, delegatee, delegationType); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function delegate(address delegatee) external override { + _delegateByType(msg.sender, delegatee, GovernancePowerType.VOTING); + _delegateByType(msg.sender, delegatee, GovernancePowerType.PROPOSITION); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function getDelegateeByType(address delegator, GovernancePowerType delegationType) + external + view + override + returns (address) + { + return _getDelegateeByType(delegator, _balances[delegator], delegationType); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function getDelegates(address delegator) external view override returns (address, address) { + DelegationAwareBalance memory delegatorBalance = _balances[delegator]; + return ( + _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.VOTING), + _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.PROPOSITION) + ); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function getPowerCurrent(address user, GovernancePowerType delegationType) + public + view + override + returns (uint256) + { + DelegationAwareBalance memory userState = _balances[user]; + uint256 userOwnPower = uint8(userState.delegationState) & (uint8(delegationType) + 1) == 0 + ? _balances[user].balance + : 0; + uint256 userDelegatedPower = _getDelegatedPowerByType(userState, delegationType); + return userOwnPower + userDelegatedPower; + } + + /// @inheritdoc IGovernancePowerDelegationToken + function getPowersCurrent(address user) external view override returns (uint256, uint256) { + return ( + getPowerCurrent(user, GovernancePowerType.VOTING), + getPowerCurrent(user, GovernancePowerType.PROPOSITION) + ); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function metaDelegateByType( + address delegator, + address delegatee, + GovernancePowerType delegationType, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external override { + require(delegator != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _nonces[delegator]; + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256( + abi.encode( + DELEGATE_BY_TYPE_TYPEHASH, + delegator, + delegatee, + delegationType, + currentValidNonce, + deadline + ) + ) + ) + ); + + require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + unchecked { + // Does not make sense to check because it's not realistic to reach uint256.max in nonce + _nonces[delegator] = currentValidNonce + 1; + } + _delegateByType(delegator, delegatee, delegationType); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function metaDelegate( + address delegator, + address delegatee, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external override { + require(delegator != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _nonces[delegator]; + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256(abi.encode(DELEGATE_TYPEHASH, delegator, delegatee, currentValidNonce, deadline)) + ) + ); + + require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + unchecked { + // does not make sense to check because it's not realistic to reach uint256.max in nonce + _nonces[delegator] = currentValidNonce + 1; + } + _delegateByType(delegator, delegatee, GovernancePowerType.VOTING); + _delegateByType(delegator, delegatee, GovernancePowerType.PROPOSITION); + } + + /** + * @dev Modifies the delegated power of a `delegatee` account by type (VOTING, PROPOSITION). + * Passing the impact on the delegation of `delegatee` account before and after to reduce conditionals and not lose + * any precision. + * @param impactOnDelegationBefore how much impact a balance of another account had over the delegation of a `delegatee` + * before an action. + * For example, if the action is a delegation from one account to another, the impact before the action will be 0. + * @param impactOnDelegationAfter how much impact a balance of another account will have over the delegation of a `delegatee` + * after an action. + * For example, if the action is a delegation from one account to another, the impact after the action will be the whole balance + * of the account changing the delegatee. + * @param delegatee the user whom delegated governance power will be changed + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + **/ + function _governancePowerTransferByType( + uint104 impactOnDelegationBefore, + uint104 impactOnDelegationAfter, + address delegatee, + GovernancePowerType delegationType + ) internal { + if (delegatee == address(0)) return; + if (impactOnDelegationBefore == impactOnDelegationAfter) return; + + // To make delegated balance fit into uint72 we're decreasing precision of delegated balance by POWER_SCALE_FACTOR + uint72 impactOnDelegationBefore72 = uint72(impactOnDelegationBefore / POWER_SCALE_FACTOR); + uint72 impactOnDelegationAfter72 = uint72(impactOnDelegationAfter / POWER_SCALE_FACTOR); + + if (delegationType == GovernancePowerType.VOTING) { + _balances[delegatee].delegatedVotingBalance = + _balances[delegatee].delegatedVotingBalance - + impactOnDelegationBefore72 + + impactOnDelegationAfter72; + } else { + _balances[delegatee].delegatedPropositionBalance = + _balances[delegatee].delegatedPropositionBalance - + impactOnDelegationBefore72 + + impactOnDelegationAfter72; + } + } + + /** + * @dev performs all state changes related to balance transfer and corresponding delegation changes + * @param from token sender + * @param to token recipient + * @param amount amount of tokens sent + **/ + function _transferWithDelegation( + address from, + address to, + uint256 amount + ) internal override { + if (from == to) { + return; + } + + if (from != address(0)) { + DelegationAwareBalance memory fromUserState = _balances[from]; + require(fromUserState.balance >= amount, 'ERC20: transfer amount exceeds balance'); + + uint104 fromBalanceAfter; + unchecked { + fromBalanceAfter = fromUserState.balance - uint104(amount); + } + _balances[from].balance = fromBalanceAfter; + if (fromUserState.delegationState != uint8(DelegationState.NO_DELEGATION)) { + _governancePowerTransferByType( + fromUserState.balance, + fromBalanceAfter, + _getDelegateeByType(from, fromUserState, GovernancePowerType.VOTING), + GovernancePowerType.VOTING + ); + _governancePowerTransferByType( + fromUserState.balance, + fromBalanceAfter, + _getDelegateeByType(from, fromUserState, GovernancePowerType.PROPOSITION), + GovernancePowerType.PROPOSITION + ); + } + } + + if (to != address(0)) { + DelegationAwareBalance memory toUserState = _balances[to]; + uint104 toBalanceBefore = toUserState.balance; + toUserState.balance = toBalanceBefore + uint104(amount); + _balances[to] = toUserState; + + if (toUserState.delegationState != uint8(DelegationState.NO_DELEGATION)) { + _governancePowerTransferByType( + toBalanceBefore, + toUserState.balance, + _getDelegateeByType(to, toUserState, GovernancePowerType.VOTING), + GovernancePowerType.VOTING + ); + _governancePowerTransferByType( + toBalanceBefore, + toUserState.balance, + _getDelegateeByType(to, toUserState, GovernancePowerType.PROPOSITION), + GovernancePowerType.PROPOSITION + ); + } + } + } + + /** + * @dev Extracts from state and returns delegated governance power (Voting, Proposition) + * @param userState the current state of a user + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + **/ + function _getDelegatedPowerByType( + DelegationAwareBalance memory userState, + GovernancePowerType delegationType + ) internal pure returns (uint256) { + return + POWER_SCALE_FACTOR * + ( + delegationType == GovernancePowerType.VOTING + ? userState.delegatedVotingBalance + : userState.delegatedPropositionBalance + ); + } + + /** + * @dev Extracts from state and returns the delegatee of a delegator by type of governance power (Voting, Proposition) + * - If the delegator doesn't have any delegatee, returns address(0) + * @param delegator delegator + * @param userState the current state of a user + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + **/ + function _getDelegateeByType( + address delegator, + DelegationAwareBalance memory userState, + GovernancePowerType delegationType + ) internal view returns (address) { + if (delegationType == GovernancePowerType.VOTING) { + return + /// With the & operation, we cover both VOTING_DELEGATED delegation and FULL_POWER_DELEGATED + /// as VOTING_DELEGATED is equivalent to 01 in binary and FULL_POWER_DELEGATED is equivalent to 11 + (uint8(userState.delegationState) & uint8(DelegationState.VOTING_DELEGATED)) != 0 + ? _votingDelegateeV2[delegator] + : address(0); + } + return + userState.delegationState >= uint8(DelegationState.PROPOSITION_DELEGATED) + ? _propositionDelegateeV2[delegator] + : address(0); + } + + /** + * @dev Changes user's delegatee address by type of governance power (Voting, Proposition) + * @param delegator delegator + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + * @param _newDelegatee the new delegatee + **/ + function _updateDelegateeByType( + address delegator, + GovernancePowerType delegationType, + address _newDelegatee + ) internal { + address newDelegatee = _newDelegatee == delegator ? address(0) : _newDelegatee; + if (delegationType == GovernancePowerType.VOTING) { + _votingDelegateeV2[delegator] = newDelegatee; + } else { + _propositionDelegateeV2[delegator] = newDelegatee; + } + } + + /** + * @dev Updates the specific flag which signaling about existence of delegation of governance power (Voting, Proposition) + * @param userState a user state to change + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + * @param willDelegate next state of delegation + **/ + function _updateDelegationFlagByType( + DelegationAwareBalance memory userState, + GovernancePowerType delegationType, + bool willDelegate + ) internal pure returns (DelegationAwareBalance memory) { + if (willDelegate) { + // Because GovernancePowerType starts from 0, we should add 1 first, then we apply bitwise OR + userState.delegationState = userState.delegationState | (uint8(delegationType) + 1); + } else { + // First bitwise NEGATION, ie was 01, after XOR with 11 will be 10, + // then bitwise AND, which means it will keep only another delegation type if it exists + userState.delegationState = userState.delegationState & + ((uint8(delegationType) + 1) ^ uint8(DelegationState.FULL_POWER_DELEGATED)); + } + return userState; + } + + /** + * @dev This is the equivalent of an ERC20 transfer(), but for a power type: an atomic transfer of a balance (power). + * When needed, it decreases the power of the `delegator` and when needed, it increases the power of the `delegatee` + * @param delegator delegator + * @param _delegatee the user which delegated power will change + * @param delegationType the type of delegation (VOTING, PROPOSITION) + **/ + function _delegateByType( + address delegator, + address _delegatee, + GovernancePowerType delegationType + ) internal { + // Here we unify the property that delegating power to address(0) == delegating power to yourself == no delegation + // So from now on, not being delegating is (exclusively) that delegatee == address(0) + address delegatee = _delegatee == delegator ? address(0) : _delegatee; + + // We read the whole struct before validating delegatee, because in the optimistic case + // (_delegatee != currentDelegatee) we will reuse userState in the rest of the function + DelegationAwareBalance memory delegatorState = _balances[delegator]; + address currentDelegatee = _getDelegateeByType(delegator, delegatorState, delegationType); + if (delegatee == currentDelegatee) return; + + bool delegatingNow = currentDelegatee != address(0); + bool willDelegateAfter = delegatee != address(0); + + if (delegatingNow) { + _governancePowerTransferByType(delegatorState.balance, 0, currentDelegatee, delegationType); + } + + if (willDelegateAfter) { + _governancePowerTransferByType(0, delegatorState.balance, delegatee, delegationType); + } + + _updateDelegateeByType(delegator, delegationType, delegatee); + + if (willDelegateAfter != delegatingNow) { + _balances[delegator] = _updateDelegationFlagByType( + delegatorState, + delegationType, + willDelegateAfter + ); + } + + emit DelegateChanged(delegator, delegatee, delegationType); + } +} \ No newline at end of file diff --git a/certora/harness/storage_harness/BaseAaveToken.sol b/certora/harness/storage_harness/BaseAaveToken.sol new file mode 100644 index 0000000..4c51174 --- /dev/null +++ b/certora/harness/storage_harness/BaseAaveToken.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Context} from '../../../lib/openzeppelin-contracts/contracts/utils/Context.sol'; +import {IERC20} from '../../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; +import {IERC20Metadata} from '../../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; + +// Inspired by OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) +abstract contract BaseAaveToken is Context, IERC20Metadata { + enum DelegationState { + NO_DELEGATION, + VOTING_DELEGATED, + PROPOSITION_DELEGATED, + FULL_POWER_DELEGATED + } + + // reorder fields to make hooks syntax simpler + struct DelegationAwareBalance { + uint104 balance; + uint72 delegatedPropositionBalance; + uint72 delegatedVotingBalance; + uint8 delegationState; // refactored from enum + } + + mapping(address => DelegationAwareBalance) internal _balances; + + mapping(address => mapping(address => uint256)) internal _allowances; + + uint256 internal _totalSupply; + + string internal _name; + string internal _symbol; + + // @dev DEPRECATED + // kept for backwards compatibility with old storage layout + uint8 private ______DEPRECATED_OLD_ERC20_DECIMALS; + + /** + * @dev Returns the name of the token. + */ + function name() public view virtual override returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view virtual override returns (string memory) { + return _symbol; + } + + function decimals() public view virtual override returns (uint8) { + return 18; + } + + function totalSupply() public view virtual override returns (uint256) { + return _totalSupply; + } + + function balanceOf(address account) public view virtual override returns (uint256) { + return _balances[account].balance; + } + + function transfer(address to, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _transfer(owner, to, amount); + return true; + } + + function allowance(address owner, address spender) + public + view + virtual + override + returns (uint256) + { + return _allowances[owner][spender]; + } + + function approve(address spender, uint256 amount) public virtual override returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, amount); + return true; + } + + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual override returns (bool) { + address spender = _msgSender(); + _spendAllowance(from, spender, amount); + _transfer(from, to, amount); + return true; + } + + function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { + address owner = _msgSender(); + _approve(owner, spender, _allowances[owner][spender] + addedValue); + return true; + } + + function decreaseAllowance(address spender, uint256 subtractedValue) + public + virtual + returns (bool) + { + address owner = _msgSender(); + uint256 currentAllowance = _allowances[owner][spender]; + require(currentAllowance >= subtractedValue, 'ERC20: decreased allowance below zero'); + unchecked { + _approve(owner, spender, currentAllowance - subtractedValue); + } + + return true; + } + + function _transfer( + address from, + address to, + uint256 amount + ) internal virtual { + require(from != address(0), 'ERC20: transfer from the zero address'); + require(to != address(0), 'ERC20: transfer to the zero address'); + + _transferWithDelegation(from, to, amount); + emit Transfer(from, to, amount); + } + + function _approve( + address owner, + address spender, + uint256 amount + ) internal virtual { + require(owner != address(0), 'ERC20: approve from the zero address'); + require(spender != address(0), 'ERC20: approve to the zero address'); + + _allowances[owner][spender] = amount; + emit Approval(owner, spender, amount); + } + + function _spendAllowance( + address owner, + address spender, + uint256 amount + ) internal virtual { + uint256 currentAllowance = allowance(owner, spender); + if (currentAllowance != type(uint256).max) { + require(currentAllowance >= amount, 'ERC20: insufficient allowance'); + unchecked { + _approve(owner, spender, currentAllowance - amount); + } + } + } + + function _transferWithDelegation( + address from, + address to, + uint256 amount + ) internal virtual {} +} diff --git a/certora/harness/storage_harness/BaseAaveTokenV2.sol b/certora/harness/storage_harness/BaseAaveTokenV2.sol new file mode 100644 index 0000000..18ab3e2 --- /dev/null +++ b/certora/harness/storage_harness/BaseAaveTokenV2.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {VersionedInitializable} from '../../../src/utils/VersionedInitializable.sol'; +import {BaseAaveToken} from './BaseAaveToken.sol'; + +abstract contract BaseAaveTokenV2 is BaseAaveToken, VersionedInitializable { + /// @dev owner => next valid nonce to submit with permit() + mapping(address => uint256) public _nonces; + + ///////// @dev DEPRECATED from AaveToken v1 ////////////////////////// + //////// kept for backwards compatibility with old storage layout //// + uint256[3] private ______DEPRECATED_FROM_AAVE_V1; + ///////// @dev END OF DEPRECATED from AaveToken v1 ////////////////////////// + + bytes32 public DOMAIN_SEPARATOR; + + ///////// @dev DEPRECATED from AaveToken v2 ////////////////////////// + //////// kept for backwards compatibility with old storage layout //// + uint256[4] private ______DEPRECATED_FROM_AAVE_V2; + ///////// @dev END OF DEPRECATED from AaveToken v2 ////////////////////////// + + bytes public constant EIP712_REVISION = bytes('1'); + bytes32 internal constant EIP712_DOMAIN = + keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'); + bytes32 public constant PERMIT_TYPEHASH = + keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'); + + uint256 public constant REVISION = 3; + + /** + * @dev initializes the contract upon assignment to the InitializableAdminUpgradeabilityProxy + */ + function initialize() external initializer {} + + /** + * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md + * @param owner the owner of the funds + * @param spender the spender + * @param value the amount + * @param deadline the deadline timestamp, type(uint256).max for no deadline + * @param v signature param + * @param s signature param + * @param r signature param + */ + + function permit( + address owner, + address spender, + uint256 value, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external { + require(owner != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _nonces[owner]; + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline)) + ) + ); + + require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + unchecked { + // does not make sense to check because it's not realistic to reach uint256.max in nonce + _nonces[owner] = currentValidNonce + 1; + } + _approve(owner, spender, value); + } + + /** + * @dev returns the revision of the implementation contract + */ + function getRevision() internal pure override returns (uint256) { + return REVISION; + } +} diff --git a/certora/harness/storage_harness/README.md b/certora/harness/storage_harness/README.md new file mode 100644 index 0000000..aa1e9ca --- /dev/null +++ b/certora/harness/storage_harness/README.md @@ -0,0 +1,5 @@ +This harness replaces DelegationState enum with a simple uint8 in order +to prove invariants about delegation. + +Certora prover has some limitation with writing hooks on an enum field +inside a `struct`, hence the change. \ No newline at end of file diff --git a/certora/scripts/run.sh b/certora/scripts/run.sh deleted file mode 100755 index 03cffd3..0000000 --- a/certora/scripts/run.sh +++ /dev/null @@ -1,7 +0,0 @@ -certoraRun src/AaveTokenV3.sol:AaveTokenV3 \ - --verify AaveTokenV3:certora/specs/complexity.spec \ - --solc solc8.13 \ - --optimistic_loop \ - --staging \ - --msg "AaveTokenV3 complexity check" - \ No newline at end of file diff --git a/certora/scripts/runRepro.sh b/certora/scripts/runRepro.sh new file mode 100644 index 0000000..b0acef1 --- /dev/null +++ b/certora/scripts/runRepro.sh @@ -0,0 +1,15 @@ +if [[ "$1" ]] +then + RULE="--rule $1" +fi + +certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3Harness \ + --verify AaveTokenV3Harness:certora/specs/repro.spec \ + $RULE \ + --solc solc8.13 \ + --optimistic_loop \ + --settings -smt_bitVectorTheory=true \ + --send_only \ + --staging \ + --msg "AaveTokenV3:repro.spec $1" + \ No newline at end of file diff --git a/certora/scripts/verifyGeneral.sh b/certora/scripts/verifyGeneral.sh index 1a03350..39b9557 100644 --- a/certora/scripts/verifyGeneral.sh +++ b/certora/scripts/verifyGeneral.sh @@ -3,13 +3,13 @@ then RULE="--rule $1" fi -certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3Harness \ +certoraRun certora/harness/AaveTokenV3HarnessStorage.sol:AaveTokenV3Harness \ --verify AaveTokenV3Harness:certora/specs/general.spec \ $RULE \ --solc solc8.13 \ --optimistic_loop \ --settings -smt_bitVectorTheory=true \ - --send_only \ --staging \ + --send_only \ --msg "AaveTokenV3:general.spec $1" \ No newline at end of file diff --git a/certora/specs/delegate.spec b/certora/specs/delegate.spec index 2564384..76da77c 100644 --- a/certora/specs/delegate.spec +++ b/certora/specs/delegate.spec @@ -1,11 +1,11 @@ /* - This is a specification file for the verification of verification + This is a specification file for the verification of delegation features of AaveTokenV3.sol smart contract using the Certora prover. For more information, visit: https://www.certora.com/ This file is run with scripts/verifyDelegate.sh - On a version with some small code modifications AaveTokenV3Harness.sol + On AaveTokenV3Harness.sol */ diff --git a/certora/specs/erc20.spec b/certora/specs/erc20.spec index f017561..067439d 100644 --- a/certora/specs/erc20.spec +++ b/certora/specs/erc20.spec @@ -1,3 +1,12 @@ +/* + This is a specification file for the verification of general ERC20 + features of AaveTokenV3.sol smart contract using the Certora prover. + For more information, visit: https://www.certora.com/ + + This file is run with scripts/erc20.sh + On the token harness AaveTokenV3Harness.sol + +*/ import "base.spec" function doesntChangeBalance(method f) returns bool { diff --git a/certora/specs/general.spec b/certora/specs/general.spec index 6c81c1b..cee8cb3 100644 --- a/certora/specs/general.spec +++ b/certora/specs/general.spec @@ -1,23 +1,144 @@ +/* + This is a specification file for the verification of delegation invariants + of AaveTokenV3.sol smart contract using the Certora prover. + For more information, visit: https://www.certora.com/ + + This file is run with scripts/verifyGeneral.sh + On a version with some minimal code modifications + AaveTokenV3HarnessStorage.sol +*/ + import "base.spec" -definition NO_DELEGATION() returns uint = 0; -definition VOTING_DELEGATED() returns uint = 1; -definition PROPOSITION_DELEGATED() returns uint = 2; -definition FULL_POWER_DELEGATED() returns uint = 3; +/** -// 1 byte -definition DelegationState(uint256 packed) returns uint256 = - (packed & 0xff); + Definitions of delegation states +*/ +definition NO_DELEGATION() returns uint8 = 0; +definition VOTING_DELEGATED() returns uint8 = 1; +definition PROPOSITION_DELEGATED() returns uint8 = 2; +definition FULL_POWER_DELEGATED() returns uint8 = 3; +definition DELEGATING_VOTING(uint8 state) returns bool = + state == VOTING_DELEGATED() || state == FULL_POWER_DELEGATED(); +definition DELEGATING_PROPOSITION(uint8 state) returns bool = + state == PROPOSITION_DELEGATED() || state == FULL_POWER_DELEGATED(); -ghost mathint sumDelegatedProposition { - init_state axiom sumDelegatedProposition == 0; -} +/** + + Ghosts + +*/ + +// sum of all user balances ghost mathint sumBalances { init_state axiom sumBalances == 0; } +// tracking voting delegation status for each address +ghost mapping(address => bool) isDelegatingVoting { + init_state axiom forall address a. isDelegatingVoting[a] == false; +} + +// tracking voting delegation status for each address +ghost mapping(address => bool) isDelegatingProposition { + init_state axiom forall address a. isDelegatingProposition[a] == false; +} + +// sum of all voting delegated balances +ghost mathint sumDelegatedBalancesV { + init_state axiom sumDelegatedBalancesV == 0; +} + +// sum of all proposition undelegated balances +ghost mathint sumUndelegatedBalancesV { + init_state axiom sumUndelegatedBalancesV == 0; +} + +// sum of all proposition delegated balances +ghost mathint sumDelegatedBalancesP { + init_state axiom sumDelegatedBalancesP == 0; +} + +// sum of all voting undelegated balances +ghost mathint sumUndelegatedBalancesP { + init_state axiom sumUndelegatedBalancesP == 0; +} + +// token balances of each address +ghost mapping(address => uint104) balances { + init_state axiom forall address a. balances[a] == 0; +} + + +/** + + Hooks + +*/ + + + +/** + + This hook updates the sum of delegated and undelegated balances on each change of delegation state. + If the user moves from not delegating to delegating, their balance is moved from undelegated to delegating, + and etc. + +*/ +hook Sstore _balances[KEY address user].delegationState uint8 new_state (uint8 old_state) STORAGE { + + bool willDelegateP = !DELEGATING_PROPOSITION(old_state) && DELEGATING_PROPOSITION(new_state); + bool wasDelegatingP = DELEGATING_PROPOSITION(old_state) && !DELEGATING_PROPOSITION(new_state); + sumUndelegatedBalancesP = willDelegateP ? (sumUndelegatedBalancesP - balances[user]) : sumUndelegatedBalancesP; + sumUndelegatedBalancesP = wasDelegatingP ? (sumUndelegatedBalancesP + balances[user]) : sumUndelegatedBalancesP; + sumDelegatedBalancesP = willDelegateP ? (sumDelegatedBalancesP + balances[user]) : sumDelegatedBalancesP; + sumDelegatedBalancesP = wasDelegatingP ? (sumDelegatedBalancesP - balances[user]) : sumDelegatedBalancesP; + + // change the delegating state only if a change is stored + + isDelegatingProposition[user] = new_state == old_state + ? isDelegatingProposition[user] + : new_state == PROPOSITION_DELEGATED() || new_state == FULL_POWER_DELEGATED(); + + + bool willDelegateV = !DELEGATING_VOTING(old_state) && DELEGATING_VOTING(new_state); + bool wasDelegatingV = DELEGATING_VOTING(old_state) && !DELEGATING_VOTING(new_state); + sumUndelegatedBalancesV = willDelegateV ? (sumUndelegatedBalancesV - balances[user]) : sumUndelegatedBalancesV; + sumUndelegatedBalancesV = wasDelegatingV ? (sumUndelegatedBalancesV + balances[user]) : sumUndelegatedBalancesV; + sumDelegatedBalancesV = willDelegateV ? (sumDelegatedBalancesV + balances[user]) : sumDelegatedBalancesV; + sumDelegatedBalancesV = wasDelegatingV ? (sumDelegatedBalancesV - balances[user]) : sumDelegatedBalancesV; + + // change the delegating state only if a change is stored + + isDelegatingVoting[user] = new_state == old_state + ? isDelegatingVoting[user] + : new_state == VOTING_DELEGATED() || new_state == FULL_POWER_DELEGATED(); +} + + +/** + + This hook updates the sum of delegated and undelegated balances on each change of user balance. + Depending on the delegation state, either the delegated or the undelegated balance get updated. + +*/ +hook Sstore _balances[KEY address user].balance uint104 balance (uint104 old_balance) STORAGE { + balances[user] = balances[user] - old_balance + balance; + sumDelegatedBalancesV = isDelegatingVoting[user] + ? sumDelegatedBalancesV + to_mathint(balance) - to_mathint(old_balance) + : sumDelegatedBalancesV; + sumUndelegatedBalancesV = !isDelegatingVoting[user] + ? sumUndelegatedBalancesV + to_mathint(balance) - to_mathint(old_balance) + : sumUndelegatedBalancesV; + sumDelegatedBalancesP = isDelegatingProposition[user] + ? sumDelegatedBalancesP + to_mathint(balance) - to_mathint(old_balance) + : sumDelegatedBalancesP; + sumUndelegatedBalancesP = !isDelegatingProposition[user] + ? sumUndelegatedBalancesP + to_mathint(balance) - to_mathint(old_balance) + : sumUndelegatedBalancesP; +} /* @Rule @@ -36,66 +157,70 @@ invariant delegateCorrectness(address user) ((getVotingDelegate(user) == user || getVotingDelegate(user) == 0) <=> !getDelegatingVoting(user)) && ((getPropositionDelegate(user) == user || getPropositionDelegate(user) == 0) <=> !getDelegatingProposition(user)) + { + preserved { + require getDelegationState(user) <= FULL_POWER_DELEGATED(); + } + } /* + @Rule -Invariant that proves sum of all balances is equal to sum of delegated and -undelegated balances. + @Description: + Sum of delegated voting balances and undelegated balances is equal to total supply + + @Notes: + + + @Link: -1. Ghost to track delegation state of each acc -2. Ghost to track delegated balances sum -3. Ghost to track undelegated balances sum -4. Ghost to track balances of each account -5. On each write to balance, check the ghost for delegation state and update either non deleg or deleg -6. On each write to delegation flag, move the balance from deleg to non deleg or the other way around */ +invariant sumOfVBalancesCorrectness() sumDelegatedBalancesV + sumUndelegatedBalancesV == totalSupply() -// 1. -ghost mapping(address => bool) isDelegatingVoting { - init_state axiom forall address a. isDelegatingVoting[a] == false; -} +/* + @Rule -// 2. -ghost mathint sumDelegatedBalances { - init_state axiom sumDelegatedBalances == 0; -} + @Description: + Sum of delegated proposition balances and undelegated balances is equal to total supply -// 3. -ghost mathint sumUndelegatedBalances { - init_state axiom sumUndelegatedBalances == 0; -} + @Notes: -// 4. -ghost mapping(address => uint104) balances { - init_state axiom forall address a. balances[a] == 0; -} -hook Sstore _balances[KEY address user].(offset 0) uint256 packed (uint256 old_packed) STORAGE { - uint256 old_state = DelegationState(packed); - uint256 new_state = DelegationState(packed); - bool willDelegate = ((old_state == NO_DELEGATION() || old_state == PROPOSITION_DELEGATED()) && - (new_state == VOTING_DELEGATED() || new_state == FULL_POWER_DELEGATED())); - bool wasDelegating = ((old_state == VOTING_DELEGATED() || old_state == FULL_POWER_DELEGATED()) && - (new_state == NO_DELEGATION() || new_state == PROPOSITION_DELEGATED())); - sumUndelegatedBalances = willDelegate ? sumUndelegatedBalances - balances[user] : sumUndelegatedBalances; - sumUndelegatedBalances = wasDelegating ? sumUndelegatedBalances + balances[user] : sumUndelegatedBalances; - sumDelegatedBalances = willDelegate ? sumDelegatedBalances + balances[user] : sumDelegatedBalances; - sumDelegatedBalances = wasDelegating ? sumDelegatedBalances - balances[user] : sumDelegatedBalances; -} + @Link: +*/ +invariant sumOfPBalancesCorrectness() sumDelegatedBalancesP + sumUndelegatedBalancesP == totalSupply() -hook Sstore _balances[KEY address user].balance uint104 balance (uint104 old_balance) STORAGE { - balances[user] = balances[user] - old_balance + balance; - sumDelegatedBalances = isDelegatingVoting[user] - ? sumDelegatedBalances + to_mathint(balance) - to_mathint(old_balance) - : sumDelegatedBalances; - sumUndelegatedBalances = !isDelegatingVoting[user] - ? sumUndelegatedBalances + to_mathint(balance) - to_mathint(old_balance) - : sumUndelegatedBalances; -} -invariant sumOfBalancesCorrectness() sumDelegatedBalances + sumUndelegatedBalances == totalSupply() +/* + @Rule + @Description: + Transfers don't change voting delegation state -// sum of power for two addresses involved in f, doesn't change. -// rule sumPowerCurrent(method f) \ No newline at end of file + @Notes: + + + @Link: + +*/ +rule testTransfer() { + env e; + address from; address to; + uint amount; + + uint8 stateFromBefore = getDelegationState(from); + uint8 stateToBefore = getDelegationState(to); + require stateFromBefore <= FULL_POWER_DELEGATED() && stateToBefore <= FULL_POWER_DELEGATED(); + bool testFromBefore = isDelegatingVoting[from]; + bool testToBefore = isDelegatingVoting[to]; + + transferFrom(e, from, to, amount); + + uint8 stateFromAfter = getDelegationState(from); + uint8 stateToAfter = getDelegationState(to); + bool testFromAfter = isDelegatingVoting[from]; + bool testToAfter = isDelegatingVoting[to]; + + assert testFromBefore == testFromAfter && testToBefore == testToAfter; +} diff --git a/certora/specs/repro.spec b/certora/specs/repro.spec new file mode 100644 index 0000000..f57407a --- /dev/null +++ b/certora/specs/repro.spec @@ -0,0 +1,162 @@ +/* + +This spec is for reproducing particular storage splitter issues +in the prover. + +*/ + +import "base.spec" + +definition NO_DELEGATION() returns uint = 0; +definition VOTING_DELEGATED() returns uint = 1; +definition PROPOSITION_DELEGATED() returns uint = 2; +definition FULL_POWER_DELEGATED() returns uint = 3; + +// 1 byte +definition DelegationState(uint256 packed) returns uint256 = + (packed & 0xff00000000000000000000000000000000000000000000000000000000000000) >> 248; + +// definition DelegationState(uint256 packed) returns uint256 = +// (packed & 0xff); + +function delegatingVotingState(uint state) returns bool { + return state == VOTING_DELEGATED() || state == FULL_POWER_DELEGATED(); +} + +ghost mathint sumDelegatedProposition { + init_state axiom sumDelegatedProposition == 0; +} + +ghost mathint sumBalances { + init_state axiom sumBalances == 0; +} + + +/* + @Rule + + @Description: + User's delegation flag is switched on iff user is delegating to an address + other than his own own or 0 + + @Notes: + + + @Link: + +*/ +invariant delegateCorrectness(address user) + ((getVotingDelegate(user) == user || getVotingDelegate(user) == 0) <=> !getDelegatingVoting(user)) + && + ((getPropositionDelegate(user) == user || getPropositionDelegate(user) == 0) <=> !getDelegatingProposition(user)) + { + preserved { + require getDelegationState(user) <= FULL_POWER_DELEGATED(); + } + } +/* + +Invariant that proves sum of all balances is equal to sum of delegated and +undelegated balances. + +1. Ghost to track delegation state of each acc +2. Ghost to track delegated balances sum +3. Ghost to track undelegated balances sum +4. Ghost to track balances of each account +5. On each write to balance, check the ghost for delegation state and update either non deleg or deleg +6. On each write to delegation flag, move the balance from deleg to non deleg or the other way around +*/ + +// 1. +ghost mapping(address => bool) isDelegatingVoting { + init_state axiom forall address a. isDelegatingVoting[a] == false; +} + +// 2. +ghost mathint sumDelegatedBalances { + init_state axiom sumDelegatedBalances == 0; +} + +// 3. +ghost mathint sumUndelegatedBalances { + init_state axiom sumUndelegatedBalances == 0; +} + +// 4. +ghost mapping(address => uint104) balances { + init_state axiom forall address a. balances[a] == 0; +} + +hook Sstore _balances[KEY address user].(offset 0) uint256 packed STORAGE { + uint256 old_state = DelegationState(packed); + uint256 new_state = DelegationState(packed); + bool willDelegate = ((old_state == NO_DELEGATION() || old_state == PROPOSITION_DELEGATED()) && + (new_state == VOTING_DELEGATED() || new_state == FULL_POWER_DELEGATED())); + bool wasDelegating = ((old_state == VOTING_DELEGATED() || old_state == FULL_POWER_DELEGATED()) && + (new_state == NO_DELEGATION() || new_state == PROPOSITION_DELEGATED())); + sumUndelegatedBalances = willDelegate ? (sumUndelegatedBalances - balances[user]) : sumUndelegatedBalances; + sumUndelegatedBalances = wasDelegating ? (sumUndelegatedBalances + balances[user]) : sumUndelegatedBalances; + sumDelegatedBalances = willDelegate ? (sumDelegatedBalances + balances[user]) : sumDelegatedBalances; + sumDelegatedBalances = wasDelegating ? (sumDelegatedBalances - balances[user]) : sumDelegatedBalances; + + // change the delegating state only if a change is stored + + isDelegatingVoting[user] = new_state == old_state + ? isDelegatingVoting[user] + : new_state == VOTING_DELEGATED() || new_state == FULL_POWER_DELEGATED(); + +} + + +hook Sstore _balances[KEY address user].balance uint104 balance (uint104 old_balance) STORAGE { + balances[user] = balances[user] - old_balance + balance; + sumDelegatedBalances = isDelegatingVoting[user] + ? sumDelegatedBalances + to_mathint(balance) - to_mathint(old_balance) + : sumDelegatedBalances; + sumUndelegatedBalances = !isDelegatingVoting[user] + ? sumUndelegatedBalances + to_mathint(balance) - to_mathint(old_balance) + : sumUndelegatedBalances; +} + +invariant sumOfBalancesCorrectness() sumDelegatedBalances + sumUndelegatedBalances == totalSupply() + + +rule whochanged(method f, address user) { + env e; + calldataarg args; + + bool testBefore = isDelegatingVoting[user]; + address v_delegate = getVotingDelegate(user); + address p_delegate = getPropositionDelegate(user); + uint8 stateBefore = getDelegationState(user); + require testBefore == delegatingVotingState(stateBefore); + + f(e, args); + bool testAfter = isDelegatingVoting[user]; + uint8 stateAfter = getDelegationState(user); + address v_delegateAfter = getVotingDelegate(user); + + assert testBefore == testAfter; +} + +rule testTransfer() { + env e; + address from; address to; + uint amount; + + uint8 stateFromBefore = getDelegationState(from); + uint8 stateToBefore = getDelegationState(to); + require stateFromBefore <= FULL_POWER_DELEGATED() && stateToBefore <= FULL_POWER_DELEGATED(); + bool testFromBefore = isDelegatingVoting[from]; + bool testToBefore = isDelegatingVoting[to]; + //require !delegatingVotingState(stateFromBefore) && !delegatingVotingState(stateToBefore); + + transferFrom(e, from, to, amount); + + uint8 stateFromAfter = getDelegationState(from); + uint8 stateToAfter = getDelegationState(to); + bool testFromAfter = isDelegatingVoting[from]; + bool testToAfter = isDelegatingVoting[to]; + + assert testFromBefore == testFromAfter && testToBefore == testToAfter; +} From 1bee65b9282730cc8cef86845d49e7a045a513b7 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Sun, 7 Aug 2022 18:31:38 +0300 Subject: [PATCH 36/58] clean up --- certora/README.md | 4 + certora/harness/AaveTokenV3Harness.sol | 7 + certora/harness/AaveTokenV3HarnessStorage.sol | 11 ++ certora/scripts/runComplexity.sh | 7 - certora/scripts/runRepro.sh | 15 -- certora/scripts/verifyDelegate.sh | 1 + certora/specs/base.spec | 40 ++++- certora/specs/complexity.spec | 103 ----------- certora/specs/delegate.spec | 25 +++ certora/specs/general.spec | 24 +-- certora/specs/repro.spec | 162 ------------------ 11 files changed, 91 insertions(+), 308 deletions(-) create mode 100644 certora/README.md delete mode 100755 certora/scripts/runComplexity.sh delete mode 100644 certora/scripts/runRepro.sh delete mode 100644 certora/specs/complexity.spec delete mode 100644 certora/specs/repro.spec diff --git a/certora/README.md b/certora/README.md new file mode 100644 index 0000000..f97371b --- /dev/null +++ b/certora/README.md @@ -0,0 +1,4 @@ +## running instructions + +// take the template from from certora/governance-crosschain-bridge +https://github.com/Certora/governance-crosschain-bridges/tree/l2-bridges-audit/certora \ No newline at end of file diff --git a/certora/harness/AaveTokenV3Harness.sol b/certora/harness/AaveTokenV3Harness.sol index 939d691..1f4d2df 100644 --- a/certora/harness/AaveTokenV3Harness.sol +++ b/certora/harness/AaveTokenV3Harness.sol @@ -1,4 +1,11 @@ // SPDX-License-Identifier: MIT + +/** + + This is an extension of the AaveTokenV3 with added getters on the _balances fields + + */ + pragma solidity ^0.8.0; import {AaveTokenV3} from "../../src/AaveTokenV3.sol"; diff --git a/certora/harness/AaveTokenV3HarnessStorage.sol b/certora/harness/AaveTokenV3HarnessStorage.sol index fb8f621..78ef106 100644 --- a/certora/harness/AaveTokenV3HarnessStorage.sol +++ b/certora/harness/AaveTokenV3HarnessStorage.sol @@ -1,4 +1,15 @@ // SPDX-License-Identifier: MIT + +/** + + This is an extension of the harnessed AaveTokenV3 with added getters on the _balances fields. + The imported harnessed AaveTokenV3 contract uses uint8 instead of an enum for delegation state. + + This modification is introduced to bypass a current Certora Prover limitation on accessing + enum fields inside CVL hooks + + */ + pragma solidity ^0.8.0; import {AaveTokenV3} from "./storage_harness/AaveTokenV3.sol"; diff --git a/certora/scripts/runComplexity.sh b/certora/scripts/runComplexity.sh deleted file mode 100755 index 03cffd3..0000000 --- a/certora/scripts/runComplexity.sh +++ /dev/null @@ -1,7 +0,0 @@ -certoraRun src/AaveTokenV3.sol:AaveTokenV3 \ - --verify AaveTokenV3:certora/specs/complexity.spec \ - --solc solc8.13 \ - --optimistic_loop \ - --staging \ - --msg "AaveTokenV3 complexity check" - \ No newline at end of file diff --git a/certora/scripts/runRepro.sh b/certora/scripts/runRepro.sh deleted file mode 100644 index b0acef1..0000000 --- a/certora/scripts/runRepro.sh +++ /dev/null @@ -1,15 +0,0 @@ -if [[ "$1" ]] -then - RULE="--rule $1" -fi - -certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3Harness \ - --verify AaveTokenV3Harness:certora/specs/repro.spec \ - $RULE \ - --solc solc8.13 \ - --optimistic_loop \ - --settings -smt_bitVectorTheory=true \ - --send_only \ - --staging \ - --msg "AaveTokenV3:repro.spec $1" - \ No newline at end of file diff --git a/certora/scripts/verifyDelegate.sh b/certora/scripts/verifyDelegate.sh index 48d5059..0e6cd58 100755 --- a/certora/scripts/verifyDelegate.sh +++ b/certora/scripts/verifyDelegate.sh @@ -11,4 +11,5 @@ certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3Harness \ --send_only \ --staging \ --msg "AaveTokenV3Harness:delegate.spec $1" +# --sanity \ No newline at end of file diff --git a/certora/specs/base.spec b/certora/specs/base.spec index 13c18ef..90dc5af 100644 --- a/certora/specs/base.spec +++ b/certora/specs/base.spec @@ -1,3 +1,16 @@ +/* + This is a base spec file that includes methods declarations, definitions + and functions to be included in other spec. There are no rules in this file. + For more information, visit: https://www.certora.com/ + +*/ + +/** + + Declaration of methods of the Aave token contract (and harness) + +*/ + methods { totalSupply() returns (uint256) envfree balanceOf(address) returns (uint256) envfree @@ -27,6 +40,30 @@ definition VOTING_POWER() returns uint8 = 0; definition PROPOSITION_POWER() returns uint8 = 1; definition DELEGATED_POWER_DIVIDER() returns uint256 = 10^10; +/** + + Definitions of delegation states + +*/ +definition NO_DELEGATION() returns uint8 = 0; +definition VOTING_DELEGATED() returns uint8 = 1; +definition PROPOSITION_DELEGATED() returns uint8 = 2; +definition FULL_POWER_DELEGATED() returns uint8 = 3; +definition DELEGATING_VOTING(uint8 state) returns bool = + state == VOTING_DELEGATED() || state == FULL_POWER_DELEGATED(); +definition DELEGATING_PROPOSITION(uint8 state) returns bool = + state == PROPOSITION_DELEGATED() || state == FULL_POWER_DELEGATED(); + +definition AAVE_MAX_SUPPLY() returns uint256 = 16000000 * 10^18; +definition SCALED_MAX_SUPPLY() returns uint256 = AAVE_MAX_SUPPLY() / DELEGATED_POWER_DIVIDER(); + + +/** + + Functions + +*/ + function normalize(uint256 amount) returns uint256 { return to_uint256(amount / DELEGATED_POWER_DIVIDER() * DELEGATED_POWER_DIVIDER()); } @@ -38,6 +75,3 @@ function validDelegationState(address user) returns bool { function validAmount(uint256 amt) returns bool { return amt < AAVE_MAX_SUPPLY(); } - -definition AAVE_MAX_SUPPLY() returns uint256 = 16000000 * 10^18; -definition SCALED_MAX_SUPPLY() returns uint256 = AAVE_MAX_SUPPLY() / DELEGATED_POWER_DIVIDER(); \ No newline at end of file diff --git a/certora/specs/complexity.spec b/certora/specs/complexity.spec deleted file mode 100644 index 40a009a..0000000 --- a/certora/specs/complexity.spec +++ /dev/null @@ -1,103 +0,0 @@ -// import "erc20.spec" - -rule sanity(method f) -{ - env e; - calldataarg args; - f(e,args); - assert false; -} - - -/* -This rule find which functions never reverts. - -*/ - - -rule noRevert(method f) -description "$f has reverting paths" -{ - env e; - calldataarg arg; - require e.msg.value == 0; - f@withrevert(e, arg); - assert !lastReverted, "${f.selector} can revert"; -} - - -rule alwaysRevert(method f) -description "$f has reverting paths" -{ - env e; - calldataarg arg; - f@withrevert(e, arg); - assert lastReverted, "${f.selector} succeeds"; -} - - -/* -This rule find which functions that can be called, may fail due to someone else calling a function right before. - -This is n expensive rule - might fail on the demo site on big contracts -*/ - -rule simpleFrontRunning(method f, address privileged) filtered { f-> !f.isView } -description "$f can no longer be called after it had been called by someone else" -{ - env e1; - calldataarg arg; - require e1.msg.sender == privileged; - - storage initialStorage = lastStorage; - f(e1, arg); - bool firstSucceeded = !lastReverted; - - env e2; - calldataarg arg2; - require e2.msg.sender != e1.msg.sender; - f(e2, arg2) at initialStorage; - f@withrevert(e1, arg); - bool succeeded = !lastReverted; - - assert succeeded, "${f.selector} can be not be called if was called by someone else"; -} - - -/* -This rule find which functions are privileged. -A function is privileged if there is only one address that can call it. - -The rules finds this by finding which functions can be called by two different users. - -*/ - - -rule privilegedOperation(method f, address privileged) -description "$f can be called by more than one user without reverting" -{ - env e1; - calldataarg arg; - require e1.msg.sender == privileged; - - storage initialStorage = lastStorage; - f@withrevert(e1, arg); // privileged succeeds executing candidate privileged operation. - bool firstSucceeded = !lastReverted; - - env e2; - calldataarg arg2; - require e2.msg.sender != privileged; - f@withrevert(e2, arg2) at initialStorage; // unprivileged - bool secondSucceeded = !lastReverted; - - assert !(firstSucceeded && secondSucceeded), "${f.selector} can be called by both ${e1.msg.sender} and ${e2.msg.sender}, so it is not privileged"; -} - -// rule whoChangedBalanceOf(method f, address u) { -// env eB; -// env eF; -// calldataarg args; -// uint256 before = balanceOf(eB, u); -// f(eF,args); -// assert balanceOf(eB, u) == before, "balanceOf changed"; -// } \ No newline at end of file diff --git a/certora/specs/delegate.spec b/certora/specs/delegate.spec index 76da77c..39ee2cd 100644 --- a/certora/specs/delegate.spec +++ b/certora/specs/delegate.spec @@ -707,3 +707,28 @@ rule delegationTypeIndependence(address who, method f) filtered { f -> !f.isView ), "one delegatee type stays the same, unless delegate or delegateBySig was called"; } +rule cantDelegateTwice(address _delegate) { + env e; + + address delegateBefore = getVotingDelegate(e.msg.sender); + require delegateBefore != _delegate && delegateBefore != e.msg.sender && delegateBefore != 0; + require _delegate != e.msg.sender && _delegate != 0 && e.msg.sender != 0; + require getDelegationState(e.msg.sender) == FULL_POWER_DELEGATED(); + + uint256 votingPowerBefore = getPowerCurrent(_delegate, VOTING_POWER()); + uint256 propPowerBefore = getPowerCurrent(_delegate, PROPOSITION_POWER()); + + delegate(e, _delegate); + + uint256 votingPowerAfter = getPowerCurrent(_delegate, VOTING_POWER()); + uint256 propPowerAfter = getPowerCurrent(_delegate, PROPOSITION_POWER()); + + delegate(e, _delegate); + + uint256 votingPowerAfter2 = getPowerCurrent(_delegate, VOTING_POWER()); + uint256 propPowerAfter2 = getPowerCurrent(_delegate, PROPOSITION_POWER()); + + assert votingPowerAfter == votingPowerBefore + normalize(balanceOf(e.msg.sender)); + assert propPowerAfter == propPowerBefore + normalize(balanceOf(e.msg.sender)); + assert votingPowerAfter2 == votingPowerAfter && propPowerAfter2 == propPowerAfter; +} \ No newline at end of file diff --git a/certora/specs/general.spec b/certora/specs/general.spec index cee8cb3..42d12cd 100644 --- a/certora/specs/general.spec +++ b/certora/specs/general.spec @@ -10,20 +10,6 @@ import "base.spec" -/** - - Definitions of delegation states - -*/ -definition NO_DELEGATION() returns uint8 = 0; -definition VOTING_DELEGATED() returns uint8 = 1; -definition PROPOSITION_DELEGATED() returns uint8 = 2; -definition FULL_POWER_DELEGATED() returns uint8 = 3; -definition DELEGATING_VOTING(uint8 state) returns bool = - state == VOTING_DELEGATED() || state == FULL_POWER_DELEGATED(); -definition DELEGATING_PROPOSITION(uint8 state) returns bool = - state == PROPOSITION_DELEGATED() || state == FULL_POWER_DELEGATED(); - /** @@ -71,7 +57,6 @@ ghost mapping(address => uint104) balances { init_state axiom forall address a. balances[a] == 0; } - /** Hooks @@ -79,7 +64,6 @@ ghost mapping(address => uint104) balances { */ - /** This hook updates the sum of delegated and undelegated balances on each change of delegation state. @@ -91,6 +75,8 @@ hook Sstore _balances[KEY address user].delegationState uint8 new_state (uint8 o bool willDelegateP = !DELEGATING_PROPOSITION(old_state) && DELEGATING_PROPOSITION(new_state); bool wasDelegatingP = DELEGATING_PROPOSITION(old_state) && !DELEGATING_PROPOSITION(new_state); + + // we cannot use if statements inside hooks, hence the ternary operator sumUndelegatedBalancesP = willDelegateP ? (sumUndelegatedBalancesP - balances[user]) : sumUndelegatedBalancesP; sumUndelegatedBalancesP = wasDelegatingP ? (sumUndelegatedBalancesP + balances[user]) : sumUndelegatedBalancesP; sumDelegatedBalancesP = willDelegateP ? (sumDelegatedBalancesP + balances[user]) : sumDelegatedBalancesP; @@ -126,6 +112,7 @@ hook Sstore _balances[KEY address user].delegationState uint8 new_state (uint8 o */ hook Sstore _balances[KEY address user].balance uint104 balance (uint104 old_balance) STORAGE { balances[user] = balances[user] - old_balance + balance; + // we cannot use if statements inside hooks, hence the ternary operator sumDelegatedBalancesV = isDelegatingVoting[user] ? sumDelegatedBalancesV + to_mathint(balance) - to_mathint(old_balance) : sumDelegatedBalancesV; @@ -138,8 +125,10 @@ hook Sstore _balances[KEY address user].balance uint104 balance (uint104 old_bal sumUndelegatedBalancesP = !isDelegatingProposition[user] ? sumUndelegatedBalancesP + to_mathint(balance) - to_mathint(old_balance) : sumUndelegatedBalancesP; + } + /* @Rule @@ -191,7 +180,6 @@ invariant sumOfVBalancesCorrectness() sumDelegatedBalancesV + sumUndelegatedBala */ invariant sumOfPBalancesCorrectness() sumDelegatedBalancesP + sumUndelegatedBalancesP == totalSupply() - /* @Rule @@ -204,7 +192,7 @@ invariant sumOfPBalancesCorrectness() sumDelegatedBalancesP + sumUndelegatedBala @Link: */ -rule testTransfer() { +rule transferDoesntChangeDelegationState() { env e; address from; address to; uint amount; diff --git a/certora/specs/repro.spec b/certora/specs/repro.spec deleted file mode 100644 index f57407a..0000000 --- a/certora/specs/repro.spec +++ /dev/null @@ -1,162 +0,0 @@ -/* - -This spec is for reproducing particular storage splitter issues -in the prover. - -*/ - -import "base.spec" - -definition NO_DELEGATION() returns uint = 0; -definition VOTING_DELEGATED() returns uint = 1; -definition PROPOSITION_DELEGATED() returns uint = 2; -definition FULL_POWER_DELEGATED() returns uint = 3; - -// 1 byte -definition DelegationState(uint256 packed) returns uint256 = - (packed & 0xff00000000000000000000000000000000000000000000000000000000000000) >> 248; - -// definition DelegationState(uint256 packed) returns uint256 = -// (packed & 0xff); - -function delegatingVotingState(uint state) returns bool { - return state == VOTING_DELEGATED() || state == FULL_POWER_DELEGATED(); -} - -ghost mathint sumDelegatedProposition { - init_state axiom sumDelegatedProposition == 0; -} - -ghost mathint sumBalances { - init_state axiom sumBalances == 0; -} - - -/* - @Rule - - @Description: - User's delegation flag is switched on iff user is delegating to an address - other than his own own or 0 - - @Notes: - - - @Link: - -*/ -invariant delegateCorrectness(address user) - ((getVotingDelegate(user) == user || getVotingDelegate(user) == 0) <=> !getDelegatingVoting(user)) - && - ((getPropositionDelegate(user) == user || getPropositionDelegate(user) == 0) <=> !getDelegatingProposition(user)) - { - preserved { - require getDelegationState(user) <= FULL_POWER_DELEGATED(); - } - } -/* - -Invariant that proves sum of all balances is equal to sum of delegated and -undelegated balances. - -1. Ghost to track delegation state of each acc -2. Ghost to track delegated balances sum -3. Ghost to track undelegated balances sum -4. Ghost to track balances of each account -5. On each write to balance, check the ghost for delegation state and update either non deleg or deleg -6. On each write to delegation flag, move the balance from deleg to non deleg or the other way around -*/ - -// 1. -ghost mapping(address => bool) isDelegatingVoting { - init_state axiom forall address a. isDelegatingVoting[a] == false; -} - -// 2. -ghost mathint sumDelegatedBalances { - init_state axiom sumDelegatedBalances == 0; -} - -// 3. -ghost mathint sumUndelegatedBalances { - init_state axiom sumUndelegatedBalances == 0; -} - -// 4. -ghost mapping(address => uint104) balances { - init_state axiom forall address a. balances[a] == 0; -} - -hook Sstore _balances[KEY address user].(offset 0) uint256 packed STORAGE { - uint256 old_state = DelegationState(packed); - uint256 new_state = DelegationState(packed); - bool willDelegate = ((old_state == NO_DELEGATION() || old_state == PROPOSITION_DELEGATED()) && - (new_state == VOTING_DELEGATED() || new_state == FULL_POWER_DELEGATED())); - bool wasDelegating = ((old_state == VOTING_DELEGATED() || old_state == FULL_POWER_DELEGATED()) && - (new_state == NO_DELEGATION() || new_state == PROPOSITION_DELEGATED())); - sumUndelegatedBalances = willDelegate ? (sumUndelegatedBalances - balances[user]) : sumUndelegatedBalances; - sumUndelegatedBalances = wasDelegating ? (sumUndelegatedBalances + balances[user]) : sumUndelegatedBalances; - sumDelegatedBalances = willDelegate ? (sumDelegatedBalances + balances[user]) : sumDelegatedBalances; - sumDelegatedBalances = wasDelegating ? (sumDelegatedBalances - balances[user]) : sumDelegatedBalances; - - // change the delegating state only if a change is stored - - isDelegatingVoting[user] = new_state == old_state - ? isDelegatingVoting[user] - : new_state == VOTING_DELEGATED() || new_state == FULL_POWER_DELEGATED(); - -} - - -hook Sstore _balances[KEY address user].balance uint104 balance (uint104 old_balance) STORAGE { - balances[user] = balances[user] - old_balance + balance; - sumDelegatedBalances = isDelegatingVoting[user] - ? sumDelegatedBalances + to_mathint(balance) - to_mathint(old_balance) - : sumDelegatedBalances; - sumUndelegatedBalances = !isDelegatingVoting[user] - ? sumUndelegatedBalances + to_mathint(balance) - to_mathint(old_balance) - : sumUndelegatedBalances; -} - -invariant sumOfBalancesCorrectness() sumDelegatedBalances + sumUndelegatedBalances == totalSupply() - - -rule whochanged(method f, address user) { - env e; - calldataarg args; - - bool testBefore = isDelegatingVoting[user]; - address v_delegate = getVotingDelegate(user); - address p_delegate = getPropositionDelegate(user); - uint8 stateBefore = getDelegationState(user); - require testBefore == delegatingVotingState(stateBefore); - - f(e, args); - bool testAfter = isDelegatingVoting[user]; - uint8 stateAfter = getDelegationState(user); - address v_delegateAfter = getVotingDelegate(user); - - assert testBefore == testAfter; -} - -rule testTransfer() { - env e; - address from; address to; - uint amount; - - uint8 stateFromBefore = getDelegationState(from); - uint8 stateToBefore = getDelegationState(to); - require stateFromBefore <= FULL_POWER_DELEGATED() && stateToBefore <= FULL_POWER_DELEGATED(); - bool testFromBefore = isDelegatingVoting[from]; - bool testToBefore = isDelegatingVoting[to]; - //require !delegatingVotingState(stateFromBefore) && !delegatingVotingState(stateToBefore); - - transferFrom(e, from, to, amount); - - uint8 stateFromAfter = getDelegationState(from); - uint8 stateToAfter = getDelegationState(to); - bool testFromAfter = isDelegatingVoting[from]; - bool testToAfter = isDelegatingVoting[to]; - - assert testFromBefore == testFromAfter && testToBefore == testToAfter; -} From b54110b17153b77dfdaea8087df016596cd2521e Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Sun, 7 Aug 2022 18:45:32 +0300 Subject: [PATCH 37/58] fix cantDelegateTwice rule --- certora/specs/delegate.spec | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/certora/specs/delegate.spec b/certora/specs/delegate.spec index 39ee2cd..231ca09 100644 --- a/certora/specs/delegate.spec +++ b/certora/specs/delegate.spec @@ -707,11 +707,24 @@ rule delegationTypeIndependence(address who, method f) filtered { f -> !f.isView ), "one delegatee type stays the same, unless delegate or delegateBySig was called"; } +/* + @Rule + + @Description: + Verifies that delegating twice to the same delegate changes the delegate's + voting power only once. + + @Note: + + @Link: +*/ rule cantDelegateTwice(address _delegate) { env e; - address delegateBefore = getVotingDelegate(e.msg.sender); - require delegateBefore != _delegate && delegateBefore != e.msg.sender && delegateBefore != 0; + address delegateBeforeV = getVotingDelegate(e.msg.sender); + address delegateBeforeP = getPropositionDelegate(e.msg.sender); + require delegateBeforeV != _delegate && delegateBeforeV != e.msg.sender && delegateBeforeV != 0; + require delegateBeforeP != _delegate && delegateBeforeP != e.msg.sender && delegateBeforeP != 0; require _delegate != e.msg.sender && _delegate != 0 && e.msg.sender != 0; require getDelegationState(e.msg.sender) == FULL_POWER_DELEGATED(); From dde0954b889ab9dfa9e9ddce3c8b4147af9ff189 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Wed, 10 Aug 2022 09:43:47 +0300 Subject: [PATCH 38/58] add README --- certora/README.md | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/certora/README.md b/certora/README.md index f97371b..5891255 100644 --- a/certora/README.md +++ b/certora/README.md @@ -1,4 +1,42 @@ -## running instructions +## Verification Overview +The current directory contains Certora's formal verification of AAVE's V3 token. +In this directory you will find three subdirectories: -// take the template from from certora/governance-crosschain-bridge -https://github.com/Certora/governance-crosschain-bridges/tree/l2-bridges-audit/certora \ No newline at end of file +1. specs - Contains all the specification files that were written by Certora for the token code. We have created two spec files, `base.spec`, `delegate.spec`, `erc20.spec` and `general.spec`. +- `base.spec` contains method declarations, CVL functions and definitions used by the main specification files +- `delegate.spec` contains rules that prove various delegation properties +- `erc20.spec` contains rules that prove ERC20 general rules, e.g. correctness of transfer and others +- `general.spec` contains general delegation invariants, e.g. sum of delegated and undelegated balances equals to +total supply + +2. scripts - Contains all the necessary run scripts to execute the spec files on the Certora Prover. These scripts composed of a run command of Certora Prover, contracts to take into account in the verification context, declaration of the compiler and a set of additional settings. +- `verifyDelegate.sh` is a script for running `delegate.spec` +- `verifyGeneral.sh` is a script for running `general.spec` +- `erc20.sh` is a script for running `erc20.spec` + +3. harness - Contains all the inheriting contracts that add/simplify functionalities to the original contract. +We use two harnesses: +- `AaveTokenV3Harness.sol` used by `erc20.sh` and `delegate.sh`. It inherits from the original AaveV3Token +contract and adds a few getter functions. + +- `AaveTokenV3HarnessStorage.sol` used by `general.sh`. It uses a modified a version of AaveV3Token contract +where the `delegationState` field of the `_balances` struct is refactored to be of type uint8 instead of +`DelegationState` enum. This is done in order to bypass a current limitation of the tool and write a hook +on the `delegationState` field (hooks don't support enum fields at the moment) + + +
+ +--- + +## Running Instructions +To run a verification job: + +1. Open terminal and `cd` your way to the main aave-token-v3 directory + +2. Run the script you'd like to get results for: + ``` + sh certora/scripts/verifyDelegate.sh + sh certora/scripts/verifyGeneral.sh + sh certora/scripts/erc20.sh + ``` From b228950fe41c39f1c1cec092d2e6ac2a9fb007be Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Wed, 10 Aug 2022 10:27:33 +0300 Subject: [PATCH 39/58] update sanity runs --- certora/scripts/erc20.sh | 1 - certora/scripts/verifyDelegate.sh | 1 - certora/scripts/verifyGeneral.sh | 1 - certora/specs/delegate.spec | 2 ++ certora/specs/erc20.spec | 3 +++ certora/specs/general.spec | 2 ++ 6 files changed, 7 insertions(+), 3 deletions(-) diff --git a/certora/scripts/erc20.sh b/certora/scripts/erc20.sh index 9f0aeac..e6c094b 100644 --- a/certora/scripts/erc20.sh +++ b/certora/scripts/erc20.sh @@ -9,6 +9,5 @@ certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3Harness \ --solc solc8.13 \ --optimistic_loop \ --send_only \ - --staging \ --msg "AaveTokenV3:erc20.spec $1" \ No newline at end of file diff --git a/certora/scripts/verifyDelegate.sh b/certora/scripts/verifyDelegate.sh index 0e6cd58..e6169f1 100755 --- a/certora/scripts/verifyDelegate.sh +++ b/certora/scripts/verifyDelegate.sh @@ -9,7 +9,6 @@ certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3Harness \ --solc solc8.13 \ --optimistic_loop \ --send_only \ - --staging \ --msg "AaveTokenV3Harness:delegate.spec $1" # --sanity \ No newline at end of file diff --git a/certora/scripts/verifyGeneral.sh b/certora/scripts/verifyGeneral.sh index 39b9557..872c680 100644 --- a/certora/scripts/verifyGeneral.sh +++ b/certora/scripts/verifyGeneral.sh @@ -9,7 +9,6 @@ certoraRun certora/harness/AaveTokenV3HarnessStorage.sol:AaveTokenV3Harness \ --solc solc8.13 \ --optimistic_loop \ --settings -smt_bitVectorTheory=true \ - --staging \ --send_only \ --msg "AaveTokenV3:general.spec $1" \ No newline at end of file diff --git a/certora/specs/delegate.spec b/certora/specs/delegate.spec index 231ca09..1d95e69 100644 --- a/certora/specs/delegate.spec +++ b/certora/specs/delegate.spec @@ -7,6 +7,8 @@ This file is run with scripts/verifyDelegate.sh On AaveTokenV3Harness.sol + Sanity check results: https://prover.certora.com/output/67509/021f59de6995d82ecf18/?anonymousKey=84f18dc61532a37fabfd59655fe7fe43989f1a8e + */ import "base.spec" diff --git a/certora/specs/erc20.spec b/certora/specs/erc20.spec index 067439d..66b780f 100644 --- a/certora/specs/erc20.spec +++ b/certora/specs/erc20.spec @@ -6,6 +6,9 @@ This file is run with scripts/erc20.sh On the token harness AaveTokenV3Harness.sol + Sanity run: + https://prover.certora.com/output/67509/fc50dbfcd1528d8549de/?anonymousKey=b3cf3650b69574655b0e4fc80a019677904a3377 + */ import "base.spec" diff --git a/certora/specs/general.spec b/certora/specs/general.spec index 42d12cd..39ec616 100644 --- a/certora/specs/general.spec +++ b/certora/specs/general.spec @@ -6,6 +6,8 @@ This file is run with scripts/verifyGeneral.sh On a version with some minimal code modifications AaveTokenV3HarnessStorage.sol + + Sanity check results: https://prover.certora.com/output/67509/8cee7c95432ede6b3f9f/?anonymousKey=78d297585a2b2edc38f6c513e0ce12df10e47b82 */ import "base.spec" From 2c80326a31422520d497ef3f5570782772aa17d6 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Wed, 10 Aug 2022 11:49:03 +0300 Subject: [PATCH 40/58] fix: bug in transferCorrect rule --- certora/specs/erc20.spec | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/certora/specs/erc20.spec b/certora/specs/erc20.spec index 66b780f..968d8e7 100644 --- a/certora/specs/erc20.spec +++ b/certora/specs/erc20.spec @@ -158,11 +158,9 @@ rule transferCorrect(address to, uint256 amount) { require validDelegationState(e.msg.sender) && validDelegationState(to); require ! ( (getDelegatingVoting(to) && v_delegateTo == to) || (getDelegatingProposition(to) && p_delegateTo == to)); + // to not overcomplicate the constraints on dvbTo and dvbFrom - require v_delegateFrom != v_delegateTo; - - // for testing this specific scenario - require v_delegateFrom == v_delegateTo && p_delegateFrom != p_delegateTo; + require v_delegateFrom != v_delegateTo && p_delegateFrom != p_delegateTo; transfer@withrevert(e, to, amount); bool reverted = lastReverted; From 2a7e41718e50e344ea8d7cc39a1b6fb08b56b268 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Thu, 11 Aug 2022 14:46:02 +0300 Subject: [PATCH 41/58] clean up comments --- certora/specs/erc20.spec | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/certora/specs/erc20.spec b/certora/specs/erc20.spec index 968d8e7..f77eba9 100644 --- a/certora/specs/erc20.spec +++ b/certora/specs/erc20.spec @@ -7,8 +7,7 @@ On the token harness AaveTokenV3Harness.sol Sanity run: - https://prover.certora.com/output/67509/fc50dbfcd1528d8549de/?anonymousKey=b3cf3650b69574655b0e4fc80a019677904a3377 - + https://prover.certora.com/output/67509/a5d16a31a49b9c9a7b71/?anonymousKey=bd108549122fd97450428a26c4ed52458793b898 */ import "base.spec" @@ -126,10 +125,6 @@ rule noFeeOnTransfer(address bob, uint256 amount) { */ -// for this rule need to prove that delegatedBalance of a delegate is >= any delegator balance - -// what if v_delegateFrom == v_delegateTo? - rule transferCorrect(address to, uint256 amount) { env e; require e.msg.value == 0 && e.msg.sender != 0; @@ -191,7 +186,7 @@ rule transferCorrect(address to, uint256 amount) { } < transferFrom(from, to, amount) - >tr + > { lastreverted => to = 0 || amount > balanceOf(from) !lastreverted => balanceOf(to) = balanceToBefore + amount && From 1dca850277f8be4ad0d55f399a5e5d20b60867fc Mon Sep 17 00:00:00 2001 From: Michael M <91594326+MichaelMorami@users.noreply.github.com> Date: Sun, 14 Aug 2022 12:49:17 +0300 Subject: [PATCH 42/58] adding the git ignore --- certora/.gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 certora/.gitignore diff --git a/certora/.gitignore b/certora/.gitignore new file mode 100644 index 0000000..e47c83b --- /dev/null +++ b/certora/.gitignore @@ -0,0 +1,5 @@ +# certora +.certora* +.certora*.json +**.last_conf* +certora-logs From bd5474c8d2535d96df662fa0c2a577e4d320dd54 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Sun, 11 Sep 2022 17:57:59 +0300 Subject: [PATCH 43/58] Add community spec --- .../harness/AaveTokenV3HarnessCommunity.sol | 106 ++++++ certora/scripts/verifyCommunity.sh | 14 + certora/specs/community.spec | 329 ++++++++++++++++++ 3 files changed, 449 insertions(+) create mode 100644 certora/harness/AaveTokenV3HarnessCommunity.sol create mode 100644 certora/scripts/verifyCommunity.sh create mode 100644 certora/specs/community.spec diff --git a/certora/harness/AaveTokenV3HarnessCommunity.sol b/certora/harness/AaveTokenV3HarnessCommunity.sol new file mode 100644 index 0000000..ace06bf --- /dev/null +++ b/certora/harness/AaveTokenV3HarnessCommunity.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT + +/** + + This is an extension of the AaveTokenV3 with added getters and view function wrappers needed for + community-written rules + */ + +pragma solidity ^0.8.0; + +import {AaveTokenV3} from "../../src/AaveTokenV3.sol"; + +contract AaveTokenV3Harness is AaveTokenV3 { + function getBalance(address user) view public returns (uint104) { + return _balances[user].balance; + } + + function getDelegatedPropositionBalance(address user) view public returns (uint72) { + return _balances[user].delegatedPropositionBalance; + } + + + function getDelegatedVotingBalance(address user) view public returns (uint72) { + return _balances[user].delegatedVotingBalance; + } + + + function getDelegatingProposition(address user) view public returns (bool) { + return _balances[user].delegationState == DelegationState.PROPOSITION_DELEGATED || + _balances[user].delegationState == DelegationState.FULL_POWER_DELEGATED; + } + + + function getDelegatingVoting(address user) view public returns (bool) { + return _balances[user].delegationState == DelegationState.VOTING_DELEGATED || + _balances[user].delegationState == DelegationState.FULL_POWER_DELEGATED; + } + + function getVotingDelegate(address user) view public returns (address) { + return _votingDelegateeV2[user]; + } + + function getPropositionDelegate(address user) view public returns (address) { + return _propositionDelegateeV2[user]; + } + + function getDelegationState(address user) view public returns (DelegationState) { + return _balances[user].delegationState; + } + + function getNonce(address user) view public returns (uint256) { + return _nonces[user]; + } + + function ecrecoverWrapper( + bytes32 hash, + uint8 v, + bytes32 r, + bytes32 s + ) public pure returns (address) { + return ecrecover(hash, v, r, s); + } + + function computeMetaDelegateHash( + address delegator, + address delegatee, + uint256 deadline, + uint256 nonce + ) public view returns (bytes32) { + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256(abi.encode(DELEGATE_TYPEHASH, delegator, delegatee, nonce, deadline)) + ) + ); + return digest; + } + + function computeMetaDelegateByTypeHash( + address delegator, + address delegatee, + GovernancePowerType delegationType, + uint256 deadline, + uint256 nonce + ) public view returns (bytes32) { + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256( + abi.encode( + DELEGATE_BY_TYPE_TYPEHASH, + delegator, + delegatee, + delegationType, + nonce, + deadline + ) + ) + ) + ); + return digest; + } + +} \ No newline at end of file diff --git a/certora/scripts/verifyCommunity.sh b/certora/scripts/verifyCommunity.sh new file mode 100644 index 0000000..fd5c867 --- /dev/null +++ b/certora/scripts/verifyCommunity.sh @@ -0,0 +1,14 @@ +if [[ "$1" ]] +then + RULE="--rule $1" +fi + +certoraRun certora/harness/AaveTokenV3HarnessCommunity.sol:AaveTokenV3Harness \ + --verify AaveTokenV3Harness:certora/specs/community.spec \ + $RULE \ + --solc solc8.13 \ + --optimistic_loop \ + --send_only \ + --msg "AaveTokenV3HarnessCommunity:community.spec $1" +# --sanity + \ No newline at end of file diff --git a/certora/specs/community.spec b/certora/specs/community.spec new file mode 100644 index 0000000..757b079 --- /dev/null +++ b/certora/specs/community.spec @@ -0,0 +1,329 @@ +import "base.spec" + +methods { + ecrecoverWrapper(bytes32 digest, uint8 v, bytes32 r, bytes32 s) returns (address) envfree + computeMetaDelegateHash(address delegator, address delegatee, uint256 deadline, uint256 nonce) returns (bytes32) envfree + computeMetaDelegateByTypeHash(address delegator, address delegatee, uint8 delegationType, uint256 deadline, uint256 nonce) returns (bytes32) envfree + _nonces(address addr) returns (uint256) envfree + getNonce(address addr) returns (uint256) envfree +} + +definition ZERO_ADDRESS() returns address = 0; + +/** + Integrity of permit function + Successful permit function increases the nonce of owner by 1 and also changes the allowance of owner to spender + + Written by parth-15 +*/ +rule permitIntegrity() { + env e; + address owner; + address spender; + uint256 value; + uint256 deadline; + uint8 v; + bytes32 r; + bytes32 s; + + uint256 allowanceBefore = allowance(owner, spender); + mathint nonceBefore = getNonce(owner); + + //checking this because function is using unchecked math and such a high nonce is unrealistic + require nonceBefore < max_uint; + + permit(e, owner, spender, value, deadline, v, r, s); + + uint256 allowanceAfter = allowance(owner, spender); + mathint nonceAfter = getNonce(owner); + + assert allowanceAfter == value, "permit increases allowance of owner to spender on success"; + assert nonceAfter == nonceBefore + 1, "successful call to permit function increases nonce of owner by 1"; +} + +/** + The delegator can always revoke his voting power only by calling delegating functions + + Written by Elpacos + +*/ +rule checkRevokingVotingPower(address someone, method f) { + env e; + calldataarg args; + + //store voting delegating state before + bool delegatingStateBefore = getDelegatingVoting(someone); + //transacction + f(e, args); + //store voting delegating state after + bool delegatingStateAfter = getDelegatingVoting(someone); + + assert (delegatingStateBefore => (delegatingStateAfter || !delegatingStateAfter)); + //assert that any delagation granted can be rovoked or changed to another delegatee using delegate functions + assert ((delegatingStateBefore && !delegatingStateAfter )=> + f.selector == delegate(address).selector || + f.selector == delegateByType(address,uint8).selector || + f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector || + f.selector == metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32).selector); +} + +/** + The delegator can always revoke his proposition power only by calling delegating functions + + Written by Elpacos + +*/ +rule checkRevokingPropositionPower(address someone, method f) { + env e; + calldataarg args; + + //store proposition delegating state before + bool delegatingStateBefore = getDelegatingProposition(someone); + //transacction + f(e, args); + //store proposition delegating state after + bool delegatingStateAfter = getDelegatingProposition(someone); + + assert (delegatingStateBefore => (delegatingStateAfter || !delegatingStateAfter)); + //assert that any delagation granted can be rovoked or changed to another delegatee using delegate functions + assert ((delegatingStateBefore && !delegatingStateAfter )=> + f.selector == delegate(address).selector || + f.selector == delegateByType(address,uint8).selector || + f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector || + f.selector == metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32).selector); +} + +/** + + Address 0 has no voting power and no proposition power + + Written by JayP11 +*/ +invariant addressZeroNoPower() + getPowerCurrent(0, VOTING_POWER()) == 0 && getPowerCurrent(0, PROPOSITION_POWER()) == 0 && balanceOf(0) == 0 + + +/** + * Check `metaDelegateByType` can only be called with a signed request. + + Written by kustosz + */ +rule metaDelegateByTypeOnlyCallableWithProperlySignedArguments(env e, address delegator, address delegatee, uint8 delegationType, uint256 deadline, uint8 v, bytes32 r, bytes32 s) { + require ecrecoverWrapper(computeMetaDelegateByTypeHash(delegator, delegatee, delegationType, deadline, _nonces(delegator)), v, r, s) != delegator; + metaDelegateByType@withrevert(e, delegator, delegatee, delegationType, deadline, v, r, s); + assert lastReverted; +} + +/** + * Check that it's impossible to use the same arguments to call `metaDalegate` twice. + + Written by kustosz + */ +rule metaDelegateNonRepeatable(env e1, env e2, address delegator, address delegatee, uint256 deadline, uint8 v, bytes32 r, bytes32 s) { + uint256 nonce = _nonces(delegator); + bytes32 hash1 = computeMetaDelegateHash(delegator, delegatee, deadline, nonce); + bytes32 hash2 = computeMetaDelegateHash(delegator, delegatee, deadline, nonce+1); + // assume no hash collisions + require hash1 != hash2; + // assume first call is properly signed + require ecrecoverWrapper(hash1, v, r, s) == delegator; + // assume ecrecover is sane: cannot sign two different messages with the same (v,r,s) + require ecrecoverWrapper(hash2, v, r, s) != ecrecoverWrapper(hash1, v, r, s); + metaDelegate(e1, delegator, delegatee, deadline, v, r, s); + metaDelegate@withrevert(e2, delegator, delegatee, deadline, v, r, s); + assert lastReverted; +} + +/** + + Verify that only delegate functions can change someone's delegate. + + Written by PeterisPrieditis + +*/ + +rule votingDelegateChanges_updated(address alice, method f) { + env e; + calldataarg args; + + address aliceVotingDelegateBefore = getVotingDelegate(alice); + address alicePropositionDelegateBefore = getPropositionDelegate(alice); + + f(e, args); + + address aliceVotingDelegateAfter = getVotingDelegate(alice); + address alicePropositionDelegateAfter = getPropositionDelegate(alice); + + // only these four function may change the delegate of an address + assert (aliceVotingDelegateAfter != aliceVotingDelegateBefore) || (alicePropositionDelegateBefore != alicePropositionDelegateAfter) => + f.selector == delegate(address).selector || + f.selector == delegateByType(address,uint8).selector || + f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector || + f.selector == metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32).selector; +} + + +/* + Power of the previous delegate is removed when the delegatee delegates to another delegate + + Written by priyankabhanderi +*/ +rule delegatingToAnotherUserRemovesPowerFromOldDelegatee(env e, address alice, address bob) { + + require e.msg.sender != ZERO_ADDRESS(); + require e.msg.sender != alice && e.msg.sender != bob; + require alice != ZERO_ADDRESS() && bob != ZERO_ADDRESS(); + + require getVotingDelegate(e.msg.sender) != alice; + + uint72 _votingBalance = getDelegatedVotingBalance(alice); + + delegateByType(e, alice, VOTING_POWER()); + + assert getVotingDelegate(e.msg.sender) == alice; + + delegateByType(e, bob, VOTING_POWER()); + + assert getVotingDelegate(e.msg.sender) == bob; + uint72 votingBalance_ = getDelegatedVotingBalance(alice); + assert alice != bob => votingBalance_ == _votingBalance; +} + +/* + + Voting and proposition power changes only as a result of a subset of functions + + Written by top-sekret + +*/ + +rule powerChanges(address alice, method f) { + env e; + calldataarg args; + + uint8 type; + require type <= 1; + uint256 powerBefore = getPowerCurrent(alice, type); + + f(e, args); + + uint256 powerAfter = getPowerCurrent(alice, type); + + assert powerBefore != powerAfter => + f.selector == delegate(address).selector || + f.selector == delegateByType(address, uint8).selector || + f.selector == metaDelegate(address, address, uint256, uint8, bytes32, bytes32).selector || + f.selector == metaDelegateByType(address, address, uint8, uint256, uint8, bytes32, bytes32).selector || + f.selector == transfer(address, uint256).selector || + f.selector == transferFrom(address, address, uint256).selector; +} + + +/* + Changing a delegate of one type doesn't influence the delegate of the other type + + Written by top-sekret +*/ +rule delegateIndependence(method f) { + env e; + + uint8 type; + require type <= 1; + + address delegateBefore = type == 1 ? getPropositionDelegate(e.msg.sender) : getVotingDelegate(e.msg.sender); + + delegateByType(e, _, 1 - type); + + address delegateAfter = type == 1 ? getPropositionDelegate(e.msg.sender) : getVotingDelegate(e.msg.sender); + + assert delegateBefore == delegateAfter; +} + +/** + Verifying voting power increases/decreases while not being a voting delegatee yourself + + Written by Zarfsec +*/ +rule votingPowerChangesWhileNotBeingADelegatee(address a) { + require a != 0; + + uint256 votingPowerBefore = getPowerCurrent(a, VOTING_POWER()); + uint256 balanceBefore = getBalance(a); + bool isVotingDelegatorBefore = getDelegatingVoting(a); + bool isVotingDelegateeBefore = getDelegatedVotingBalance(a) != 0; + + method f; + env e; + calldataarg args; + f(e, args); + + uint256 votingPowerAfter = getPowerCurrent(a, VOTING_POWER()); + uint256 balanceAfter = getBalance(a); + bool isVotingDelegatorAfter = getDelegatingVoting(a); + bool isVotingDelegateeAfter = getDelegatedVotingBalance(a) != 0; + + require !isVotingDelegateeBefore && !isVotingDelegateeAfter; + + /** + If you're not a delegatee, your voting power only increases when + 1. You're not delegating and your balance increases + 2. You're delegating and stop delegating and your balanceBefore != 0 + */ + assert votingPowerBefore < votingPowerAfter <=> + (!isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore < balanceAfter)) || + (isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore != 0)); + + /** + If you're not a delegatee, your voting power only decreases when + 1. You're not delegating and your balance decreases + 2. You're not delegating and start delegating and your balanceBefore != 0 + */ + assert votingPowerBefore > votingPowerAfter <=> + (!isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore > balanceAfter)) || + (!isVotingDelegatorBefore && isVotingDelegatorAfter && (balanceBefore != 0)); +} + +/** + Verifying proposition power increases/decreases while not being a proposition delegatee yourself + + Written by Zarfsec +*/ +rule propositionPowerChangesWhileNotBeingADelegatee(address a) { + require a != 0; + + uint256 propositionPowerBefore = getPowerCurrent(a, PROPOSITION_POWER()); + uint256 balanceBefore = getBalance(a); + bool isPropositionDelegatorBefore = getDelegatingProposition(a); + bool isPropositionDelegateeBefore = getDelegatedPropositionBalance(a) != 0; + + method f; + env e; + calldataarg args; + f(e, args); + + uint256 propositionPowerAfter = getPowerCurrent(a, PROPOSITION_POWER()); + uint256 balanceAfter = getBalance(a); + bool isPropositionDelegatorAfter = getDelegatingProposition(a); + bool isPropositionDelegateeAfter = getDelegatedPropositionBalance(a) != 0; + + require !isPropositionDelegateeBefore && !isPropositionDelegateeAfter; + + /** + If you're not a delegatee, your proposition power only increases when + 1. You're not delegating and your balance increases + 2. You're delegating and stop delegating and your balanceBefore != 0 + */ + assert propositionPowerBefore < propositionPowerAfter <=> + (!isPropositionDelegatorBefore && !isPropositionDelegatorAfter && (balanceBefore < balanceAfter)) || + (isPropositionDelegatorBefore && !isPropositionDelegatorAfter && (balanceBefore != 0)); + + /** + If you're not a delegatee, your proposition power only decreases when + 1. You're not delegating and your balance decreases + 2. You're not delegating and start delegating and your balanceBefore != 0 + */ + assert propositionPowerBefore > propositionPowerAfter <=> + (!isPropositionDelegatorBefore && !isPropositionDelegatorBefore && (balanceBefore > balanceAfter)) || + (!isPropositionDelegatorBefore && isPropositionDelegatorAfter && (balanceBefore != 0)); +} + From 610fdbfd1b78a812c03149cb1b1341b9ab6c184b Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Mon, 12 Sep 2022 16:07:30 +0300 Subject: [PATCH 44/58] add comments to the community spec --- certora/specs/community.spec | 387 +++++++++++++++++++++++++---------- 1 file changed, 276 insertions(+), 111 deletions(-) diff --git a/certora/specs/community.spec b/certora/specs/community.spec index 757b079..0bb1f4f 100644 --- a/certora/specs/community.spec +++ b/certora/specs/community.spec @@ -1,3 +1,18 @@ +/* + This is a specification file for the verification of AaveTokenV3.sol + smart contract using the Certora prover. The rules in this spec have been + contributed by the community. Individual attribution is given in the comments + to each rule. + + For more information, visit: https://www.certora.com/ + + This file is run with scripts/verifyCommunity.sh + On AaveTokenV3Harness.sol + + Run results: https://prover.certora.com/output/67509/d36b2357623beec546c1?anonymousKey=f6fb866df18e6bc9ed880806375e7861cde8274f + +*/ + import "base.spec" methods { @@ -10,11 +25,29 @@ methods { definition ZERO_ADDRESS() returns address = 0; -/** + +/* + @Rule + + @Description: Integrity of permit function Successful permit function increases the nonce of owner by 1 and also changes the allowance of owner to spender - Written by parth-15 + @Formula: + { + nonceBefore = getNonce(owner) + } + < + permit(owner, spender, value, deadline, v, r, s) + > + { + allowance(owner, spender) == value && getNonce(owner) == nonceBefore + 1 + } + + @Note: + Written by https://github.com/parth-15 + + @Link: */ rule permitIntegrity() { env e; @@ -41,84 +74,80 @@ rule permitIntegrity() { assert nonceAfter == nonceBefore + 1, "successful call to permit function increases nonce of owner by 1"; } -/** - The delegator can always revoke his voting power only by calling delegating functions - - Written by Elpacos - -*/ -rule checkRevokingVotingPower(address someone, method f) { - env e; - calldataarg args; - - //store voting delegating state before - bool delegatingStateBefore = getDelegatingVoting(someone); - //transacction - f(e, args); - //store voting delegating state after - bool delegatingStateAfter = getDelegatingVoting(someone); - - assert (delegatingStateBefore => (delegatingStateAfter || !delegatingStateAfter)); - //assert that any delagation granted can be rovoked or changed to another delegatee using delegate functions - assert ((delegatingStateBefore && !delegatingStateAfter )=> - f.selector == delegate(address).selector || - f.selector == delegateByType(address,uint8).selector || - f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector || - f.selector == metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32).selector); -} -/** - The delegator can always revoke his proposition power only by calling delegating functions - - Written by Elpacos - -*/ -rule checkRevokingPropositionPower(address someone, method f) { - env e; - calldataarg args; +/* + @Rule - //store proposition delegating state before - bool delegatingStateBefore = getDelegatingProposition(someone); - //transacction - f(e, args); - //store proposition delegating state after - bool delegatingStateAfter = getDelegatingProposition(someone); - - assert (delegatingStateBefore => (delegatingStateAfter || !delegatingStateAfter)); - //assert that any delagation granted can be rovoked or changed to another delegatee using delegate functions - assert ((delegatingStateBefore && !delegatingStateAfter )=> - f.selector == delegate(address).selector || - f.selector == delegateByType(address,uint8).selector || - f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector || - f.selector == metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32).selector); -} + @Description: + Address 0 has no voting or proposition power -/** + @Formula: + { + getPowerCurrent(0, VOTING_POWER) == 0 && getPowerCurrent(0, PROPOSITION_POWER) == && balanceOf(0) == 0 + } - Address 0 has no voting power and no proposition power + @Note: + Written by https://github.com/JayP11 - Written by JayP11 + @Link: */ invariant addressZeroNoPower() getPowerCurrent(0, VOTING_POWER()) == 0 && getPowerCurrent(0, PROPOSITION_POWER()) == 0 && balanceOf(0) == 0 -/** - * Check `metaDelegateByType` can only be called with a signed request. - - Written by kustosz - */ +/* + @Rule + + @Description: + Verify that `metaDelegateByType` can only be called with a signed request. + + @Formula: + { + ecrecover(v,r,s) != delegator + } + < + metaDelegateByType@withrevert(delegator, delegatee, delegationType, deadline, v, r, s) + > + { + lastReverted == true + } + + @Note: + Written by https://github.com/kustosz + + @Link: +*/ rule metaDelegateByTypeOnlyCallableWithProperlySignedArguments(env e, address delegator, address delegatee, uint8 delegationType, uint256 deadline, uint8 v, bytes32 r, bytes32 s) { require ecrecoverWrapper(computeMetaDelegateByTypeHash(delegator, delegatee, delegationType, deadline, _nonces(delegator)), v, r, s) != delegator; metaDelegateByType@withrevert(e, delegator, delegatee, delegationType, deadline, v, r, s); assert lastReverted; } -/** - * Check that it's impossible to use the same arguments to call `metaDalegate` twice. - - Written by kustosz - */ + /* + @Rule + + @Description: + Verify that it's impossible to use the same arguments to call `metaDalegate` twice. + + @Formula: + { + hash1 = computeMetaDelegateHash(delegator, delegatee, deadline, nonce) + hash2 = computeMetaDelegateHash(delegator, delegatee, deadline, nonce + 1) + ecrecover(hash1, v, r, s) == delegator + } + < + metaDelegate(e1, delegator, delegatee, v, r, s) + metaDelegate@withrevert(e2, delegator, delegatee, delegationType, deadline, v, r, s) + > + { + lastReverted == true + } + + @Note: + Written by https://github.com/kustosz + + @Link: +*/ rule metaDelegateNonRepeatable(env e1, env e2, address delegator, address delegatee, uint256 deadline, uint8 v, bytes32 r, bytes32 s) { uint256 nonce = _nonces(delegator); bytes32 hash1 = computeMetaDelegateHash(delegator, delegatee, deadline, nonce); @@ -134,39 +163,29 @@ rule metaDelegateNonRepeatable(env e1, env e2, address delegator, address delega assert lastReverted; } -/** - - Verify that only delegate functions can change someone's delegate. - - Written by PeterisPrieditis - -*/ - -rule votingDelegateChanges_updated(address alice, method f) { - env e; - calldataarg args; - - address aliceVotingDelegateBefore = getVotingDelegate(alice); - address alicePropositionDelegateBefore = getPropositionDelegate(alice); - - f(e, args); - - address aliceVotingDelegateAfter = getVotingDelegate(alice); - address alicePropositionDelegateAfter = getPropositionDelegate(alice); - - // only these four function may change the delegate of an address - assert (aliceVotingDelegateAfter != aliceVotingDelegateBefore) || (alicePropositionDelegateBefore != alicePropositionDelegateAfter) => - f.selector == delegate(address).selector || - f.selector == delegateByType(address,uint8).selector || - f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector || - f.selector == metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32).selector; -} - /* - Power of the previous delegate is removed when the delegatee delegates to another delegate - - Written by priyankabhanderi + @Rule + + @Description: + Power of the previous delegate is removed when the delegatee delegates to another delegate + + @Formula: + { + _votingBalance = getDelegatedVotingBalance(alice) + } + < + delegateByType(alice, VOTING_POWER) + delegateByType(bob, VOTING_POWER) + > + { + alice != bob => getDelegatedVotingBalance(alice) == _votingBalance + } + + @Note: + Written by https://github.com/priyankabhanderi + + @Link: */ rule delegatingToAnotherUserRemovesPowerFromOldDelegatee(env e, address alice, address bob) { @@ -190,11 +209,33 @@ rule delegatingToAnotherUserRemovesPowerFromOldDelegatee(env e, address alice, a } /* + @Rule + + @Description: + Voting and proposition power change only as a result of specific functions + + @Formula: + { + powerBefore = getPowerCurrent(alice, type) + } + < + f(e, args) + > + { + powerAfter = getPowerCurrent(alice, type) + powerAfter != powerBefore => + f.selector == delegate(address).selector || + f.selector == delegateByType(address, uint8).selector || + f.selector == metaDelegate(address, address, uint256, uint8, bytes32, bytes32).selector || + f.selector == metaDelegateByType(address, address, uint8, uint256, uint8, bytes32, bytes32).selector || + f.selector == transfer(address, uint256).selector || + f.selector == transferFrom(address, address, uint256).selector + } - Voting and proposition power changes only as a result of a subset of functions - - Written by top-sekret + @Note: + Written by https://github.com/top-sekret + @Link: */ rule powerChanges(address alice, method f) { @@ -219,10 +260,29 @@ rule powerChanges(address alice, method f) { } -/* - Changing a delegate of one type doesn't influence the delegate of the other type - Written by top-sekret +/* + @Rule + + @Description: + Changing a delegate of one type doesn't influence the delegate of the other type + + @Formula: + { + delegateBefore = type == 1 ? getPropositionDelegate(e.msg.sender) : getVotingDelegate(e.msg.sender) + } + < + delegateByType(e, delegatee, 1 - type) + > + { + delegateBefore = type == 1 ? getPropositionDelegate(e.msg.sender) : getVotingDelegate(e.msg.sender) + delegateBefore == delegateAfter + } + + @Note: + Written by https://github.com/top-sekret + + @Link: */ rule delegateIndependence(method f) { env e; @@ -239,10 +299,41 @@ rule delegateIndependence(method f) { assert delegateBefore == delegateAfter; } -/** - Verifying voting power increases/decreases while not being a voting delegatee yourself +/* + @Rule + + @Description: + Verifying voting power increases/decreases while not being a voting delegatee yourself + + @Formula: + { + votingPowerBefore = getPowerCurrent(a, VOTING_POWER) + balanceBefore = balanceOf(a) + isVotingDelegatorBefore = getDelegatingVoting(a) + isVotingDelegateeBefore = getDelegatedVotingBalance(a) != 0 + } + < + f(e, args) + > + { + votingPowerAfter = getPowerCurrent(a, VOTING_POWER() + balanceAfter = getBalance(a) + isVotingDelegatorAfter = getDelegatingVoting(a); + isVotingDelegateeAfter = getDelegatedVotingBalance(a) != 0 + + votingPowerBefore < votingPowerAfter <=> + (!isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore < balanceAfter)) || + (isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore != 0)) + && + votingPowerBefore > votingPowerAfter <=> + (!isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore > balanceAfter)) || + (!isVotingDelegatorBefore && isVotingDelegatorAfter && (balanceBefore != 0)) + } + + @Note: + Written by https://github.com/Zarfsec - Written by Zarfsec + @Link: */ rule votingPowerChangesWhileNotBeingADelegatee(address a) { require a != 0; @@ -264,7 +355,7 @@ rule votingPowerChangesWhileNotBeingADelegatee(address a) { require !isVotingDelegateeBefore && !isVotingDelegateeAfter; - /** + /* If you're not a delegatee, your voting power only increases when 1. You're not delegating and your balance increases 2. You're delegating and stop delegating and your balanceBefore != 0 @@ -273,7 +364,7 @@ rule votingPowerChangesWhileNotBeingADelegatee(address a) { (!isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore < balanceAfter)) || (isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore != 0)); - /** + /* If you're not a delegatee, your voting power only decreases when 1. You're not delegating and your balance decreases 2. You're not delegating and start delegating and your balanceBefore != 0 @@ -283,10 +374,41 @@ rule votingPowerChangesWhileNotBeingADelegatee(address a) { (!isVotingDelegatorBefore && isVotingDelegatorAfter && (balanceBefore != 0)); } -/** - Verifying proposition power increases/decreases while not being a proposition delegatee yourself +/* + @Rule + + @Description: + Verifying proposition power increases/decreases while not being a proposition delegatee yourself + + @Formula: + { + propositionPowerBefore = getPowerCurrent(a, PROPOSITION_POWER) + balanceBefore = balanceOf(a) + isPropositionDelegatorBefore = getDelegatingProposition(a) + isPropositionDelegateeBefore = getDelegatedPropositionBalance(a) != 0 + } + < + f(e, args) + > + { + propositionPowerAfter = getPowerCurrent(a, PROPOSITION_POWER() + balanceAfter = getBalance(a) + isPropositionDelegatorAfter = getDelegatingProposition(a); + isPropositionDelegateeAfter = getDelegatedPropositionBalance(a) != 0 + + propositionPowerBefore < propositionPowerAfter <=> + (!isPropositionDelegatorBefore && !isPropositionDelegatorAfter && (balanceBefore < balanceAfter)) || + (isPropositionDelegatorBefore && !isPropositionDelegatorAfter && (balanceBefore != 0)) + && + propositionPowerBefore > propositionPowerAfter <=> + (!isPropositionDelegatorBefore && !isPropositionDelegatorAfter && (balanceBefore > balanceAfter)) || + (!isPropositionDelegatorBefore && isPropositionDelegatorAfter && (balanceBefore != 0)) + } - Written by Zarfsec + @Note: + Written by https://github.com/Zarfsec + + @Link: */ rule propositionPowerChangesWhileNotBeingADelegatee(address a) { require a != 0; @@ -308,7 +430,7 @@ rule propositionPowerChangesWhileNotBeingADelegatee(address a) { require !isPropositionDelegateeBefore && !isPropositionDelegateeAfter; - /** + /* If you're not a delegatee, your proposition power only increases when 1. You're not delegating and your balance increases 2. You're delegating and stop delegating and your balanceBefore != 0 @@ -317,7 +439,7 @@ rule propositionPowerChangesWhileNotBeingADelegatee(address a) { (!isPropositionDelegatorBefore && !isPropositionDelegatorAfter && (balanceBefore < balanceAfter)) || (isPropositionDelegatorBefore && !isPropositionDelegatorAfter && (balanceBefore != 0)); - /** + /* If you're not a delegatee, your proposition power only decreases when 1. You're not delegating and your balance decreases 2. You're not delegating and start delegating and your balanceBefore != 0 @@ -327,3 +449,46 @@ rule propositionPowerChangesWhileNotBeingADelegatee(address a) { (!isPropositionDelegatorBefore && isPropositionDelegatorAfter && (balanceBefore != 0)); } +/* + @Rule + + @Description: + Allowance only changes as a result of specific subset of functions + + @Formula: + { + allowanceBefore = allowance(owner, spender) + } + < + f(e, args) + > + { + allowance(owner, spender) != allowanceBefore =>f.selector==approve(address,uint256).selector + || f.selector==increaseAllowance(address,uint256).selector + || f.selector==decreaseAllowance(address,uint256).selector + || f.selector==transferFrom(address,address,uint256).selector + || f.selector==permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector + + } + + @Note: + Written by https://github.com/oracleorb + + @Link: +*/ +rule allowanceStateChange(env e){ + address owner; + address user; + method f; + calldataarg args; + + uint256 allowanceBefore=getAllowance(owner,user); + f(e, args); + uint256 allowanceAfter=getAllowance(owner,user); + + assert allowanceBefore!=allowanceAfter => f.selector==approve(address,uint256).selector + || f.selector==increaseAllowance(address,uint256).selector + || f.selector==decreaseAllowance(address,uint256).selector + || f.selector==transferFrom(address,address,uint256).selector + || f.selector==permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector; +} From f181c15a7b18e94857e986b90cd7f2ebc62ade51 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Wed, 21 Sep 2022 17:05:11 +0300 Subject: [PATCH 45/58] add munged for creating harnesses --- .gitignore | 2 + certora/Makefile | 26 ++ certora/TODO.md | 30 ++ certora/applyHarness.patch | 81 ++++ certora/harness/AaveTokenV3Harness.sol | 13 +- certora/harness/AaveTokenV3HarnessStorage.sol | 2 +- certora/harness/README.md | 7 + .../harness/storage_harness/AaveTokenV3.sol | 383 ------------------ .../harness/storage_harness/BaseAaveToken.sol | 162 -------- .../storage_harness/BaseAaveTokenV2.sol | 82 ---- certora/harness/storage_harness/README.md | 5 - certora/properties.md | 43 -- certora/specs/base.spec | 2 +- certora/specs/delegate.spec | 76 +++- certora/specs/general.spec | 19 +- properties.md | 9 +- 16 files changed, 248 insertions(+), 694 deletions(-) create mode 100644 certora/Makefile create mode 100644 certora/TODO.md create mode 100644 certora/applyHarness.patch create mode 100644 certora/harness/README.md delete mode 100644 certora/harness/storage_harness/AaveTokenV3.sol delete mode 100644 certora/harness/storage_harness/BaseAaveToken.sol delete mode 100644 certora/harness/storage_harness/BaseAaveTokenV2.sol delete mode 100644 certora/harness/storage_harness/README.md delete mode 100644 certora/properties.md diff --git a/.gitignore b/.gitignore index ca9a9a1..ba23d3b 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ out/ .certora_* node_modules/ .env +certora/munged +resource_errors.json \ No newline at end of file diff --git a/certora/Makefile b/certora/Makefile new file mode 100644 index 0000000..5e6e1bc --- /dev/null +++ b/certora/Makefile @@ -0,0 +1,26 @@ +default: help + +PATCH = applyHarness.patch +CONTRACTS_DIR = ../src +LIB_DIR = ../lib +MUNGED_DIR = munged + +help: + @echo "usage:" + @echo " make clean: remove all generated files (those ignored by git)" + @echo " make $(MUNGED_DIR): create $(MUNGED_DIR) directory by applying the patch file to $(CONTRACTS_DIR)" + @echo " make record: record a new patch file capturing the differences between $(CONTRACTS_DIR) and $(MUNGED_DIR)" + +munged: $(wildcard $(CONTRACTS_DIR)/*.sol) $(PATCH) + rm -rf $@ + cp -r $(CONTRACTS_DIR) $@ + cp -r $(LIB_DIR) $@ + patch -p0 -d $@ < $(PATCH) + +record: + diff -uN $(CONTRACTS_DIR) $(MUNGED_DIR) | sed 's+$(CONTRACTS_DIR)/++g' | sed 's+$(MUNGED_DIR)++g' > $(PATCH) + +clean: + git clean -fdX + touch $(PATCH) + diff --git a/certora/TODO.md b/certora/TODO.md new file mode 100644 index 0000000..d7f437a --- /dev/null +++ b/certora/TODO.md @@ -0,0 +1,30 @@ +- [v] comment in the Harness file to explain the getters + + +- natspec. formatted like in openzeppelin specs +``` + +/** + +*/ + +``` +- [v] changed 'how to run' instructions to use munged + +- add assert messages [last] + +- [v] make sure properties.md on certora is identical to the main branch + +- [v] delete the properties.md that i wrote + +- [v] invariant delegationStateValid() and then requireInvariant in `delegateCorrectness` + +- [v] voting power changes as a result of only specific calls (transfer, transferFrom, delegate, metadelegate, delegateByType) + +- [v] transferAndTransferFrom are the same: do transfer and transferFrom at the same storage and check voting power transfer + +- [v] delegationTypeIndependence => rewrite into more readable form (either both change) + +- [v] transferDoesntChangeDelegationState: add charlie + +- report: more about community, so many rules were submitted, so many were selected. \ No newline at end of file diff --git a/certora/applyHarness.patch b/certora/applyHarness.patch new file mode 100644 index 0000000..a091c7a --- /dev/null +++ b/certora/applyHarness.patch @@ -0,0 +1,81 @@ +diff -uN AaveTokenV3.sol /AaveTokenV3.sol +--- AaveTokenV3.sol 2022-08-04 12:28:46.000000000 +0300 ++++ /AaveTokenV3.sol 2022-09-21 10:49:34.000000000 +0300 +@@ -210,7 +210,7 @@ + fromBalanceAfter = fromUserState.balance - uint104(amount); + } + _balances[from].balance = fromBalanceAfter; +- if (fromUserState.delegationState != DelegationState.NO_DELEGATION) { ++ if (fromUserState.delegationState != uint8(DelegationState.NO_DELEGATION)) { + _governancePowerTransferByType( + fromUserState.balance, + fromBalanceAfter, +@@ -232,7 +232,7 @@ + toUserState.balance = toBalanceBefore + uint104(amount); + _balances[to] = toUserState; + +- if (toUserState.delegationState != DelegationState.NO_DELEGATION) { ++ if (toUserState.delegationState != uint8(DelegationState.NO_DELEGATION)) { + _governancePowerTransferByType( + toBalanceBefore, + toUserState.balance, +@@ -288,7 +288,7 @@ + : address(0); + } + return +- userState.delegationState >= DelegationState.PROPOSITION_DELEGATED ++ userState.delegationState >= uint8(DelegationState.PROPOSITION_DELEGATED) + ? _propositionDelegateeV2[delegator] + : address(0); + } +@@ -325,16 +325,12 @@ + ) internal pure returns (DelegationAwareBalance memory) { + if (willDelegate) { + // Because GovernancePowerType starts from 0, we should add 1 first, then we apply bitwise OR +- userState.delegationState = DelegationState( +- uint8(userState.delegationState) | (uint8(delegationType) + 1) +- ); ++ userState.delegationState = userState.delegationState | (uint8(delegationType) + 1); + } else { + // First bitwise NEGATION, ie was 01, after XOR with 11 will be 10, + // then bitwise AND, which means it will keep only another delegation type if it exists +- userState.delegationState = DelegationState( +- uint8(userState.delegationState) & +- ((uint8(delegationType) + 1) ^ uint8(DelegationState.FULL_POWER_DELEGATED)) +- ); ++ userState.delegationState = userState.delegationState & ++ ((uint8(delegationType) + 1) ^ uint8(DelegationState.FULL_POWER_DELEGATED)); + } + return userState; + } +diff -uN BaseAaveToken.sol /BaseAaveToken.sol +--- BaseAaveToken.sol 2022-08-04 15:01:43.000000000 +0300 ++++ /BaseAaveToken.sol 2022-09-21 10:51:54.000000000 +0300 +@@ -1,9 +1,9 @@ + // SPDX-License-Identifier: MIT + pragma solidity ^0.8.0; + +-import {Context} from '../lib/openzeppelin-contracts/contracts/utils/Context.sol'; +-import {IERC20} from '../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; +-import {IERC20Metadata} from '../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; ++import {Context} from './lib/openzeppelin-contracts/contracts/utils/Context.sol'; ++import {IERC20} from './lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; ++import {IERC20Metadata} from './lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; + + // Inspired by OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) + abstract contract BaseAaveToken is Context, IERC20Metadata { +@@ -16,10 +16,10 @@ + + // reorder fields to make hooks syntax simpler + struct DelegationAwareBalance { +- DelegationState delegationState; + uint104 balance; + uint72 delegatedPropositionBalance; + uint72 delegatedVotingBalance; ++ uint8 delegationState; // refactored from enum + } + + mapping(address => DelegationAwareBalance) internal _balances; +Common subdirectories: interfaces and /interfaces +Common subdirectories: test and /test +Common subdirectories: utils and /utils diff --git a/certora/harness/AaveTokenV3Harness.sol b/certora/harness/AaveTokenV3Harness.sol index 1f4d2df..d0e13c6 100644 --- a/certora/harness/AaveTokenV3Harness.sol +++ b/certora/harness/AaveTokenV3Harness.sol @@ -11,39 +11,44 @@ pragma solidity ^0.8.0; import {AaveTokenV3} from "../../src/AaveTokenV3.sol"; contract AaveTokenV3Harness is AaveTokenV3 { + + // returns user's token balance, used in some community rules function getBalance(address user) view public returns (uint104) { return _balances[user].balance; } - + // returns user's delegated proposition balance function getDelegatedPropositionBalance(address user) view public returns (uint72) { return _balances[user].delegatedPropositionBalance; } - + // returns user's delegated voting balance function getDelegatedVotingBalance(address user) view public returns (uint72) { return _balances[user].delegatedVotingBalance; } - + //returns user's delegating proposition status function getDelegatingProposition(address user) view public returns (bool) { return _balances[user].delegationState == DelegationState.PROPOSITION_DELEGATED || _balances[user].delegationState == DelegationState.FULL_POWER_DELEGATED; } - + // returns user's delegating voting status function getDelegatingVoting(address user) view public returns (bool) { return _balances[user].delegationState == DelegationState.VOTING_DELEGATED || _balances[user].delegationState == DelegationState.FULL_POWER_DELEGATED; } + // returns user's voting delegate function getVotingDelegate(address user) view public returns (address) { return _votingDelegateeV2[user]; } + // returns user's proposition delegate function getPropositionDelegate(address user) view public returns (address) { return _propositionDelegateeV2[user]; } + // returns user's delegation state function getDelegationState(address user) view public returns (DelegationState) { return _balances[user].delegationState; } diff --git a/certora/harness/AaveTokenV3HarnessStorage.sol b/certora/harness/AaveTokenV3HarnessStorage.sol index 78ef106..4c77b29 100644 --- a/certora/harness/AaveTokenV3HarnessStorage.sol +++ b/certora/harness/AaveTokenV3HarnessStorage.sol @@ -12,7 +12,7 @@ pragma solidity ^0.8.0; -import {AaveTokenV3} from "./storage_harness/AaveTokenV3.sol"; +import {AaveTokenV3} from "../munged/AaveTokenV3.sol"; contract AaveTokenV3Harness is AaveTokenV3 { function getBalance(address user) view public returns (uint104) { diff --git a/certora/harness/README.md b/certora/harness/README.md new file mode 100644 index 0000000..e7d1aaa --- /dev/null +++ b/certora/harness/README.md @@ -0,0 +1,7 @@ +We use two harnesses: + +- AaveTokenV3Harness adds a few simple getters to the original AaveTokenV3 code + +- AaveTokenV3HarnessStorage is used to verify general.spec. It changes delegationState field in _balances +to be uint8 instead of DelegationState enum. The harness files are produced by running `make munged` which +patches the original code with `applyHarness.patch` patch. \ No newline at end of file diff --git a/certora/harness/storage_harness/AaveTokenV3.sol b/certora/harness/storage_harness/AaveTokenV3.sol deleted file mode 100644 index ffd3928..0000000 --- a/certora/harness/storage_harness/AaveTokenV3.sol +++ /dev/null @@ -1,383 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {VersionedInitializable} from '../../../src/utils/VersionedInitializable.sol'; -import {IGovernancePowerDelegationToken} from '../../../src/interfaces/IGovernancePowerDelegationToken.sol'; -import {BaseAaveTokenV2} from './BaseAaveTokenV2.sol'; - -contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { - mapping(address => address) internal _votingDelegateeV2; - mapping(address => address) internal _propositionDelegateeV2; - - /// @dev we assume that for the governance system 18 decimals of precision is not needed, - // by this constant we reduce it by 10, to 8 decimals - uint256 public constant POWER_SCALE_FACTOR = 1e10; - - bytes32 public constant DELEGATE_BY_TYPE_TYPEHASH = - keccak256( - 'DelegateByType(address delegator,address delegatee,GovernancePowerType delegationType,uint256 nonce,uint256 deadline)' - ); - bytes32 public constant DELEGATE_TYPEHASH = - keccak256('Delegate(address delegator,address delegatee,uint256 nonce,uint256 deadline)'); - - /// @inheritdoc IGovernancePowerDelegationToken - function delegateByType(address delegatee, GovernancePowerType delegationType) - external - virtual - override - { - _delegateByType(msg.sender, delegatee, delegationType); - } - - /// @inheritdoc IGovernancePowerDelegationToken - function delegate(address delegatee) external override { - _delegateByType(msg.sender, delegatee, GovernancePowerType.VOTING); - _delegateByType(msg.sender, delegatee, GovernancePowerType.PROPOSITION); - } - - /// @inheritdoc IGovernancePowerDelegationToken - function getDelegateeByType(address delegator, GovernancePowerType delegationType) - external - view - override - returns (address) - { - return _getDelegateeByType(delegator, _balances[delegator], delegationType); - } - - /// @inheritdoc IGovernancePowerDelegationToken - function getDelegates(address delegator) external view override returns (address, address) { - DelegationAwareBalance memory delegatorBalance = _balances[delegator]; - return ( - _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.VOTING), - _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.PROPOSITION) - ); - } - - /// @inheritdoc IGovernancePowerDelegationToken - function getPowerCurrent(address user, GovernancePowerType delegationType) - public - view - override - returns (uint256) - { - DelegationAwareBalance memory userState = _balances[user]; - uint256 userOwnPower = uint8(userState.delegationState) & (uint8(delegationType) + 1) == 0 - ? _balances[user].balance - : 0; - uint256 userDelegatedPower = _getDelegatedPowerByType(userState, delegationType); - return userOwnPower + userDelegatedPower; - } - - /// @inheritdoc IGovernancePowerDelegationToken - function getPowersCurrent(address user) external view override returns (uint256, uint256) { - return ( - getPowerCurrent(user, GovernancePowerType.VOTING), - getPowerCurrent(user, GovernancePowerType.PROPOSITION) - ); - } - - /// @inheritdoc IGovernancePowerDelegationToken - function metaDelegateByType( - address delegator, - address delegatee, - GovernancePowerType delegationType, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external override { - require(delegator != address(0), 'INVALID_OWNER'); - //solium-disable-next-line - require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); - uint256 currentValidNonce = _nonces[delegator]; - bytes32 digest = keccak256( - abi.encodePacked( - '\x19\x01', - DOMAIN_SEPARATOR, - keccak256( - abi.encode( - DELEGATE_BY_TYPE_TYPEHASH, - delegator, - delegatee, - delegationType, - currentValidNonce, - deadline - ) - ) - ) - ); - - require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); - unchecked { - // Does not make sense to check because it's not realistic to reach uint256.max in nonce - _nonces[delegator] = currentValidNonce + 1; - } - _delegateByType(delegator, delegatee, delegationType); - } - - /// @inheritdoc IGovernancePowerDelegationToken - function metaDelegate( - address delegator, - address delegatee, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external override { - require(delegator != address(0), 'INVALID_OWNER'); - //solium-disable-next-line - require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); - uint256 currentValidNonce = _nonces[delegator]; - bytes32 digest = keccak256( - abi.encodePacked( - '\x19\x01', - DOMAIN_SEPARATOR, - keccak256(abi.encode(DELEGATE_TYPEHASH, delegator, delegatee, currentValidNonce, deadline)) - ) - ); - - require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); - unchecked { - // does not make sense to check because it's not realistic to reach uint256.max in nonce - _nonces[delegator] = currentValidNonce + 1; - } - _delegateByType(delegator, delegatee, GovernancePowerType.VOTING); - _delegateByType(delegator, delegatee, GovernancePowerType.PROPOSITION); - } - - /** - * @dev Modifies the delegated power of a `delegatee` account by type (VOTING, PROPOSITION). - * Passing the impact on the delegation of `delegatee` account before and after to reduce conditionals and not lose - * any precision. - * @param impactOnDelegationBefore how much impact a balance of another account had over the delegation of a `delegatee` - * before an action. - * For example, if the action is a delegation from one account to another, the impact before the action will be 0. - * @param impactOnDelegationAfter how much impact a balance of another account will have over the delegation of a `delegatee` - * after an action. - * For example, if the action is a delegation from one account to another, the impact after the action will be the whole balance - * of the account changing the delegatee. - * @param delegatee the user whom delegated governance power will be changed - * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) - **/ - function _governancePowerTransferByType( - uint104 impactOnDelegationBefore, - uint104 impactOnDelegationAfter, - address delegatee, - GovernancePowerType delegationType - ) internal { - if (delegatee == address(0)) return; - if (impactOnDelegationBefore == impactOnDelegationAfter) return; - - // To make delegated balance fit into uint72 we're decreasing precision of delegated balance by POWER_SCALE_FACTOR - uint72 impactOnDelegationBefore72 = uint72(impactOnDelegationBefore / POWER_SCALE_FACTOR); - uint72 impactOnDelegationAfter72 = uint72(impactOnDelegationAfter / POWER_SCALE_FACTOR); - - if (delegationType == GovernancePowerType.VOTING) { - _balances[delegatee].delegatedVotingBalance = - _balances[delegatee].delegatedVotingBalance - - impactOnDelegationBefore72 + - impactOnDelegationAfter72; - } else { - _balances[delegatee].delegatedPropositionBalance = - _balances[delegatee].delegatedPropositionBalance - - impactOnDelegationBefore72 + - impactOnDelegationAfter72; - } - } - - /** - * @dev performs all state changes related to balance transfer and corresponding delegation changes - * @param from token sender - * @param to token recipient - * @param amount amount of tokens sent - **/ - function _transferWithDelegation( - address from, - address to, - uint256 amount - ) internal override { - if (from == to) { - return; - } - - if (from != address(0)) { - DelegationAwareBalance memory fromUserState = _balances[from]; - require(fromUserState.balance >= amount, 'ERC20: transfer amount exceeds balance'); - - uint104 fromBalanceAfter; - unchecked { - fromBalanceAfter = fromUserState.balance - uint104(amount); - } - _balances[from].balance = fromBalanceAfter; - if (fromUserState.delegationState != uint8(DelegationState.NO_DELEGATION)) { - _governancePowerTransferByType( - fromUserState.balance, - fromBalanceAfter, - _getDelegateeByType(from, fromUserState, GovernancePowerType.VOTING), - GovernancePowerType.VOTING - ); - _governancePowerTransferByType( - fromUserState.balance, - fromBalanceAfter, - _getDelegateeByType(from, fromUserState, GovernancePowerType.PROPOSITION), - GovernancePowerType.PROPOSITION - ); - } - } - - if (to != address(0)) { - DelegationAwareBalance memory toUserState = _balances[to]; - uint104 toBalanceBefore = toUserState.balance; - toUserState.balance = toBalanceBefore + uint104(amount); - _balances[to] = toUserState; - - if (toUserState.delegationState != uint8(DelegationState.NO_DELEGATION)) { - _governancePowerTransferByType( - toBalanceBefore, - toUserState.balance, - _getDelegateeByType(to, toUserState, GovernancePowerType.VOTING), - GovernancePowerType.VOTING - ); - _governancePowerTransferByType( - toBalanceBefore, - toUserState.balance, - _getDelegateeByType(to, toUserState, GovernancePowerType.PROPOSITION), - GovernancePowerType.PROPOSITION - ); - } - } - } - - /** - * @dev Extracts from state and returns delegated governance power (Voting, Proposition) - * @param userState the current state of a user - * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) - **/ - function _getDelegatedPowerByType( - DelegationAwareBalance memory userState, - GovernancePowerType delegationType - ) internal pure returns (uint256) { - return - POWER_SCALE_FACTOR * - ( - delegationType == GovernancePowerType.VOTING - ? userState.delegatedVotingBalance - : userState.delegatedPropositionBalance - ); - } - - /** - * @dev Extracts from state and returns the delegatee of a delegator by type of governance power (Voting, Proposition) - * - If the delegator doesn't have any delegatee, returns address(0) - * @param delegator delegator - * @param userState the current state of a user - * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) - **/ - function _getDelegateeByType( - address delegator, - DelegationAwareBalance memory userState, - GovernancePowerType delegationType - ) internal view returns (address) { - if (delegationType == GovernancePowerType.VOTING) { - return - /// With the & operation, we cover both VOTING_DELEGATED delegation and FULL_POWER_DELEGATED - /// as VOTING_DELEGATED is equivalent to 01 in binary and FULL_POWER_DELEGATED is equivalent to 11 - (uint8(userState.delegationState) & uint8(DelegationState.VOTING_DELEGATED)) != 0 - ? _votingDelegateeV2[delegator] - : address(0); - } - return - userState.delegationState >= uint8(DelegationState.PROPOSITION_DELEGATED) - ? _propositionDelegateeV2[delegator] - : address(0); - } - - /** - * @dev Changes user's delegatee address by type of governance power (Voting, Proposition) - * @param delegator delegator - * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) - * @param _newDelegatee the new delegatee - **/ - function _updateDelegateeByType( - address delegator, - GovernancePowerType delegationType, - address _newDelegatee - ) internal { - address newDelegatee = _newDelegatee == delegator ? address(0) : _newDelegatee; - if (delegationType == GovernancePowerType.VOTING) { - _votingDelegateeV2[delegator] = newDelegatee; - } else { - _propositionDelegateeV2[delegator] = newDelegatee; - } - } - - /** - * @dev Updates the specific flag which signaling about existence of delegation of governance power (Voting, Proposition) - * @param userState a user state to change - * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) - * @param willDelegate next state of delegation - **/ - function _updateDelegationFlagByType( - DelegationAwareBalance memory userState, - GovernancePowerType delegationType, - bool willDelegate - ) internal pure returns (DelegationAwareBalance memory) { - if (willDelegate) { - // Because GovernancePowerType starts from 0, we should add 1 first, then we apply bitwise OR - userState.delegationState = userState.delegationState | (uint8(delegationType) + 1); - } else { - // First bitwise NEGATION, ie was 01, after XOR with 11 will be 10, - // then bitwise AND, which means it will keep only another delegation type if it exists - userState.delegationState = userState.delegationState & - ((uint8(delegationType) + 1) ^ uint8(DelegationState.FULL_POWER_DELEGATED)); - } - return userState; - } - - /** - * @dev This is the equivalent of an ERC20 transfer(), but for a power type: an atomic transfer of a balance (power). - * When needed, it decreases the power of the `delegator` and when needed, it increases the power of the `delegatee` - * @param delegator delegator - * @param _delegatee the user which delegated power will change - * @param delegationType the type of delegation (VOTING, PROPOSITION) - **/ - function _delegateByType( - address delegator, - address _delegatee, - GovernancePowerType delegationType - ) internal { - // Here we unify the property that delegating power to address(0) == delegating power to yourself == no delegation - // So from now on, not being delegating is (exclusively) that delegatee == address(0) - address delegatee = _delegatee == delegator ? address(0) : _delegatee; - - // We read the whole struct before validating delegatee, because in the optimistic case - // (_delegatee != currentDelegatee) we will reuse userState in the rest of the function - DelegationAwareBalance memory delegatorState = _balances[delegator]; - address currentDelegatee = _getDelegateeByType(delegator, delegatorState, delegationType); - if (delegatee == currentDelegatee) return; - - bool delegatingNow = currentDelegatee != address(0); - bool willDelegateAfter = delegatee != address(0); - - if (delegatingNow) { - _governancePowerTransferByType(delegatorState.balance, 0, currentDelegatee, delegationType); - } - - if (willDelegateAfter) { - _governancePowerTransferByType(0, delegatorState.balance, delegatee, delegationType); - } - - _updateDelegateeByType(delegator, delegationType, delegatee); - - if (willDelegateAfter != delegatingNow) { - _balances[delegator] = _updateDelegationFlagByType( - delegatorState, - delegationType, - willDelegateAfter - ); - } - - emit DelegateChanged(delegator, delegatee, delegationType); - } -} \ No newline at end of file diff --git a/certora/harness/storage_harness/BaseAaveToken.sol b/certora/harness/storage_harness/BaseAaveToken.sol deleted file mode 100644 index 4c51174..0000000 --- a/certora/harness/storage_harness/BaseAaveToken.sol +++ /dev/null @@ -1,162 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {Context} from '../../../lib/openzeppelin-contracts/contracts/utils/Context.sol'; -import {IERC20} from '../../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; -import {IERC20Metadata} from '../../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; - -// Inspired by OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) -abstract contract BaseAaveToken is Context, IERC20Metadata { - enum DelegationState { - NO_DELEGATION, - VOTING_DELEGATED, - PROPOSITION_DELEGATED, - FULL_POWER_DELEGATED - } - - // reorder fields to make hooks syntax simpler - struct DelegationAwareBalance { - uint104 balance; - uint72 delegatedPropositionBalance; - uint72 delegatedVotingBalance; - uint8 delegationState; // refactored from enum - } - - mapping(address => DelegationAwareBalance) internal _balances; - - mapping(address => mapping(address => uint256)) internal _allowances; - - uint256 internal _totalSupply; - - string internal _name; - string internal _symbol; - - // @dev DEPRECATED - // kept for backwards compatibility with old storage layout - uint8 private ______DEPRECATED_OLD_ERC20_DECIMALS; - - /** - * @dev Returns the name of the token. - */ - function name() public view virtual override returns (string memory) { - return _name; - } - - /** - * @dev Returns the symbol of the token, usually a shorter version of the - * name. - */ - function symbol() public view virtual override returns (string memory) { - return _symbol; - } - - function decimals() public view virtual override returns (uint8) { - return 18; - } - - function totalSupply() public view virtual override returns (uint256) { - return _totalSupply; - } - - function balanceOf(address account) public view virtual override returns (uint256) { - return _balances[account].balance; - } - - function transfer(address to, uint256 amount) public virtual override returns (bool) { - address owner = _msgSender(); - _transfer(owner, to, amount); - return true; - } - - function allowance(address owner, address spender) - public - view - virtual - override - returns (uint256) - { - return _allowances[owner][spender]; - } - - function approve(address spender, uint256 amount) public virtual override returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, amount); - return true; - } - - function transferFrom( - address from, - address to, - uint256 amount - ) public virtual override returns (bool) { - address spender = _msgSender(); - _spendAllowance(from, spender, amount); - _transfer(from, to, amount); - return true; - } - - function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, _allowances[owner][spender] + addedValue); - return true; - } - - function decreaseAllowance(address spender, uint256 subtractedValue) - public - virtual - returns (bool) - { - address owner = _msgSender(); - uint256 currentAllowance = _allowances[owner][spender]; - require(currentAllowance >= subtractedValue, 'ERC20: decreased allowance below zero'); - unchecked { - _approve(owner, spender, currentAllowance - subtractedValue); - } - - return true; - } - - function _transfer( - address from, - address to, - uint256 amount - ) internal virtual { - require(from != address(0), 'ERC20: transfer from the zero address'); - require(to != address(0), 'ERC20: transfer to the zero address'); - - _transferWithDelegation(from, to, amount); - emit Transfer(from, to, amount); - } - - function _approve( - address owner, - address spender, - uint256 amount - ) internal virtual { - require(owner != address(0), 'ERC20: approve from the zero address'); - require(spender != address(0), 'ERC20: approve to the zero address'); - - _allowances[owner][spender] = amount; - emit Approval(owner, spender, amount); - } - - function _spendAllowance( - address owner, - address spender, - uint256 amount - ) internal virtual { - uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance != type(uint256).max) { - require(currentAllowance >= amount, 'ERC20: insufficient allowance'); - unchecked { - _approve(owner, spender, currentAllowance - amount); - } - } - } - - function _transferWithDelegation( - address from, - address to, - uint256 amount - ) internal virtual {} -} diff --git a/certora/harness/storage_harness/BaseAaveTokenV2.sol b/certora/harness/storage_harness/BaseAaveTokenV2.sol deleted file mode 100644 index 18ab3e2..0000000 --- a/certora/harness/storage_harness/BaseAaveTokenV2.sol +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {VersionedInitializable} from '../../../src/utils/VersionedInitializable.sol'; -import {BaseAaveToken} from './BaseAaveToken.sol'; - -abstract contract BaseAaveTokenV2 is BaseAaveToken, VersionedInitializable { - /// @dev owner => next valid nonce to submit with permit() - mapping(address => uint256) public _nonces; - - ///////// @dev DEPRECATED from AaveToken v1 ////////////////////////// - //////// kept for backwards compatibility with old storage layout //// - uint256[3] private ______DEPRECATED_FROM_AAVE_V1; - ///////// @dev END OF DEPRECATED from AaveToken v1 ////////////////////////// - - bytes32 public DOMAIN_SEPARATOR; - - ///////// @dev DEPRECATED from AaveToken v2 ////////////////////////// - //////// kept for backwards compatibility with old storage layout //// - uint256[4] private ______DEPRECATED_FROM_AAVE_V2; - ///////// @dev END OF DEPRECATED from AaveToken v2 ////////////////////////// - - bytes public constant EIP712_REVISION = bytes('1'); - bytes32 internal constant EIP712_DOMAIN = - keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'); - bytes32 public constant PERMIT_TYPEHASH = - keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'); - - uint256 public constant REVISION = 3; - - /** - * @dev initializes the contract upon assignment to the InitializableAdminUpgradeabilityProxy - */ - function initialize() external initializer {} - - /** - * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md - * @param owner the owner of the funds - * @param spender the spender - * @param value the amount - * @param deadline the deadline timestamp, type(uint256).max for no deadline - * @param v signature param - * @param s signature param - * @param r signature param - */ - - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) external { - require(owner != address(0), 'INVALID_OWNER'); - //solium-disable-next-line - require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); - uint256 currentValidNonce = _nonces[owner]; - bytes32 digest = keccak256( - abi.encodePacked( - '\x19\x01', - DOMAIN_SEPARATOR, - keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline)) - ) - ); - - require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); - unchecked { - // does not make sense to check because it's not realistic to reach uint256.max in nonce - _nonces[owner] = currentValidNonce + 1; - } - _approve(owner, spender, value); - } - - /** - * @dev returns the revision of the implementation contract - */ - function getRevision() internal pure override returns (uint256) { - return REVISION; - } -} diff --git a/certora/harness/storage_harness/README.md b/certora/harness/storage_harness/README.md deleted file mode 100644 index aa1e9ca..0000000 --- a/certora/harness/storage_harness/README.md +++ /dev/null @@ -1,5 +0,0 @@ -This harness replaces DelegationState enum with a simple uint8 in order -to prove invariants about delegation. - -Certora prover has some limitation with writing hooks on an enum field -inside a `struct`, hence the change. \ No newline at end of file diff --git a/certora/properties.md b/certora/properties.md deleted file mode 100644 index cd1ac21..0000000 --- a/certora/properties.md +++ /dev/null @@ -1,43 +0,0 @@ -## functions summary - -### internals - -- \_delegationMoveByType internal: apply operation on proper delegation balance -- \_delegationMove: delegation move by type _voting_, delegation move by type _proposition_ -- \_transferWithDelegation: delegation move with `-` op for `from`, delegation move with `+` op for `to` -- \_getDelegatedPowerByType: returns the voting/proposition power by type -- \_getDelegateeByType: returns the delegate address if user is delegating, or 0 if not -- \_updateDelegateeByType: updates the delegate for user. if delegate == user, then delegate is recorded as 0. -- \_updateDelegationFlagByType: updates the user's flag for delegating by type -- \_delegateByType: the whole delegation process - update voting power and flags - -### externals -- delegateByType: call the internal -- delegate(): call the internal on both types -- getDelegateeByType(): call the internal -- getPowerCurrent(): (if not delegating ) user balance + delegated balance -- metaDelegateByType(): delegate voting power using a signature from the delegator -- metaDelegate: metaDelegateByType for both types - -## ideas for properties - -- transfer where from == to doesn't change delegation balances -- address 0 has no voting/prop power -- \_transferWithDelegation removes delegation from `from` but does nothing on `to` if it's 0. - who can call this? -- delegation flag <=> delegatee != 0 -- anyone can delegate to zero. which means they're forfeiting the voting power. - - -## properties for Aave Token v3 spec - --- on token transfer, the delegation balances change correctly for all cases: -from delegating, to delegating, both delegating, none delegating - --- delegating to 0 == delegating to self. which means the flags are set to false - --- the flags are updated properly after delegation - --- the delegated power is stored divided by 10^10. make sure this doesn't kill precision. - --- \ No newline at end of file diff --git a/certora/specs/base.spec b/certora/specs/base.spec index 90dc5af..9dd2e17 100644 --- a/certora/specs/base.spec +++ b/certora/specs/base.spec @@ -5,7 +5,7 @@ */ -/** +/* Declaration of methods of the Aave token contract (and harness) diff --git a/certora/specs/delegate.spec b/certora/specs/delegate.spec index 1d95e69..e5283c6 100644 --- a/certora/specs/delegate.spec +++ b/certora/specs/delegate.spec @@ -681,6 +681,38 @@ rule votingDelegateChanges(address alice, method f) { f.selector == metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32).selector; } +/* + @Rule + + @Description: + Verify that an account's voting and proposition power changes only as a result of a call to + the delegation and transfer functions + + @Note: + + @Link: +*/ +rule votingPowerChanges(address alice, method f) { + env e; + calldataarg args; + + uint aliceVotingPowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint alicePropPowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + + f(e, args); + + uint aliceVotingPowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint alicePropPowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + + // only these four function may change the power of an address + assert aliceVotingPowerAfter != aliceVotingPowerBefore || alicePropPowerAfter != alicePropPowerBefore => + f.selector == delegate(address).selector || + f.selector == delegateByType(address,uint8).selector || + f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector || + f.selector == metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32).selector || + f.selector == transfer(address,uint256).selector || + f.selector == transferFrom(address,address,uint256).selector; +} /* @Rule @@ -703,10 +735,10 @@ rule delegationTypeIndependence(address who, method f) filtered { f -> !f.isView address delegateeV_ = getVotingDelegate(who); address delegateeP_ = getPropositionDelegate(who); - assert (_delegateeV == delegateeV_ || _delegateeP == delegateeP_) || ( - f.selector == delegate(address).selector || - f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector - ), "one delegatee type stays the same, unless delegate or delegateBySig was called"; + assert _delegateeV != delegateeV_ && _delegateeP != delegateeP_ => + (f.selector == delegate(address).selector || + f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector), + "one delegatee type stays the same, unless delegate or delegateBySig was called"; } /* @@ -746,4 +778,40 @@ rule cantDelegateTwice(address _delegate) { assert votingPowerAfter == votingPowerBefore + normalize(balanceOf(e.msg.sender)); assert propPowerAfter == propPowerBefore + normalize(balanceOf(e.msg.sender)); assert votingPowerAfter2 == votingPowerAfter && propPowerAfter2 == propPowerAfter; +} + +/* + @Rule + + @Description: + transfer and transferFrom change voting/proposition power identically + + @Note: + + @Link: +*/ +rule transferAndTransferFromPowerEquivalence(address bob, uint amount) { + env e1; + env e2; + storage init = lastStorage; + + address alice; + require alice == e1.msg.sender; + + uint aliceVotingPowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint alicePropPowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + + transfer(e1, bob, amount); + + uint aliceVotingPowerAfterTransfer = getPowerCurrent(alice, VOTING_POWER()); + uint alicePropPowerAfterTransfer = getPowerCurrent(alice, PROPOSITION_POWER()); + + transferFrom(e2, alice, bob, amount) at init; + + uint aliceVotingPowerAfterTransferFrom = getPowerCurrent(alice, VOTING_POWER()); + uint alicePropPowerAfterTransferFrom = getPowerCurrent(alice, PROPOSITION_POWER()); + + assert aliceVotingPowerAfterTransfer == aliceVotingPowerAfterTransferFrom && + alicePropPowerAfterTransfer == alicePropPowerAfterTransferFrom; + } \ No newline at end of file diff --git a/certora/specs/general.spec b/certora/specs/general.spec index 39ec616..45ec69f 100644 --- a/certora/specs/general.spec +++ b/certora/specs/general.spec @@ -59,14 +59,14 @@ ghost mapping(address => uint104) balances { init_state axiom forall address a. balances[a] == 0; } -/** +/* Hooks */ -/** +/* This hook updates the sum of delegated and undelegated balances on each change of delegation state. If the user moves from not delegating to delegating, their balance is moved from undelegated to delegating, @@ -106,7 +106,7 @@ hook Sstore _balances[KEY address user].delegationState uint8 new_state (uint8 o } -/** +/* This hook updates the sum of delegated and undelegated balances on each change of user balance. Depending on the delegation state, either the delegated or the undelegated balance get updated. @@ -130,6 +130,12 @@ hook Sstore _balances[KEY address user].balance uint104 balance (uint104 old_bal } +// user's delegation state is always valid, i.e. one of the 4 legitimate states +// (NO_DELEGATION, VOTING_DELEGATED, PROPOSITION_DELEGATED, FULL_POWER_DELEGATED) +// passes +invariant delegationStateValid(address user) + validDelegationState(user) + /* @Rule @@ -150,7 +156,7 @@ invariant delegateCorrectness(address user) ((getPropositionDelegate(user) == user || getPropositionDelegate(user) == 0) <=> !getDelegatingProposition(user)) { preserved { - require getDelegationState(user) <= FULL_POWER_DELEGATED(); + requireInvariant delegationStateValid(user); } } @@ -196,11 +202,13 @@ invariant sumOfPBalancesCorrectness() sumDelegatedBalancesP + sumUndelegatedBala */ rule transferDoesntChangeDelegationState() { env e; - address from; address to; + address from; address to; address charlie; + require (charlie != from && charlie != to); uint amount; uint8 stateFromBefore = getDelegationState(from); uint8 stateToBefore = getDelegationState(to); + uint8 stateCharlieBefore = getDelegationState(charlie); require stateFromBefore <= FULL_POWER_DELEGATED() && stateToBefore <= FULL_POWER_DELEGATED(); bool testFromBefore = isDelegatingVoting[from]; bool testToBefore = isDelegatingVoting[to]; @@ -213,4 +221,5 @@ rule transferDoesntChangeDelegationState() { bool testToAfter = isDelegatingVoting[to]; assert testFromBefore == testFromAfter && testToBefore == testToAfter; + assert getDelegationState(charlie) == stateCharlieBefore; } diff --git a/properties.md b/properties.md index e7ceefc..516b48e 100644 --- a/properties.md +++ b/properties.md @@ -30,6 +30,7 @@ $t_1$ → the state of the system after a transaction. ## General rules +- The total power (of one type) of all users in the system is less or equal than the sum of balances of all AAVE holders (totalSupply of AAVE token): $$\sum powerOfAccount_i <= \sum balanceOf(account_i)$$ - If an account is delegating a power to itself or to `address(0)`, that means that account is not delegating that power to anybody: $$powerOfAccountX = (accountXDelegatingPower \ ? \ 0 : balanceOf(accountX)) + @@ -62,7 +63,7 @@ $t_1$ → the state of the system after a transaction. $$account1Power_{t1} = account1Power_{t0} = 0$$ - $$delegatee1Power_{t1} = delegatee1Power_{t0} - z / 10^{10} * 10^{10}$$ + $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance_{t0} / 10^{10} * 10^{10} + account1Balance_{t1} / 10^{10} * 10^{10}$$ $$account2Power_{t1} = account2Power_{t0} + z$$ @@ -91,7 +92,7 @@ $t_1$ → the state of the system after a transaction. $$account2Power_{t1} = account2Power_{t0} = 0$$ - $$delegatee2Power_{t1}=delegatee2Power_{t0} + z / 10^{10} * 10^{10}$$ + $$delegatee2Power_{t1}=delegatee2Power_{t0} - account2Balance_{t0} / 10^{10} * 10^{10} + account2Balance_{t1} / 10^{10} * 10^{10}$$ ## Account1 is delegating power to delegatee1, **account2** is delegating power to delegatee2 @@ -100,8 +101,8 @@ $t_1$ → the state of the system after a transaction. $$account1Power_{t1} = account1Power_{t0} = 0$$ - $$delegatee1Power_{t1} = delegatee1Power_{t0} - z / 10^{10} * 10^{10}$$ + $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance_{t0} / 10^{10} * 10^{10} + account1Balance_{t1} / 10^{10} * 10^{10}$$ $$account2Power_{t1} = account2Power_{t0} = 0$$ - $$delegatee2Power_{t1}=delegatee2Power_{t0} + z / 10^{10} * 10^{10}$$ + $$delegatee2Power_{t1}=delegatee2Power_{t0} - account2Balance_{t0} / 10^{10} * 10^{10} + account2Balance_{t1} / 10^{10} * 10^{10}$$ \ No newline at end of file From 8456c345cc0873145ccd59147e2d80191b5cc7d1 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Wed, 21 Sep 2022 17:26:39 +0300 Subject: [PATCH 46/58] delete todos --- certora/TODO.md | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 certora/TODO.md diff --git a/certora/TODO.md b/certora/TODO.md deleted file mode 100644 index d7f437a..0000000 --- a/certora/TODO.md +++ /dev/null @@ -1,30 +0,0 @@ -- [v] comment in the Harness file to explain the getters - - -- natspec. formatted like in openzeppelin specs -``` - -/** - -*/ - -``` -- [v] changed 'how to run' instructions to use munged - -- add assert messages [last] - -- [v] make sure properties.md on certora is identical to the main branch - -- [v] delete the properties.md that i wrote - -- [v] invariant delegationStateValid() and then requireInvariant in `delegateCorrectness` - -- [v] voting power changes as a result of only specific calls (transfer, transferFrom, delegate, metadelegate, delegateByType) - -- [v] transferAndTransferFrom are the same: do transfer and transferFrom at the same storage and check voting power transfer - -- [v] delegationTypeIndependence => rewrite into more readable form (either both change) - -- [v] transferDoesntChangeDelegationState: add charlie - -- report: more about community, so many rules were submitted, so many were selected. \ No newline at end of file From 5046629b179563db54aad1c44e7e5f32f4ccf292 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Wed, 21 Sep 2022 18:32:27 +0300 Subject: [PATCH 47/58] add markdown report --- ...al Verification Report of AAVE Token V3.md | 924 ++++++++++++++++++ 1 file changed, 924 insertions(+) create mode 100644 certora/report/Formal Verification Report of AAVE Token V3.md diff --git a/certora/report/Formal Verification Report of AAVE Token V3.md b/certora/report/Formal Verification Report of AAVE Token V3.md new file mode 100644 index 0000000..d96167c --- /dev/null +++ b/certora/report/Formal Verification Report of AAVE Token V3.md @@ -0,0 +1,924 @@ +![Certora logo](https://hackmd.io/_uploads/SkaW6ZXr9.png) + +# Formal Verification Report of AAVE Token V3 + +## Summary + +This document describes the specification and verification of AAVE Token V3 using the Certora Prover. The work was undertaken from July 15th to August 10th, 2022. The latest commit reviewed and run through the Certora Prover was [8bb9f896](https://github.com/bgd-labs/aave-token-v3/commit/8bb9f8966991f8225adbce2b5cd38b5ae3612ecd). + +The scope of this verification is AAVE token V3 code which includes the following contracts: + +* `AaveV3Token.sol` + + +And its parent contracts: + +* `BaseAaveToken.sol` +* `BaseAaveTokenV2.sol` + +This project has been a part of a joint Certora and Aave community program. Contributors from the community have conducted independent formal verification of the code, where Certora has provided an initial setup for writing a specification. + +19 out of the 25 community participants submitted spec files containing formal specifications resulting in 275 properties in total. Out of the 275 correctness rules, 240 quality rules passed our professional review and credited their authors with grants. + +Selected rules written by the community are included in this report in the [_Community_](#Community) section. + +Certora also performed a manual audit of these contracts. + +During this verification process, the Certora Prover discovered issues in the code which are listed in the tables below. + +All the rules and specification files are publicly available and can be found in [AAVE Token V3 repository](https://github.com/bgd-labs/aave-token-v3/tree/certora/certora). + +## List of Main Issues Discovered + +**Severity: **Low**** + +Discovered independently by the following contributors: +https://github.com/Elpacos +https://github.com/himanshu-Bhatt +https://github.com/jessicapointing +and Certora team. + +Discover + +| Issue: | Precision loss during voting power transfer | +| -------- | -------- | +| Description: | When calculating delegated balance on token transfer, the new delegated balance of a delegate was calculated with a small precision loss that violated the property $$delegatee1Power_{t1} = delegatee1Power_{t0} - z / 10^{10} * 10^{10}$$ after a delegator to delegatee1 transfers z amount of tokens. +|Property Violated: | vpTransferWhenOnlyOneIsDelegating (Property #6) and others | +| AAVE Response:| The issue was fixed in commit [a287d134](https://github.com/bgd-labs/aave-token-v3/commit/a287d134903618bed5671d411c66641bfd96002b) and the relevant property was modified to be $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance_{t0} / 10^{10} * 10^{10} + account1Balance_{t1} / 10^{10} * 10^{10}$$ + +## List of Issues Discovered Independently By The Community + +**Severity: **High**** + +Found by the following contributors: +https://github.com/Elpacos + +| Issue: | Wrong parameters order in a `_transferWithDelegation` call | +| -------- | -------- | +| Description: | This issue was present in an intermediary version of the code given to the community to verify, but not in the finalized version that Certora has verified. It was introduced for a short period of time during development, and immediately fixed by the AAVE team. +|Property Violated: | multiple properties | +| AAVE Response:| The issue was fixed in commit [190c03f4](https://github.com/bgd-labs/aave-token-v3/commit/190c03f43208ae85bfcbf7dcb4a0022bb34df169) + + + + + ## Disclaimer + +The Certora Prover takes as input a contract and a specification and formally proves that the contract satisfies the specification in all scenarios. Importantly, the guarantees of the Certora Prover are scoped to the provided specification, and the Certora Prover does not check any cases not covered by the specification. + +We hope that this information is useful, but provide no warranty of any kind, explicit or implied. The contents of this report should not be construed as a complete guarantee that the contract is secure in all dimensions. In no event shall Certora or any of its employees be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the results reported here. + +# Summary of Formal Verification + +## Overview of the AAVE Token V3 + +`AaveV3Token.sol` is the main contract. It inherits from `BaseAaveToken.sol` and `BaseAaveTokenV2.sol`. + +The following description is taken from the token [repository](https://github.com/bgd-labs/aave-token-v3/blob/main/properties.md): + +AAVE is an ERC20 token deployed on Ethereum, which main utility is participating in the Aave governance system via voting on proposals or creating them. + +AAVE is a transparent proxy contract, and its current [implementation](https://etherscan.io/address/0xc13eac3b4f9eed480045113b7af00f7b5655ece8#code) is version 2. + +Together with all the standard ERC20 functionalities, the current implementation includes extra logic mainly for the management and accounting of voting and proposition power. Due to the design/architecture of the Aave governance v2 system, of which AAVE is the main voting asset, the current AAVE implementation makes the token transfers quite gas-consuming, as multiple snapshots of data (voting and proposition power) need to be stored all the time. + +With a new iteration of the Aave governance in the Aave/BGD roadmap down the line, snapshots on the token will not be required anymore for its integration with the governance system. So this new version 3 of AAVE consists mainly of removing the snapshotting, together with adding extra minor meta-transactions capabilities. + +## Assumptions and Simplifications Made During Verification + + + +The invariants in `general.spec` were proven on a slightly modified version of the token code. To bypass a current limitation of the Certora prover, we've refactored the `delegationState` field of the `_balances` struct to be a `uint8` instead of a `DelegationState` enum type. The `AaveV3Token.sol` code was modified to accomodate this change. + +These changes can be seen in the [patch file](https://github.com/bgd-labs/aave-token-v3/blob/certora/certora/applyHarness.patch) in the Certora branch of the token repository. + +To create this harness, we run `make munged` command from the `certora` directory (on the `certora` branch). + +- We unroll loops. Violations that require a loop to execute more than twice will not be detected. + +## Notations + +✔️ indicates the rule is formally verified on the latest reviewed commit. We write ✔️* when the rule was verified on the simplified assumptions described above in "Assumptions and Simplifications Made During Verification". + +❌ indicates the rule was violated under one of the tested versions of the code. + + +🔁 indicates the rule is timing out. + +Our tool uses Hoare triples of the form {p} C {q}, which means that if the execution of program C starts in any state satisfying p, it will end in a state satisfying q. This logical structure is reflected in the included formulae for many of the properties below. Note that p and q here can be thought of as analogous to `require` and `assert` in Solidity. + +The syntax {p} (C1 ~ C2) {q} is a generalization of Hoare rules, called relational properties. {p} is a requirement on the states before C1 and C2, and {q} describes the states after their executions. Notice that C1 and C2 result in different states. As a special case, C1 ~op C2, where op is a getter, indicating that C1 and C2 result in states with the same value for op. + +Our tool consists of a special struct type variable called environment, usually denoted by e. This complex type includes the various block data context accessible by solidity (e.g. block.timestamp, msg.sender, msg.value etc.) +These fields are accessible via the environment variable. + +## Community + +The following properties were written and verified by contributors from the Aave community + +1. **permitIntegrity** +Integrity of permit function - successful permit function increases the nonce of owner by 1 and also changes the allowance of owner to spender. +Contributed by https://github.com/parth-15 +``` + { + nonceBefore = getNonce(owner) + } + < + permit(owner, spender, value, deadline, v, r, s) + > + { + allowance(owner, spender) == value && getNonce(owner) == nonceBefore + 1 + } +``` +2. **addressZeroNoPower** +Address 0 has no voting or proposition power. +Contributed by https://github.com/JayP11 +``` +{ + getPowerCurrent(0, VOTING_POWER) == 0 && getPowerCurrent(0, PROPOSITION_POWER) == && balanceOf(0) == 0 +} +``` + +3. **metaDelegateByTypeOnlyCallableWithProperlySignedArguments** +Verify that `metaDelegateByType` can only be called with a signed request. +Contributed by https://github.com/kustosz +``` + { + ecrecover(v,r,s) != delegator + } + < + metaDelegateByType@withrevert(delegator, delegatee, delegationType, deadline, v, r, s) + > + { + lastReverted == true + } +``` +4. **metaDelegateNonRepeatable** +Verify that it's impossible to use the same arguments to call `metaDalegate` twice. +Contributed by https://github.com/kustosz +``` + { + hash1 = computeMetaDelegateHash(delegator, delegatee, deadline, nonce) + hash2 = computeMetaDelegateHash(delegator, delegatee, deadline, nonce + 1) + ecrecover(hash1, v, r, s) == delegator + } + < + metaDelegate(e1, delegator, delegatee, v, r, s) + metaDelegate@withrevert(e2, delegator, delegatee, delegationType, deadline, v, r, s) + > + { + lastReverted == true + } +``` +5. **delegatingToAnotherUserRemovesPowerFromOldDelegatee** +Power of the previous delegate is removed when the delegatee delegates to another delegate. +Contributed by https://github.com/priyankabhanderi +``` + { + _votingBalance = getDelegatedVotingBalance(alice) + } + < + delegateByType(alice, VOTING_POWER) + delegateByType(bob, VOTING_POWER) + > + { + alice != bob => getDelegatedVotingBalance(alice) == _votingBalance + } +``` +6. **powerChanges** +Voting and proposition power change only as a result of specific functions. +Contributed by https://github.com/top-sekret +``` + { + powerBefore = getPowerCurrent(alice, type) + } + < + f(e, args) + > + { + powerAfter = getPowerCurrent(alice, type) + powerAfter != powerBefore => + f.selector == delegate(address).selector || + f.selector == delegateByType(address, uint8).selector || + f.selector == metaDelegate(address, address, uint256, uint8, bytes32, bytes32).selector || + f.selector == metaDelegateByType(address, address, uint8, uint256, uint8, bytes32, bytes32).selector || + f.selector == transfer(address, uint256).selector || + f.selector == transferFrom(address, address, uint256).selector + } +``` +7. **delegateIndependence** +Changing a delegate of one type doesn't influence the delegate of the other type. +Written by https://github.com/top-sekret +``` + { + delegateBefore = type == 1 ? getPropositionDelegate(e.msg.sender) : getVotingDelegate(e.msg.sender) + } + < + delegateByType(e, delegatee, 1 - type) + > + { + delegateBefore = type == 1 ? getPropositionDelegate(e.msg.sender) : getVotingDelegate(e.msg.sender) + delegateBefore == delegateAfter + } +``` +8. **votingPowerChangesWhileNotBeingADelegatee** +Verify that voting power increases/decreases while not being a voting delegatee yourself. +Contributed by https://github.com/Zarfsec +``` + { + votingPowerBefore = getPowerCurrent(a, VOTING_POWER) + balanceBefore = balanceOf(a) + isVotingDelegatorBefore = getDelegatingVoting(a) + isVotingDelegateeBefore = getDelegatedVotingBalance(a) != 0 + } + < + f(e, args) + > + { + votingPowerAfter = getPowerCurrent(a, VOTING_POWER() + balanceAfter = getBalance(a) + isVotingDelegatorAfter = getDelegatingVoting(a); + isVotingDelegateeAfter = getDelegatedVotingBalance(a) != 0 + + votingPowerBefore < votingPowerAfter <=> + (!isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore < balanceAfter)) || + (isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore != 0)) + && + votingPowerBefore > votingPowerAfter <=> + (!isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore > balanceAfter)) || + (!isVotingDelegatorBefore && isVotingDelegatorAfter && (balanceBefore != 0)) + } +``` + +9. **propositionPowerChangesWhileNotBeingADelegatee** +Verify that proposition power increases/decreases while not being a voting delegatee yourself. +Contributed by https://github.com/Zarfsec +``` + { + propositionPowerBefore = getPowerCurrent(a, PROPOSITION_POWER) + balanceBefore = balanceOf(a) + isPropositionDelegatorBefore = getDelegatingProposition(a) + isPropositionDelegateeBefore = getDelegatedPropositionBalance(a) != 0 + } + < + f(e, args) + > + { + propositionPowerAfter = getPowerCurrent(a, PROPOSITION_POWER() + balanceAfter = getBalance(a) + isPropositionDelegatorAfter = getDelegatingProposition(a); + isPropositionDelegateeAfter = getDelegatedPropositionBalance(a) != 0 + + propositionPowerBefore < propositionPowerAfter <=> + (!isPropositionDelegatorBefore && !isPropositionDelegatorAfter && (balanceBefore < balanceAfter)) || + (isPropositionDelegatorBefore && !isPropositionDelegatorAfter && (balanceBefore != 0)) + && + propositionPowerBefore > propositionPowerAfter <=> + (!isPropositionDelegatorBefore && !isPropositionDelegatorAfter && (balanceBefore > balanceAfter)) || + (!isPropositionDelegatorBefore && isPropositionDelegatorAfter && (balanceBefore != 0)) + } +``` +10. **allowanceStateChange** +Allowance only changes as a result of specific subset of functions. +Contributed by https://github.com/oracleorb +``` + { + allowanceBefore = allowance(owner, spender) + } + < + f(e, args) + > + { + allowance(owner, spender) != allowanceBefore =>f.selector==approve(address,uint256).selector + || f.selector==increaseAllowance(address,uint256).selector + || f.selector==decreaseAllowance(address,uint256).selector + || f.selector==transferFrom(address,address,uint256).selector + || f.selector==permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector + + } +``` + + +## Formal Properties for AaveTokenV3 +The following properties were written and verified by Certora + +### Delegation Invariants + +1. **delegateCorrectness** ✔️ +User's delegation flag is switched on iff user is delegating to an address other than his own own or 0 +``` + { + (getVotingDelegate(account) == account || getVotingDelegate(account) == 0) <=> !getDelegatingVoting(account) + && + (getPropositionDelegate(account) == account || getPropositionDelegate(account) == 0) <=> !getDelegatingProposition(account) + } +``` + +2. **sumOfVBalancesCorrectness** ✔️ +Sum of delegated voting balances and undelegated voting balances is equal to total supply + +$$\sum balances[u'].delegatedVotingBalance * 10^{10} + \sum balanceOf(u) = totalSupply()$$ + +where getVotingDelegate(u) == 0 + +``` + { + sumDelegatedVotingBalances + sumUndelegatedVotingBalances == totalSupply() + } +``` +3. **sumOfPBalancesCorrectness** ✔️ +Sum of delegated proposition balances and undelegated proposition balances is equal to total supply. +$$\sum balances[u'].delegatedPropositionBalance * 10^{10} + \sum balanceOf(u) = totalSupply()$$ + +where getPropositionDelegate(u) == 0 +``` + { + sumDelegatedPropositionBalances + sumUndelegatedPropositionBalances == totalSupply() + } +``` + +### Delegation Properties + + +4. **powerWhenNotDelegating** ✔️ +If an account is not receiving delegation of power (one type) from anybody, +and that account is not delegating that power to anybody, the power of that account must be equal to its token balance. + +``` + { + dvb = _balances[account].delegatedVotingBalance + votingPower = getPowerCurrent(account, VOTING_POWER) + (dvb == 0 && !isDelegatingVoting(account)) => votingPower == balanceOf(account) + } +``` + +5. **vpTransferWhenBothNotDelegating** ✔️ +When both accounts are not delegating: +On transfer of z amount of tokens from account1 to account2, voting power holds the following properties: +$$account1Power_{t1} = account1Power_{t0} - z$$ $$account2Power_{t1} = account2Power_{t0} + z$$ + +``` + { + !isDelegatingVoting(account1) && !isDelegatingVoting(account2) + account1PowerBefore = getPowerCurrent(account1, VOTING_POWER) + account2PowerBefore = getPowerCurrent(account2, VOTING_POWER) + account3PowerBefore = getPowerCurrent(account3, VOTING_POWER) + } + < + transferFrom(account1, account2, z) + > + { + getPowerCurrent(account1, VOTING_POWER) == account1PowerBefore - z + getPowerCurrent(account2, VOTING_POWER) == account2PowerBefore + z + getPowerCurrent(account3, VOTING_POWER) == account3PowerBefore + } +``` +6. **ppTransferWhenBothNotDelegating** ✔️ +When both account1 and account2 are not delegating: +On transfer of z amount of tokens from account1 to account2, proposition power holds the following properties: +$$account1Power_{t1} = account1Power_{t0} - z$$ $$account2Power_{t1} = account2Power_{t0} + z$$ + +``` + { + !isDelegatingProposition(account1) && !isDelegatingProposition(account2) + account1PowerBefore = getPowerCurrent(account1, PROPOSITION_POWER) + account2PowerBefore = getPowerCurrent(account2, PROPOSITION_POWER) + account3PowerBefore = getPowerCurrent(account3, PROPOSITION_POWER) + } + < + transferFrom(account1, account2, z) + > + { + getPowerCurrent(account1, PROPOSITION_POWER) == account1PowerBefore - z + getPowerCurrent(account2, PROPOSITION_POWER) == account2PowerBefore + z + getPowerCurrent(account3, PROPOSITION_POWER) == account3PowerBefore + } +``` +7. **vpDelegateWhenBothNotDelegating** ✔️ +When both account1 and account2 are not delegating: +After account1 will delegate his voting power to account2 + + $$account1Power_{t1} = account1Power_{t0} - account1Balance$$ + + $$account2Power_{t1} = account2Power_{t0} + account1Balance / 10^{10} * 10^{10}$$ + + $$account1PowerDelegatee_{t1} = account2$$ +``` + { + account1 = e.msg.sender + !isDelegatingVoting(account1) && !isDelegatingVoting(account2) + account1PowerBefore = getPowerCurrent(account1, VOTING_POWER) + account2PowerBefore = getPowerCurrent(account2, VOTING_POWER) + account3PowerBefore = getPowerCurrent(account3, VOTING_POWER) + } + < + delegate(account2) + > + { + getPowerCurrent(account1, VOTING_POWER) == account1PowerBefore - balanceOf(account1) + getPowerCurrent(account2, VOTING_POWER) == account2PowerBefore + balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(account3, VOTING_POWER) == account3PowerBefore + } +``` +8. **ppDelegateWhenBothNotDelegating**✔️ + +When both account1 and account2 are not delegating: +After account1 will delegate his proposition power to account2 + $$account1Power_{t1} = account1Power_{t0} - account1Balance$$ + + $$account2Power_{t1} = account2Power_{t0} + account1Balance / 10^{10} * 10^{10}$$ + + $$account1PowerDelegatee_{t1} = account2$$ +``` + { + account1 = e.msg.sender + !isDelegatingProposition(account1) && !isDelegatingProposition(account2) + account1PowerBefore = getPowerCurrent(account1, PROPOSITION_POWER) + account2PowerBefore = getPowerCurrent(account2, PROPOSITION_POWER) + account3PowerBefore = getPowerCurrent(account3, PROPOSITION_POWER) + } + < + delegate(account2) + > + { + getPowerCurrent(account1, PROPOSITION_POWER) == account1PowerBefore - balanceOf(account1) + getPowerCurrent(account2, PROPOSITION_POWER) == account2PowerBefore + balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(account3, PROPOSITION_POWER) == account3PowerBefore + } +``` + +9. **vpTransferWhenOnlyOneIsDelegating** ✔️ +When account1 is delegating voting power to delegatee1 and account2 is not delegating voting power: +On transfer of z amount of tokens from account1 to account2 +$$account1Power_{t1} = account1Power_{t0} = 0$$ + + $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance_{t0} / 10^{10} * 10^{10} + account1Balance_{t1} / 10^{10} * 10^{10}$$ + + $$account2Power_{t1} = account2Power_{t0} + z$$ +``` + { + isDelegatingVoting(account1) && !isDelegatingVoting(account2) + account1PowerBefore = getPowerCurrent(account1, VOTING_POWER) + account2PowerBefore = getPowerCurrent(account2, VOTING_POWER) + account3PowerBefore = getPowerCurrent(account3, VOTING_POWER) + delegatee1PowerBefore = getPowerCurrent(delegatee1, VOTING_POWER) + balanceAccount1Before = balanceOf(account1) + } + < + transferFrom(account1, account2, z) + > + { + getPowerCurrent(account1, VOTING_POWER) == account1PowerBefore == 0 + getPowerCurrent(delegatee1, VOTING_POWER) == delegatee1PowerBefore - balanceAccount1Before / 10^10 * 10^10 + balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(account2, VOTING_POWER) == account2PowerBefore + z + getPowerCurrent(account3, VOTING_POWER) == account3PowerBefore + } +``` +10. **ppTransferWhenOnlyOneIsDelegating** ✔️ +When account1 is delegating proposition power to delegatee1 and account2 is not delegating proposition power: +On transfer of z amount of tokens from account1 to account2 + $$account1Power_{t1} = account1Power_{t0} = 0$$ + + $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance_{t0} / 10^{10} * 10^{10} + account1Balance_{t1} / 10^{10} * 10^{10}$$ + + $$account2Power_{t1} = account2Power_{t0} + z$$ +``` + { + isDelegatingProposition(account1) && !isDelegatingProposition(account2) + account1PowerBefore = getPowerCurrent(account1, PROPOSITION_POWER) + account2PowerBefore = getPowerCurrent(account2, PROPOSITION_POWER) + account3PowerBefore = getPowerCurrent(account3, PROPOSITION_POWER) + delegatee1PowerBefore = getPowerCurrent(delegatee1, PROPOSITION_POWER) + balanceAccount1Before = balanceOf(account1) + } + < + transferFrom(account1, account2, z) + > + { + getPowerCurrent(account1, PROPOSITION_POWER) == account1PowerBefore == 0 + getPowerCurrent(delegatee1, PROPOSITION_POWER) == delegatee1PowerBefore - balanceAccount1Before / 10^10 * 10^10 + balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(account2, PROPOSITION_POWER) == account2PowerBefore + z + getPowerCurrent(account3, PROPOSITION_POWER) == account3PowerBefore + } +``` + +11. **vpStopDelegatingWhenOnlyOneIsDelegating** ✔️ +When account1 is delegating voting power to delegatee1 and account2 is not delegating voting power: +After account will stop delegating voting power to delegatee1 + $$account1Power_{t1} = account1Power_{t0} + account1Balance$$ + + $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance / 10^{10} * 10^{10}$$ + +``` + { + account1 == msg.sender && isDelegatingVoting(account1) + account1PowerBefore = getPowerCurrent(account1, VOTING_POWER) + account2PowerBefore = getPowerCurrent(account2, VOTING_POWER) + account3PowerBefore = getPowerCurrent(account3, VOTING_POWER) + delegatee1PowerBefore = getPowerCurrent(delegatee1, VOTING_POWER) + balanceAccount1Before = balanceOf(account1) + } + < + delegate(0) + > + { + getPowerCurrent(account1, VOTING_POWER) == account1PowerBefore + balanceOfAccount1Before + getPowerCurrent(delegatee1, VOTING_POWER) == delegatee1PowerBefore - balanceAccount1Before / 10^10 * 10^10 + getPowerCurrent(account3, VOTING_POWER) == account3PowerBefore + } +``` + +12. **ppStopDelegatingWhenOnlyOneIsDelegating** ✔️ +When account1 is delegating proposition power to delegatee1 and account2 is not delegating proposition power: +After account will stop delegating proposition power to delegatee1 + $$account1Power_{t1} = account1Power_{t0} + account1Balance$$ + + $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance / 10^{10} * 10^{10}$$ + +``` + { + account1 == msg.sender && isDelegatingProposition(account1) + account1PowerBefore = getPowerCurrent(account1, PROPOSITION_POWER) + account2PowerBefore = getPowerCurrent(account2, PROPOSITION_POWER) + account3PowerBefore = getPowerCurrent(account3, PROPOSITION_POWER) + delegatee1PowerBefore = getPowerCurrent(delegatee1, PROPOSITION_POWER) + balanceAccount1Before = balanceOf(account1) + } + < + delegate(0) + > + { + getPowerCurrent(account1, PROPOSITION_POWER) == account1PowerBefore + balanceOfAccount1Before + getPowerCurrent(delegatee1, PROPOSITION_POWER) == delegatee1PowerBefore - balanceAccount1Before / 10^10 * 10^10 + getPowerCurrent(account3, PROPOSITION_POWER) == account3PowerBefore + } +``` + +13. **vpChangeDelegateWhenOnlyOneIsDelegating** ✔️ +When account1 is delegating voting power to delegatee1 and account2 is not delegating voting power: +After account1 will delegate power to delegatee2 + + $$account1Power_{t1} = account1Power_{t0} = 0$$ + + $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance / 10^{10} * 10^{10}$$ + + $$delegatee2Power_{t1} = delegatee2Power_{t0} + account1Balance / 10^{10} * 10^{10}$$ + + $$account1PowerDelegatee_{t1} = delegatee2$$ +``` + { + account1 == msg.sender && isDelegatingVoting(account1) + account1PowerBefore = getPowerCurrent(account1, VOTING_POWER) + account3PowerBefore = getPowerCurrent(account3, VOTING_POWER) + delegatee1PowerBefore = getPowerCurrent(delegatee1, VOTING_POWER) + delegatee2PowerBefore = getPowerCurrent(delegatee1, VOTING_POWER) + + } + < + delegate(delegatee2) + > + { + getPowerCurrent(account1, VOTING_POWER) == account1PowerBefore == 0 + getPowerCurrent(delegatee1, VOTING_POWER) == delegatee1PowerBefore - balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(delegatee2, VOTING_POWER) == delegatee2PowerBefore + balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(account3, VOTING_POWER) == account3PowerBefore + } +``` + +14. **ppChangeDelegateWhenOnlyOneIsDelegating** ✔️ +When account1 is delegating voting power to delegatee1 and account2 is not delegating voting power: +After account1 will delegate power to delegatee2 + + $$account1Power_{t1} = account1Power_{t0} = 0$$ + + $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance / 10^{10} * 10^{10}$$ + + $$delegatee2Power_{t1} = delegatee2Power_{t0} + account1Balance / 10^{10} * 10^{10}$$ + + $$account1PowerDelegatee_{t1} = delegatee2$$ + +``` + { + account1 == msg.sender && isDelegatingProposition(account1) + account1PowerBefore = getPowerCurrent(account1, PROPOSITION_POWER) + account3PowerBefore = getPowerCurrent(account3, PROPOSITION_POWER) + delegatee1PowerBefore = getPowerCurrent(delegatee1, PROPOSITION_POWER) + delegatee2PowerBefore = getPowerCurrent(delegatee1, PROPOSITION_POWER) + + } + < + delegate(delegatee2) + > + { + getPowerCurrent(account1, PROPOSITION_POWER) == account1PowerBefore == 0 + getPowerCurrent(delegatee1, PROPOSITION_POWER) == delegatee1PowerBefore - balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(delegatee2, PROPOSITION_POWER) == delegatee2PowerBefore + balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(account3, PROPOSITION_POWER) == account3PowerBefore + } +``` + +15. **vpOnlyAccount2IsDelegating** ✔️ +Account1 not delegating voting power to anybody, account2 is delegating voting power to delegatee2: +On transfer of z tokens from account1 to account 2 + $$account1Power_{t1} = account1Power_{t0} - z$$ + + $$account2Power_{t1} = account2Power_{t0} = 0$$ + + $$delegatee2Power_{t1}=delegatee2Power_{t0} - account2Balance_{t0} / 10^{10} * 10^{10} + account2Balance_{t1} / 10^{10} * 10^{10}$$ + +``` + { + isDelegatingVoting(account1) && isDelegatingVoting(account2) + delegatee2 == getVotingDelegate(account2) + account1PowerBefore = getPowerCurrent(account1, VOTING_POWER) + account3PowerBefore = getPowerCurrent(account3, VOTING_POWER) + delegatee2PowerBefore = getPowerCurrent(delegatee2, VOTING_POWER) + account2BalanceBefore == balanceOf(account2) + } + < + transferFrom(account1, account2, z) + > + { + getPowerCurrent(account1, VOTING_POWER) == account1PowerBefore - z + getPowerCurrent(account2, VOTING_POWER) == 0 + getPowerCurrent(delegatee2, VOTING_POWER) == delegatee2PowerBefore - account2BalanceBefore / 10^10 *10^10 + balanceOf(account2) / 10^10 * 10^10 + getPowerCurrent(account3, VOTING_POWER) == account3PowerBefore + } +``` + +16. **ppOnlyAccount2IsDelegating** ✔️ +Account1 not delegating proposition power to anybody, account2 is delegating proposition power to delegatee2: +On transfer of z tokens from account1 to account 2 + $$account1Power_{t1} = account1Power_{t0} - z$$ + + $$account2Power_{t1} = account2Power_{t0} = 0$$ + + $$delegatee2Power_{t1}=delegatee2Power_{t0} - account2Balance_{t0} / 10^{10} * 10^{10} + account2Balance_{t1} / 10^{10} * 10^{10}$$ + +``` + { + isDelegatingProposition(account1) && isDelegatingProposition(account2) + account1PowerBefore = getPowerCurrent(account1, PROPOSITION_POWER) + account3PowerBefore = getPowerCurrent(account3, PROPOSITION_POWER) + delegatee2PowerBefore = getPowerCurrent(delegatee2, PROPOSITION_POWER) + account2BalanceBefore == balanceOf(account2) + } + < + transferFrom(account1, account2, z) + > + { + getPowerCurrent(account1, PROPOSITION_POWER) == account1PowerBefore - z + getPowerCurrent(account2, PROPOSITION_POWER) == 0 + getPowerCurrent(delegatee2, PROPOSITION_POWER) == delegatee2PowerBefore - account2BalanceBefore / 10^10 *10^10 + balanceOf(account2) / 10^10 * 10^10 + getPowerCurrent(account3, PROPOSITION_POWER) == account3PowerBefore + } +``` + +17. **vpTransferWhenBothAreDelegating** ✔️ +Account1 is delegating voting power to delegatee1, account2 is delegating voting power to delegatee2: +On transfer of z tokens from account1 to account2 + $$account1Power_{t1} = account1Power_{t0} = 0$$ + + $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance_{t0} / 10^{10} * 10^{10} + account1Balance_{t1} / 10^{10} * 10^{10}$$ + + $$account2Power_{t1} = account2Power_{t0} = 0$$ + + $$delegatee2Power_{t1}=delegatee2Power_{t0} - account2Balance_{t0} / 10^{10} * 10^{10} + account2Balance_{t1} / 10^{10} * 10^{10}$$ + +``` + { + isDelegatingVoting(account1) && isDelegatingVoting(account2) + account1PowerBefore = getPowerCurrent(account1, VOTING_POWER) + account2PowerBefore = getPowerCurrent(account2, VOTING_POWER) + account3PowerBefore = getPowerCurrent(account3, VOTING_POWER) + delegatee1PowerBefore = getPowerCurrent(delegatee1, VOTING_POWER) + delegatee2PowerBefore = getPowerCurrent(delegatee2, VOTING_POWER) + account1BalanceBefore = balanceOf(account1) + account2BalanceBefore = balanceOf(account2) + } + < + transferFrom(account1, account2, z) + > + { + getPowerCurrent(account1, VOTING_POWER) == account1PowerBefore == 0 + getPowerCurrent(account2, VOTING_POWER) == account2PowerBefore == 0 + getPowerCurrent(delegatee1, VOTING_POWER) == delegatee1PowerBefore - account1BalanceBefore / 10^10 * 10^10 + balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(delegatee2, VOTING_POWER) == delegatee2PowerBefore - account2BalanceBefore / 10^10 * 10^10 + balanceOf(account2) / 10^10 * 10^10 + } +``` + +18. **ppTransferWhenBothAreDelegating** ✔️ +Account1 is delegating proposition power to delegatee1, account2 is delegating proposition power to delegatee2: +On transfer of z tokens from account1 to account2 + $$account1Power_{t1} = account1Power_{t0} = 0$$ + + $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance_{t0} / 10^{10} * 10^{10} + account1Balance_{t1} / 10^{10} * 10^{10}$$ + + $$account2Power_{t1} = account2Power_{t0} = 0$$ + + $$delegatee2Power_{t1}=delegatee2Power_{t0} - account2Balance_{t0} / 10^{10} * 10^{10} + account2Balance_{t1} / 10^{10} * 10^{10}$$ + +``` + { + isDelegatingProposition(account1) && isDelegatingProposition(account2) + account1PowerBefore = getPowerCurrent(account1, PROPOSITION_POWER) + account2PowerBefore = getPowerCurrent(account2, PROPOSITION_POWER) + account3PowerBefore = getPowerCurrent(account3, PROPOSITION_POWER) + delegatee1PowerBefore = getPowerCurrent(delegatee1, PROPOSITION_POWER) + delegatee2PowerBefore = getPowerCurrent(delegatee2, PROPOSITION_POWER) + account1BalanceBefore = balanceOf(account1) + account2BalanceBefore = balanceOf(account2) + } + < + transferFrom(account1, account2, z) + > + { + getPowerCurrent(account1, PROPOSITION_POWER) == account1PowerBefore == 0 + getPowerCurrent(account2, PROPOSITION_POWER) == account2PowerBefore == 0 + getPowerCurrent(delegatee1, PROPOSITION_POWER) == delegatee1PowerBefore - account1BalanceBefore / 10^10 * 10^10 + balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(delegatee2, PROPOSITION_POWER) == delegatee2PowerBefore - account2BalanceBefore / 10^10 * 10^10 + balanceOf(account2) / 10^10 * 10^10 + } +``` + +19. **delegationTypeIndependence** ✔️ +Only delegate() and metaDelegate() may change both voting and +proposition delegates of an account at once. +``` + { + delegateVBefore = getVotingDelegate(account) + delegatePBefore = getPropositionDelegate(account) + } + < + f(e, args) + > + { + delegateVAfter = getVotingDelegate(account) + delegatePAfter = getPropositionDelegate(account) + (delegateVBefore == delegateVAfter || delegatePBefore == delegatePAfter) || (f.selector == delegate(address).selector || f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector) + } +``` + +20. **cantDelegateTwice** ✔️ +Delegating twice to the same delegate _delegate changes the delegate's voting power only once. + +``` + { + votingPowerBefore = getPowerCurrent(_delegate, VOTING_POWER) + propositionPowerBefore = getPowerCurrent(_delegate, PROPOSITION_POWER) + } + < + delegate(_delegate) + votingPowerAfter = getPowerCurrent(_delegate, VOTING_POWER) + propositionPowerAfter = getPowerCurrent(_delegate, PROPOSITION_POWER) + delegate(_delegate) + > + { + getPowerCurrent(_delegate, VOTING_POWER) == votingPowerAfter + getPowerCurrent(_delegate, PROPOSITION_POWER) == propositionPowerAfter + } +``` + +### ERC20 Properties + +21. **transferCorrect** ✔️ +Token transfer works correctly. Balances are updated if not reverted. +If reverted then the transfer amount was too high, or the recipient is 0. +``` + { + balanceFromBefore = balanceOf(msg.sender) + balanceToBefore = balanceOf(to) + } + < + transfer(to, amount) + > + { + lastReverted => to = 0 || amount > balanceOf(msg.sender) + !lastReverted => balanceOf(to) = balanceToBefore + amount && + balanceOf(msg.sender) = balanceFromBefore - amount + } +``` + +22. **transferFromCorrect** ✔️ +Token transferFrom function works correctly. Balances are updated if not reverted. If reverted then the transfer amount was too high, or the recipient is 0, or the allowance was not sufficient +``` + { + balanceFromBefore = balanceOf(from) + balanceToBefore = balanceOf(to) + } + < + transferFrom(from, to, amount) + > + { + lastreverted => to = 0 || amount > balanceOf(from) + !lastreverted => balanceOf(to) = balanceToBefore + amount && + balanceOf(from) = balanceFromBefore - amount + } +``` + +23. **zeroAddressNoBalance** ✔️ +Balance of address 0 is always 0 +``` +{ balanceOf(0) = 0 } +``` + +24. **NoChangeTotalSupply** ✔️ +Contract calls don't change token total supply. +``` + { + supplyBefore = totalSupply() + } + < f(e, args)> + { + supplyAfter = totalSupply() + supplyBefore == supplyAfter + } +``` + +25. **ChangingAllowance** ✔️ +Allowance changes correctly as a result of calls to approve, transfer, increaseAllowance, decreaseAllowance +``` + { + allowanceBefore = allowance(from, spender) + } + < + f(e, args) + > + { + f.selector = approve(spender, amount) => allowance(from, spender) = amount + f.selector = transferFrom(from, spender, amount) => allowance(from, spender) = allowanceBefore - amount + f.selector = decreaseAllowance(spender, delta) => allowance(from, spender) = allowanceBefore - delta + f.selector = increaseAllowance(spender, delta) => allowance(from, spender) = allowanceBefore + delta + generic f.selector => allowance(from, spender) == allowanceBefore + } +``` + +26. **TransferSumOfFromAndToBalancesStaySame** ✔️ +Transfer from msg.sender to b doesn't change the sum of their balances +``` + { + balancesBefore = balanceOf(msg.sender) + balanceOf(b) + } + < + transfer(b, amount) + > + { + balancesBefore == balanceOf(msg.sender) + balanceOf(b) + } +``` + +27. **TransferFromSumOfFromAndToBalancesStaySame** ✔️ +transferFrom from a to b doesn't change the sum of their balances +``` + { + balancesBefore = balanceOf(a) + balanceOf(b) + } + < + transferFrom(a, b) + > + { + balancesBefore == balanceOf(a) + balanceOf(b) + } +``` + +28. **TransferDoesntChangeOtherBalance** ✔️ +Transfer from msg.sender to alice doesn't change the balance of other addresses +``` + { + balanceBefore = balanceOf(charlie) + } + < + transfer(alice, amount) + > + { + balanceOf(charlie) == balanceBefore + } +``` + +29. **TransferFromDoesntChangeOtherBalance** ✔️ +``` + { + balanceBefore = balanceOf(charlie) + } + < + transferFrom(alice, bob, amount) + > + { + balanceOf(charlie) = balanceBefore + } +``` + +30. **OtherBalanceOnlyGoesUp** ✔️ +Balance of an address, who is not a sender or a recipient in transfer functions, doesn't decrease as a result of contract calls +``` + { + balanceBefore = balanceOf(charlie) + } + < + f(e, args) + > + { + f.selector != transfer && f.selector != transferFrom => balanceOf(charlie) == balanceBefore + } +``` + + From d4b0cd1b958af6e86aafcfb225a76d9a49d7f9fc Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Wed, 21 Sep 2022 20:03:56 +0300 Subject: [PATCH 48/58] add PDF report --- ...al Verification Report of AAVE Token V3.pdf | Bin 0 -> 679642 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 certora/report/Formal Verification Report of AAVE Token V3.pdf diff --git a/certora/report/Formal Verification Report of AAVE Token V3.pdf b/certora/report/Formal Verification Report of AAVE Token V3.pdf new file mode 100644 index 0000000000000000000000000000000000000000..556c81baaa87d3e0d0604e4f1dae8ef31e2f5780 GIT binary patch literal 679642 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 }M}c)X%=0<74rN!XIPA@GmG(Sv^`% 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 Date: Thu, 6 Oct 2022 14:08:39 +0300 Subject: [PATCH 49/58] Create temp.yml --- .github/workflows/temp.yml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/temp.yml diff --git a/.github/workflows/temp.yml b/.github/workflows/temp.yml new file mode 100644 index 0000000..18a6a3e --- /dev/null +++ b/.github/workflows/temp.yml @@ -0,0 +1,36 @@ +# This is a basic workflow to help you get started with Actions + +name: CI + +# Controls when the workflow will run +on: + # Triggers the workflow on push or pull request events but only for the "main" branch + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v3 + + # Runs a single command using the runners shell + - name: Run a one-line script + run: echo Hello, world! + + # Runs a set of commands using the runners shell + - name: Run a multi-line script + run: | + echo Add other actions to build, + echo test, and deploy your project. From 04688de61f9b67099e994b398c044765ff976431 Mon Sep 17 00:00:00 2001 From: Michael M Date: Tue, 11 Oct 2022 18:09:28 +0300 Subject: [PATCH 50/58] Certora + Community Formal Verification --- .github/workflows/certora-erc20.yml | 58 ++ .github/workflows/certora.yml | 60 ++ .gitignore | 7 + certora/.gitignore | 5 + certora/Makefile | 26 + certora/README.md | 42 + certora/applyHarness.patch | 79 ++ certora/harness/AaveTokenV3Harness.sol | 55 ++ .../harness/AaveTokenV3HarnessCommunity.sol | 106 ++ certora/harness/AaveTokenV3HarnessStorage.sol | 56 ++ certora/harness/README.md | 7 + ...al Verification Report of AAVE Token V3.md | 924 ++++++++++++++++++ ...l Verification Report of AAVE Token V3.pdf | Bin 0 -> 679642 bytes certora/scripts/erc20.sh | 13 + certora/scripts/verifyCommunity.sh | 14 + certora/scripts/verifyDelegate.sh | 14 + certora/scripts/verifyGeneral.sh | 14 + certora/specs/base.spec | 77 ++ certora/specs/community.spec | 494 ++++++++++ certora/specs/delegate.spec | 817 ++++++++++++++++ certora/specs/erc20.spec | 559 +++++++++++ certora/specs/general.spec | 225 +++++ 22 files changed, 3652 insertions(+) create mode 100644 .github/workflows/certora-erc20.yml create mode 100644 .github/workflows/certora.yml create mode 100644 certora/.gitignore create mode 100644 certora/Makefile create mode 100644 certora/README.md create mode 100644 certora/applyHarness.patch create mode 100644 certora/harness/AaveTokenV3Harness.sol create mode 100644 certora/harness/AaveTokenV3HarnessCommunity.sol create mode 100644 certora/harness/AaveTokenV3HarnessStorage.sol create mode 100644 certora/harness/README.md create mode 100644 certora/report/Formal Verification Report of AAVE Token V3.md create mode 100644 certora/report/Formal Verification Report of AAVE Token V3.pdf create mode 100644 certora/scripts/erc20.sh create mode 100644 certora/scripts/verifyCommunity.sh create mode 100755 certora/scripts/verifyDelegate.sh create mode 100644 certora/scripts/verifyGeneral.sh create mode 100644 certora/specs/base.spec create mode 100644 certora/specs/community.spec create mode 100644 certora/specs/delegate.spec create mode 100644 certora/specs/erc20.spec create mode 100644 certora/specs/general.spec diff --git a/.github/workflows/certora-erc20.yml b/.github/workflows/certora-erc20.yml new file mode 100644 index 0000000..664c381 --- /dev/null +++ b/.github/workflows/certora-erc20.yml @@ -0,0 +1,58 @@ +name: certora-erc20 + +on: + push: + branches: + - main + pull_request: + branches: + - main + + workflow_dispatch: + +jobs: + verify: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Check key + env: + CERTORAKEY: ${{ secrets.CERTORAKEY }} + run: echo "key length" ${#CERTORAKEY} + + - name: Install python + uses: actions/setup-python@v2 + with: { python-version: 3.9 } + + - name: Install java + uses: actions/setup-java@v1 + with: { java-version: "11", java-package: jre } + + - name: Install certora cli + run: pip install certora-cli + + - name: Install solc + run: | + wget https://github.com/ethereum/solidity/releases/download/v0.8.13/solc-static-linux + chmod +x solc-static-linux + sudo mv solc-static-linux /usr/local/bin/solc8.13 + + - name: Verify rule ${{ matrix.rule }} + run: | + cd certora + touch applyHarness.patch + make munged + cd .. + echo "key length" ${#CERTORAKEY} + sh certora/scripts/${{ matrix.rule }} + env: + CERTORAKEY: ${{ secrets.CERTORAKEY }} + + strategy: + fail-fast: false + max-parallel: 16 + matrix: + rule: + - erc20.sh diff --git a/.github/workflows/certora.yml b/.github/workflows/certora.yml new file mode 100644 index 0000000..7dfd691 --- /dev/null +++ b/.github/workflows/certora.yml @@ -0,0 +1,60 @@ +name: certora + +on: + push: + branches: + - main + pull_request: + branches: + - main + + workflow_dispatch: + +jobs: + verify: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Check key + env: + CERTORAKEY: ${{ secrets.CERTORAKEY }} + run: echo "key length" ${#CERTORAKEY} + + - name: Install python + uses: actions/setup-python@v2 + with: { python-version: 3.9 } + + - name: Install java + uses: actions/setup-java@v1 + with: { java-version: "11", java-package: jre } + + - name: Install certora cli + run: pip install certora-cli + + - name: Install solc + run: | + wget https://github.com/ethereum/solidity/releases/download/v0.8.13/solc-static-linux + chmod +x solc-static-linux + sudo mv solc-static-linux /usr/local/bin/solc8.13 + + - name: Verify rule ${{ matrix.rule }} + run: | + cd certora + touch applyHarness.patch + make munged + cd .. + echo "key length" ${#CERTORAKEY} + sh certora/scripts/${{ matrix.rule }} + env: + CERTORAKEY: ${{ secrets.CERTORAKEY }} + + strategy: + fail-fast: false + max-parallel: 16 + matrix: + rule: + - verifyGeneral.sh + - verifyDelegate.sh + - verifyCommunity.sh diff --git a/.gitignore b/.gitignore index 71f35f8..f0a2ae5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,10 @@ out/ node_modules package-lock.json .env + +# Certora +.certora_config/ +.last_confs/ +.certora_* +certora/munged +resource_errors.json diff --git a/certora/.gitignore b/certora/.gitignore new file mode 100644 index 0000000..e47c83b --- /dev/null +++ b/certora/.gitignore @@ -0,0 +1,5 @@ +# certora +.certora* +.certora*.json +**.last_conf* +certora-logs diff --git a/certora/Makefile b/certora/Makefile new file mode 100644 index 0000000..5e6e1bc --- /dev/null +++ b/certora/Makefile @@ -0,0 +1,26 @@ +default: help + +PATCH = applyHarness.patch +CONTRACTS_DIR = ../src +LIB_DIR = ../lib +MUNGED_DIR = munged + +help: + @echo "usage:" + @echo " make clean: remove all generated files (those ignored by git)" + @echo " make $(MUNGED_DIR): create $(MUNGED_DIR) directory by applying the patch file to $(CONTRACTS_DIR)" + @echo " make record: record a new patch file capturing the differences between $(CONTRACTS_DIR) and $(MUNGED_DIR)" + +munged: $(wildcard $(CONTRACTS_DIR)/*.sol) $(PATCH) + rm -rf $@ + cp -r $(CONTRACTS_DIR) $@ + cp -r $(LIB_DIR) $@ + patch -p0 -d $@ < $(PATCH) + +record: + diff -uN $(CONTRACTS_DIR) $(MUNGED_DIR) | sed 's+$(CONTRACTS_DIR)/++g' | sed 's+$(MUNGED_DIR)++g' > $(PATCH) + +clean: + git clean -fdX + touch $(PATCH) + diff --git a/certora/README.md b/certora/README.md new file mode 100644 index 0000000..5891255 --- /dev/null +++ b/certora/README.md @@ -0,0 +1,42 @@ +## Verification Overview +The current directory contains Certora's formal verification of AAVE's V3 token. +In this directory you will find three subdirectories: + +1. specs - Contains all the specification files that were written by Certora for the token code. We have created two spec files, `base.spec`, `delegate.spec`, `erc20.spec` and `general.spec`. +- `base.spec` contains method declarations, CVL functions and definitions used by the main specification files +- `delegate.spec` contains rules that prove various delegation properties +- `erc20.spec` contains rules that prove ERC20 general rules, e.g. correctness of transfer and others +- `general.spec` contains general delegation invariants, e.g. sum of delegated and undelegated balances equals to +total supply + +2. scripts - Contains all the necessary run scripts to execute the spec files on the Certora Prover. These scripts composed of a run command of Certora Prover, contracts to take into account in the verification context, declaration of the compiler and a set of additional settings. +- `verifyDelegate.sh` is a script for running `delegate.spec` +- `verifyGeneral.sh` is a script for running `general.spec` +- `erc20.sh` is a script for running `erc20.spec` + +3. harness - Contains all the inheriting contracts that add/simplify functionalities to the original contract. +We use two harnesses: +- `AaveTokenV3Harness.sol` used by `erc20.sh` and `delegate.sh`. It inherits from the original AaveV3Token +contract and adds a few getter functions. + +- `AaveTokenV3HarnessStorage.sol` used by `general.sh`. It uses a modified a version of AaveV3Token contract +where the `delegationState` field of the `_balances` struct is refactored to be of type uint8 instead of +`DelegationState` enum. This is done in order to bypass a current limitation of the tool and write a hook +on the `delegationState` field (hooks don't support enum fields at the moment) + + +
+ +--- + +## Running Instructions +To run a verification job: + +1. Open terminal and `cd` your way to the main aave-token-v3 directory + +2. Run the script you'd like to get results for: + ``` + sh certora/scripts/verifyDelegate.sh + sh certora/scripts/verifyGeneral.sh + sh certora/scripts/erc20.sh + ``` diff --git a/certora/applyHarness.patch b/certora/applyHarness.patch new file mode 100644 index 0000000..b074b9f --- /dev/null +++ b/certora/applyHarness.patch @@ -0,0 +1,79 @@ +diff -uN AaveTokenV3.sol /AaveTokenV3.sol +--- AaveTokenV3.sol 2022-10-11 16:03:49.000000000 +0300 ++++ /AaveTokenV3.sol 2022-10-11 16:13:48.000000000 +0300 +@@ -210,7 +210,7 @@ + fromBalanceAfter = fromUserState.balance - uint104(amount); + } + _balances[from].balance = fromBalanceAfter; +- if (fromUserState.delegationState != DelegationState.NO_DELEGATION) { ++ if (fromUserState.delegationState != uint8(DelegationState.NO_DELEGATION)) { + _governancePowerTransferByType( + fromUserState.balance, + fromBalanceAfter, +@@ -232,7 +232,7 @@ + toUserState.balance = toBalanceBefore + uint104(amount); + _balances[to] = toUserState; + +- if (toUserState.delegationState != DelegationState.NO_DELEGATION) { ++ if (toUserState.delegationState != uint8(DelegationState.NO_DELEGATION)) { + _governancePowerTransferByType( + toBalanceBefore, + toUserState.balance, +@@ -288,7 +288,7 @@ + : address(0); + } + return +- userState.delegationState >= DelegationState.PROPOSITION_DELEGATED ++ userState.delegationState >= uint8(DelegationState.PROPOSITION_DELEGATED) + ? _propositionDelegateeV2[delegator] + : address(0); + } +@@ -325,16 +325,12 @@ + ) internal pure returns (DelegationAwareBalance memory) { + if (willDelegate) { + // Because GovernancePowerType starts from 0, we should add 1 first, then we apply bitwise OR +- userState.delegationState = DelegationState( +- uint8(userState.delegationState) | (uint8(delegationType) + 1) +- ); ++ userState.delegationState = userState.delegationState | (uint8(delegationType) + 1); + } else { + // First bitwise NEGATION, ie was 01, after XOR with 11 will be 10, + // then bitwise AND, which means it will keep only another delegation type if it exists +- userState.delegationState = DelegationState( +- uint8(userState.delegationState) & +- ((uint8(delegationType) + 1) ^ uint8(DelegationState.FULL_POWER_DELEGATED)) +- ); ++ userState.delegationState = userState.delegationState & ++ ((uint8(delegationType) + 1) ^ uint8(DelegationState.FULL_POWER_DELEGATED)); + } + return userState; + } +diff -uN BaseAaveToken.sol /BaseAaveToken.sol +--- BaseAaveToken.sol 2022-10-11 17:36:35.000000000 +0300 ++++ /BaseAaveToken.sol 2022-10-11 16:20:40.000000000 +0300 +@@ -1,9 +1,9 @@ + // SPDX-License-Identifier: MIT + pragma solidity ^0.8.0; + +-import {Context} from '../lib/openzeppelin-contracts/contracts/utils/Context.sol'; +-import {IERC20} from '../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; +-import {IERC20Metadata} from '../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; ++import {Context} from './lib/openzeppelin-contracts/contracts/utils/Context.sol'; ++import {IERC20} from './lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; ++import {IERC20Metadata} from './lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; + + // Inspired by OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) + abstract contract BaseAaveToken is Context, IERC20Metadata { +@@ -18,7 +18,7 @@ + uint104 balance; + uint72 delegatedPropositionBalance; + uint72 delegatedVotingBalance; +- DelegationState delegationState; ++ uint8 delegationState; // refactored from enum + } + + mapping(address => DelegationAwareBalance) internal _balances; +Common subdirectories: interfaces and /interfaces +Common subdirectories: lib and /lib +Common subdirectories: test and /test +Common subdirectories: utils and /utils diff --git a/certora/harness/AaveTokenV3Harness.sol b/certora/harness/AaveTokenV3Harness.sol new file mode 100644 index 0000000..d0e13c6 --- /dev/null +++ b/certora/harness/AaveTokenV3Harness.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT + +/** + + This is an extension of the AaveTokenV3 with added getters on the _balances fields + + */ + +pragma solidity ^0.8.0; + +import {AaveTokenV3} from "../../src/AaveTokenV3.sol"; + +contract AaveTokenV3Harness is AaveTokenV3 { + + // returns user's token balance, used in some community rules + function getBalance(address user) view public returns (uint104) { + return _balances[user].balance; + } + // returns user's delegated proposition balance + function getDelegatedPropositionBalance(address user) view public returns (uint72) { + return _balances[user].delegatedPropositionBalance; + } + + // returns user's delegated voting balance + function getDelegatedVotingBalance(address user) view public returns (uint72) { + return _balances[user].delegatedVotingBalance; + } + + //returns user's delegating proposition status + function getDelegatingProposition(address user) view public returns (bool) { + return _balances[user].delegationState == DelegationState.PROPOSITION_DELEGATED || + _balances[user].delegationState == DelegationState.FULL_POWER_DELEGATED; + } + + // returns user's delegating voting status + function getDelegatingVoting(address user) view public returns (bool) { + return _balances[user].delegationState == DelegationState.VOTING_DELEGATED || + _balances[user].delegationState == DelegationState.FULL_POWER_DELEGATED; + } + + // returns user's voting delegate + function getVotingDelegate(address user) view public returns (address) { + return _votingDelegateeV2[user]; + } + + // returns user's proposition delegate + function getPropositionDelegate(address user) view public returns (address) { + return _propositionDelegateeV2[user]; + } + + // returns user's delegation state + function getDelegationState(address user) view public returns (DelegationState) { + return _balances[user].delegationState; + } +} \ No newline at end of file diff --git a/certora/harness/AaveTokenV3HarnessCommunity.sol b/certora/harness/AaveTokenV3HarnessCommunity.sol new file mode 100644 index 0000000..ace06bf --- /dev/null +++ b/certora/harness/AaveTokenV3HarnessCommunity.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT + +/** + + This is an extension of the AaveTokenV3 with added getters and view function wrappers needed for + community-written rules + */ + +pragma solidity ^0.8.0; + +import {AaveTokenV3} from "../../src/AaveTokenV3.sol"; + +contract AaveTokenV3Harness is AaveTokenV3 { + function getBalance(address user) view public returns (uint104) { + return _balances[user].balance; + } + + function getDelegatedPropositionBalance(address user) view public returns (uint72) { + return _balances[user].delegatedPropositionBalance; + } + + + function getDelegatedVotingBalance(address user) view public returns (uint72) { + return _balances[user].delegatedVotingBalance; + } + + + function getDelegatingProposition(address user) view public returns (bool) { + return _balances[user].delegationState == DelegationState.PROPOSITION_DELEGATED || + _balances[user].delegationState == DelegationState.FULL_POWER_DELEGATED; + } + + + function getDelegatingVoting(address user) view public returns (bool) { + return _balances[user].delegationState == DelegationState.VOTING_DELEGATED || + _balances[user].delegationState == DelegationState.FULL_POWER_DELEGATED; + } + + function getVotingDelegate(address user) view public returns (address) { + return _votingDelegateeV2[user]; + } + + function getPropositionDelegate(address user) view public returns (address) { + return _propositionDelegateeV2[user]; + } + + function getDelegationState(address user) view public returns (DelegationState) { + return _balances[user].delegationState; + } + + function getNonce(address user) view public returns (uint256) { + return _nonces[user]; + } + + function ecrecoverWrapper( + bytes32 hash, + uint8 v, + bytes32 r, + bytes32 s + ) public pure returns (address) { + return ecrecover(hash, v, r, s); + } + + function computeMetaDelegateHash( + address delegator, + address delegatee, + uint256 deadline, + uint256 nonce + ) public view returns (bytes32) { + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256(abi.encode(DELEGATE_TYPEHASH, delegator, delegatee, nonce, deadline)) + ) + ); + return digest; + } + + function computeMetaDelegateByTypeHash( + address delegator, + address delegatee, + GovernancePowerType delegationType, + uint256 deadline, + uint256 nonce + ) public view returns (bytes32) { + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256( + abi.encode( + DELEGATE_BY_TYPE_TYPEHASH, + delegator, + delegatee, + delegationType, + nonce, + deadline + ) + ) + ) + ); + return digest; + } + +} \ No newline at end of file diff --git a/certora/harness/AaveTokenV3HarnessStorage.sol b/certora/harness/AaveTokenV3HarnessStorage.sol new file mode 100644 index 0000000..4c77b29 --- /dev/null +++ b/certora/harness/AaveTokenV3HarnessStorage.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT + +/** + + This is an extension of the harnessed AaveTokenV3 with added getters on the _balances fields. + The imported harnessed AaveTokenV3 contract uses uint8 instead of an enum for delegation state. + + This modification is introduced to bypass a current Certora Prover limitation on accessing + enum fields inside CVL hooks + + */ + +pragma solidity ^0.8.0; + +import {AaveTokenV3} from "../munged/AaveTokenV3.sol"; + +contract AaveTokenV3Harness is AaveTokenV3 { + function getBalance(address user) view public returns (uint104) { + return _balances[user].balance; + } + + function getDelegatedPropositionBalance(address user) view public returns (uint72) { + return _balances[user].delegatedPropositionBalance; + } + + + function getDelegatedVotingBalance(address user) view public returns (uint72) { + return _balances[user].delegatedVotingBalance; + } + + + function getDelegatingProposition(address user) view public returns (bool) { + uint8 state = _balances[user].delegationState; + return state == uint8(DelegationState.PROPOSITION_DELEGATED) || + state == uint8(DelegationState.FULL_POWER_DELEGATED); + } + + + function getDelegatingVoting(address user) view public returns (bool) { + uint8 state = _balances[user].delegationState; + return state == uint8(DelegationState.VOTING_DELEGATED) || + state == uint8(DelegationState.FULL_POWER_DELEGATED); + } + + function getVotingDelegate(address user) view public returns (address) { + return _votingDelegateeV2[user]; + } + + function getPropositionDelegate(address user) view public returns (address) { + return _propositionDelegateeV2[user]; + } + + function getDelegationState(address user) view public returns (uint8) { + return _balances[user].delegationState; + } +} \ No newline at end of file diff --git a/certora/harness/README.md b/certora/harness/README.md new file mode 100644 index 0000000..e7d1aaa --- /dev/null +++ b/certora/harness/README.md @@ -0,0 +1,7 @@ +We use two harnesses: + +- AaveTokenV3Harness adds a few simple getters to the original AaveTokenV3 code + +- AaveTokenV3HarnessStorage is used to verify general.spec. It changes delegationState field in _balances +to be uint8 instead of DelegationState enum. The harness files are produced by running `make munged` which +patches the original code with `applyHarness.patch` patch. \ No newline at end of file diff --git a/certora/report/Formal Verification Report of AAVE Token V3.md b/certora/report/Formal Verification Report of AAVE Token V3.md new file mode 100644 index 0000000..d96167c --- /dev/null +++ b/certora/report/Formal Verification Report of AAVE Token V3.md @@ -0,0 +1,924 @@ +![Certora logo](https://hackmd.io/_uploads/SkaW6ZXr9.png) + +# Formal Verification Report of AAVE Token V3 + +## Summary + +This document describes the specification and verification of AAVE Token V3 using the Certora Prover. The work was undertaken from July 15th to August 10th, 2022. The latest commit reviewed and run through the Certora Prover was [8bb9f896](https://github.com/bgd-labs/aave-token-v3/commit/8bb9f8966991f8225adbce2b5cd38b5ae3612ecd). + +The scope of this verification is AAVE token V3 code which includes the following contracts: + +* `AaveV3Token.sol` + + +And its parent contracts: + +* `BaseAaveToken.sol` +* `BaseAaveTokenV2.sol` + +This project has been a part of a joint Certora and Aave community program. Contributors from the community have conducted independent formal verification of the code, where Certora has provided an initial setup for writing a specification. + +19 out of the 25 community participants submitted spec files containing formal specifications resulting in 275 properties in total. Out of the 275 correctness rules, 240 quality rules passed our professional review and credited their authors with grants. + +Selected rules written by the community are included in this report in the [_Community_](#Community) section. + +Certora also performed a manual audit of these contracts. + +During this verification process, the Certora Prover discovered issues in the code which are listed in the tables below. + +All the rules and specification files are publicly available and can be found in [AAVE Token V3 repository](https://github.com/bgd-labs/aave-token-v3/tree/certora/certora). + +## List of Main Issues Discovered + +**Severity: **Low**** + +Discovered independently by the following contributors: +https://github.com/Elpacos +https://github.com/himanshu-Bhatt +https://github.com/jessicapointing +and Certora team. + +Discover + +| Issue: | Precision loss during voting power transfer | +| -------- | -------- | +| Description: | When calculating delegated balance on token transfer, the new delegated balance of a delegate was calculated with a small precision loss that violated the property $$delegatee1Power_{t1} = delegatee1Power_{t0} - z / 10^{10} * 10^{10}$$ after a delegator to delegatee1 transfers z amount of tokens. +|Property Violated: | vpTransferWhenOnlyOneIsDelegating (Property #6) and others | +| AAVE Response:| The issue was fixed in commit [a287d134](https://github.com/bgd-labs/aave-token-v3/commit/a287d134903618bed5671d411c66641bfd96002b) and the relevant property was modified to be $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance_{t0} / 10^{10} * 10^{10} + account1Balance_{t1} / 10^{10} * 10^{10}$$ + +## List of Issues Discovered Independently By The Community + +**Severity: **High**** + +Found by the following contributors: +https://github.com/Elpacos + +| Issue: | Wrong parameters order in a `_transferWithDelegation` call | +| -------- | -------- | +| Description: | This issue was present in an intermediary version of the code given to the community to verify, but not in the finalized version that Certora has verified. It was introduced for a short period of time during development, and immediately fixed by the AAVE team. +|Property Violated: | multiple properties | +| AAVE Response:| The issue was fixed in commit [190c03f4](https://github.com/bgd-labs/aave-token-v3/commit/190c03f43208ae85bfcbf7dcb4a0022bb34df169) + + + + + ## Disclaimer + +The Certora Prover takes as input a contract and a specification and formally proves that the contract satisfies the specification in all scenarios. Importantly, the guarantees of the Certora Prover are scoped to the provided specification, and the Certora Prover does not check any cases not covered by the specification. + +We hope that this information is useful, but provide no warranty of any kind, explicit or implied. The contents of this report should not be construed as a complete guarantee that the contract is secure in all dimensions. In no event shall Certora or any of its employees be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the results reported here. + +# Summary of Formal Verification + +## Overview of the AAVE Token V3 + +`AaveV3Token.sol` is the main contract. It inherits from `BaseAaveToken.sol` and `BaseAaveTokenV2.sol`. + +The following description is taken from the token [repository](https://github.com/bgd-labs/aave-token-v3/blob/main/properties.md): + +AAVE is an ERC20 token deployed on Ethereum, which main utility is participating in the Aave governance system via voting on proposals or creating them. + +AAVE is a transparent proxy contract, and its current [implementation](https://etherscan.io/address/0xc13eac3b4f9eed480045113b7af00f7b5655ece8#code) is version 2. + +Together with all the standard ERC20 functionalities, the current implementation includes extra logic mainly for the management and accounting of voting and proposition power. Due to the design/architecture of the Aave governance v2 system, of which AAVE is the main voting asset, the current AAVE implementation makes the token transfers quite gas-consuming, as multiple snapshots of data (voting and proposition power) need to be stored all the time. + +With a new iteration of the Aave governance in the Aave/BGD roadmap down the line, snapshots on the token will not be required anymore for its integration with the governance system. So this new version 3 of AAVE consists mainly of removing the snapshotting, together with adding extra minor meta-transactions capabilities. + +## Assumptions and Simplifications Made During Verification + + + +The invariants in `general.spec` were proven on a slightly modified version of the token code. To bypass a current limitation of the Certora prover, we've refactored the `delegationState` field of the `_balances` struct to be a `uint8` instead of a `DelegationState` enum type. The `AaveV3Token.sol` code was modified to accomodate this change. + +These changes can be seen in the [patch file](https://github.com/bgd-labs/aave-token-v3/blob/certora/certora/applyHarness.patch) in the Certora branch of the token repository. + +To create this harness, we run `make munged` command from the `certora` directory (on the `certora` branch). + +- We unroll loops. Violations that require a loop to execute more than twice will not be detected. + +## Notations + +✔️ indicates the rule is formally verified on the latest reviewed commit. We write ✔️* when the rule was verified on the simplified assumptions described above in "Assumptions and Simplifications Made During Verification". + +❌ indicates the rule was violated under one of the tested versions of the code. + + +🔁 indicates the rule is timing out. + +Our tool uses Hoare triples of the form {p} C {q}, which means that if the execution of program C starts in any state satisfying p, it will end in a state satisfying q. This logical structure is reflected in the included formulae for many of the properties below. Note that p and q here can be thought of as analogous to `require` and `assert` in Solidity. + +The syntax {p} (C1 ~ C2) {q} is a generalization of Hoare rules, called relational properties. {p} is a requirement on the states before C1 and C2, and {q} describes the states after their executions. Notice that C1 and C2 result in different states. As a special case, C1 ~op C2, where op is a getter, indicating that C1 and C2 result in states with the same value for op. + +Our tool consists of a special struct type variable called environment, usually denoted by e. This complex type includes the various block data context accessible by solidity (e.g. block.timestamp, msg.sender, msg.value etc.) +These fields are accessible via the environment variable. + +## Community + +The following properties were written and verified by contributors from the Aave community + +1. **permitIntegrity** +Integrity of permit function - successful permit function increases the nonce of owner by 1 and also changes the allowance of owner to spender. +Contributed by https://github.com/parth-15 +``` + { + nonceBefore = getNonce(owner) + } + < + permit(owner, spender, value, deadline, v, r, s) + > + { + allowance(owner, spender) == value && getNonce(owner) == nonceBefore + 1 + } +``` +2. **addressZeroNoPower** +Address 0 has no voting or proposition power. +Contributed by https://github.com/JayP11 +``` +{ + getPowerCurrent(0, VOTING_POWER) == 0 && getPowerCurrent(0, PROPOSITION_POWER) == && balanceOf(0) == 0 +} +``` + +3. **metaDelegateByTypeOnlyCallableWithProperlySignedArguments** +Verify that `metaDelegateByType` can only be called with a signed request. +Contributed by https://github.com/kustosz +``` + { + ecrecover(v,r,s) != delegator + } + < + metaDelegateByType@withrevert(delegator, delegatee, delegationType, deadline, v, r, s) + > + { + lastReverted == true + } +``` +4. **metaDelegateNonRepeatable** +Verify that it's impossible to use the same arguments to call `metaDalegate` twice. +Contributed by https://github.com/kustosz +``` + { + hash1 = computeMetaDelegateHash(delegator, delegatee, deadline, nonce) + hash2 = computeMetaDelegateHash(delegator, delegatee, deadline, nonce + 1) + ecrecover(hash1, v, r, s) == delegator + } + < + metaDelegate(e1, delegator, delegatee, v, r, s) + metaDelegate@withrevert(e2, delegator, delegatee, delegationType, deadline, v, r, s) + > + { + lastReverted == true + } +``` +5. **delegatingToAnotherUserRemovesPowerFromOldDelegatee** +Power of the previous delegate is removed when the delegatee delegates to another delegate. +Contributed by https://github.com/priyankabhanderi +``` + { + _votingBalance = getDelegatedVotingBalance(alice) + } + < + delegateByType(alice, VOTING_POWER) + delegateByType(bob, VOTING_POWER) + > + { + alice != bob => getDelegatedVotingBalance(alice) == _votingBalance + } +``` +6. **powerChanges** +Voting and proposition power change only as a result of specific functions. +Contributed by https://github.com/top-sekret +``` + { + powerBefore = getPowerCurrent(alice, type) + } + < + f(e, args) + > + { + powerAfter = getPowerCurrent(alice, type) + powerAfter != powerBefore => + f.selector == delegate(address).selector || + f.selector == delegateByType(address, uint8).selector || + f.selector == metaDelegate(address, address, uint256, uint8, bytes32, bytes32).selector || + f.selector == metaDelegateByType(address, address, uint8, uint256, uint8, bytes32, bytes32).selector || + f.selector == transfer(address, uint256).selector || + f.selector == transferFrom(address, address, uint256).selector + } +``` +7. **delegateIndependence** +Changing a delegate of one type doesn't influence the delegate of the other type. +Written by https://github.com/top-sekret +``` + { + delegateBefore = type == 1 ? getPropositionDelegate(e.msg.sender) : getVotingDelegate(e.msg.sender) + } + < + delegateByType(e, delegatee, 1 - type) + > + { + delegateBefore = type == 1 ? getPropositionDelegate(e.msg.sender) : getVotingDelegate(e.msg.sender) + delegateBefore == delegateAfter + } +``` +8. **votingPowerChangesWhileNotBeingADelegatee** +Verify that voting power increases/decreases while not being a voting delegatee yourself. +Contributed by https://github.com/Zarfsec +``` + { + votingPowerBefore = getPowerCurrent(a, VOTING_POWER) + balanceBefore = balanceOf(a) + isVotingDelegatorBefore = getDelegatingVoting(a) + isVotingDelegateeBefore = getDelegatedVotingBalance(a) != 0 + } + < + f(e, args) + > + { + votingPowerAfter = getPowerCurrent(a, VOTING_POWER() + balanceAfter = getBalance(a) + isVotingDelegatorAfter = getDelegatingVoting(a); + isVotingDelegateeAfter = getDelegatedVotingBalance(a) != 0 + + votingPowerBefore < votingPowerAfter <=> + (!isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore < balanceAfter)) || + (isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore != 0)) + && + votingPowerBefore > votingPowerAfter <=> + (!isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore > balanceAfter)) || + (!isVotingDelegatorBefore && isVotingDelegatorAfter && (balanceBefore != 0)) + } +``` + +9. **propositionPowerChangesWhileNotBeingADelegatee** +Verify that proposition power increases/decreases while not being a voting delegatee yourself. +Contributed by https://github.com/Zarfsec +``` + { + propositionPowerBefore = getPowerCurrent(a, PROPOSITION_POWER) + balanceBefore = balanceOf(a) + isPropositionDelegatorBefore = getDelegatingProposition(a) + isPropositionDelegateeBefore = getDelegatedPropositionBalance(a) != 0 + } + < + f(e, args) + > + { + propositionPowerAfter = getPowerCurrent(a, PROPOSITION_POWER() + balanceAfter = getBalance(a) + isPropositionDelegatorAfter = getDelegatingProposition(a); + isPropositionDelegateeAfter = getDelegatedPropositionBalance(a) != 0 + + propositionPowerBefore < propositionPowerAfter <=> + (!isPropositionDelegatorBefore && !isPropositionDelegatorAfter && (balanceBefore < balanceAfter)) || + (isPropositionDelegatorBefore && !isPropositionDelegatorAfter && (balanceBefore != 0)) + && + propositionPowerBefore > propositionPowerAfter <=> + (!isPropositionDelegatorBefore && !isPropositionDelegatorAfter && (balanceBefore > balanceAfter)) || + (!isPropositionDelegatorBefore && isPropositionDelegatorAfter && (balanceBefore != 0)) + } +``` +10. **allowanceStateChange** +Allowance only changes as a result of specific subset of functions. +Contributed by https://github.com/oracleorb +``` + { + allowanceBefore = allowance(owner, spender) + } + < + f(e, args) + > + { + allowance(owner, spender) != allowanceBefore =>f.selector==approve(address,uint256).selector + || f.selector==increaseAllowance(address,uint256).selector + || f.selector==decreaseAllowance(address,uint256).selector + || f.selector==transferFrom(address,address,uint256).selector + || f.selector==permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector + + } +``` + + +## Formal Properties for AaveTokenV3 +The following properties were written and verified by Certora + +### Delegation Invariants + +1. **delegateCorrectness** ✔️ +User's delegation flag is switched on iff user is delegating to an address other than his own own or 0 +``` + { + (getVotingDelegate(account) == account || getVotingDelegate(account) == 0) <=> !getDelegatingVoting(account) + && + (getPropositionDelegate(account) == account || getPropositionDelegate(account) == 0) <=> !getDelegatingProposition(account) + } +``` + +2. **sumOfVBalancesCorrectness** ✔️ +Sum of delegated voting balances and undelegated voting balances is equal to total supply + +$$\sum balances[u'].delegatedVotingBalance * 10^{10} + \sum balanceOf(u) = totalSupply()$$ + +where getVotingDelegate(u) == 0 + +``` + { + sumDelegatedVotingBalances + sumUndelegatedVotingBalances == totalSupply() + } +``` +3. **sumOfPBalancesCorrectness** ✔️ +Sum of delegated proposition balances and undelegated proposition balances is equal to total supply. +$$\sum balances[u'].delegatedPropositionBalance * 10^{10} + \sum balanceOf(u) = totalSupply()$$ + +where getPropositionDelegate(u) == 0 +``` + { + sumDelegatedPropositionBalances + sumUndelegatedPropositionBalances == totalSupply() + } +``` + +### Delegation Properties + + +4. **powerWhenNotDelegating** ✔️ +If an account is not receiving delegation of power (one type) from anybody, +and that account is not delegating that power to anybody, the power of that account must be equal to its token balance. + +``` + { + dvb = _balances[account].delegatedVotingBalance + votingPower = getPowerCurrent(account, VOTING_POWER) + (dvb == 0 && !isDelegatingVoting(account)) => votingPower == balanceOf(account) + } +``` + +5. **vpTransferWhenBothNotDelegating** ✔️ +When both accounts are not delegating: +On transfer of z amount of tokens from account1 to account2, voting power holds the following properties: +$$account1Power_{t1} = account1Power_{t0} - z$$ $$account2Power_{t1} = account2Power_{t0} + z$$ + +``` + { + !isDelegatingVoting(account1) && !isDelegatingVoting(account2) + account1PowerBefore = getPowerCurrent(account1, VOTING_POWER) + account2PowerBefore = getPowerCurrent(account2, VOTING_POWER) + account3PowerBefore = getPowerCurrent(account3, VOTING_POWER) + } + < + transferFrom(account1, account2, z) + > + { + getPowerCurrent(account1, VOTING_POWER) == account1PowerBefore - z + getPowerCurrent(account2, VOTING_POWER) == account2PowerBefore + z + getPowerCurrent(account3, VOTING_POWER) == account3PowerBefore + } +``` +6. **ppTransferWhenBothNotDelegating** ✔️ +When both account1 and account2 are not delegating: +On transfer of z amount of tokens from account1 to account2, proposition power holds the following properties: +$$account1Power_{t1} = account1Power_{t0} - z$$ $$account2Power_{t1} = account2Power_{t0} + z$$ + +``` + { + !isDelegatingProposition(account1) && !isDelegatingProposition(account2) + account1PowerBefore = getPowerCurrent(account1, PROPOSITION_POWER) + account2PowerBefore = getPowerCurrent(account2, PROPOSITION_POWER) + account3PowerBefore = getPowerCurrent(account3, PROPOSITION_POWER) + } + < + transferFrom(account1, account2, z) + > + { + getPowerCurrent(account1, PROPOSITION_POWER) == account1PowerBefore - z + getPowerCurrent(account2, PROPOSITION_POWER) == account2PowerBefore + z + getPowerCurrent(account3, PROPOSITION_POWER) == account3PowerBefore + } +``` +7. **vpDelegateWhenBothNotDelegating** ✔️ +When both account1 and account2 are not delegating: +After account1 will delegate his voting power to account2 + + $$account1Power_{t1} = account1Power_{t0} - account1Balance$$ + + $$account2Power_{t1} = account2Power_{t0} + account1Balance / 10^{10} * 10^{10}$$ + + $$account1PowerDelegatee_{t1} = account2$$ +``` + { + account1 = e.msg.sender + !isDelegatingVoting(account1) && !isDelegatingVoting(account2) + account1PowerBefore = getPowerCurrent(account1, VOTING_POWER) + account2PowerBefore = getPowerCurrent(account2, VOTING_POWER) + account3PowerBefore = getPowerCurrent(account3, VOTING_POWER) + } + < + delegate(account2) + > + { + getPowerCurrent(account1, VOTING_POWER) == account1PowerBefore - balanceOf(account1) + getPowerCurrent(account2, VOTING_POWER) == account2PowerBefore + balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(account3, VOTING_POWER) == account3PowerBefore + } +``` +8. **ppDelegateWhenBothNotDelegating**✔️ + +When both account1 and account2 are not delegating: +After account1 will delegate his proposition power to account2 + $$account1Power_{t1} = account1Power_{t0} - account1Balance$$ + + $$account2Power_{t1} = account2Power_{t0} + account1Balance / 10^{10} * 10^{10}$$ + + $$account1PowerDelegatee_{t1} = account2$$ +``` + { + account1 = e.msg.sender + !isDelegatingProposition(account1) && !isDelegatingProposition(account2) + account1PowerBefore = getPowerCurrent(account1, PROPOSITION_POWER) + account2PowerBefore = getPowerCurrent(account2, PROPOSITION_POWER) + account3PowerBefore = getPowerCurrent(account3, PROPOSITION_POWER) + } + < + delegate(account2) + > + { + getPowerCurrent(account1, PROPOSITION_POWER) == account1PowerBefore - balanceOf(account1) + getPowerCurrent(account2, PROPOSITION_POWER) == account2PowerBefore + balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(account3, PROPOSITION_POWER) == account3PowerBefore + } +``` + +9. **vpTransferWhenOnlyOneIsDelegating** ✔️ +When account1 is delegating voting power to delegatee1 and account2 is not delegating voting power: +On transfer of z amount of tokens from account1 to account2 +$$account1Power_{t1} = account1Power_{t0} = 0$$ + + $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance_{t0} / 10^{10} * 10^{10} + account1Balance_{t1} / 10^{10} * 10^{10}$$ + + $$account2Power_{t1} = account2Power_{t0} + z$$ +``` + { + isDelegatingVoting(account1) && !isDelegatingVoting(account2) + account1PowerBefore = getPowerCurrent(account1, VOTING_POWER) + account2PowerBefore = getPowerCurrent(account2, VOTING_POWER) + account3PowerBefore = getPowerCurrent(account3, VOTING_POWER) + delegatee1PowerBefore = getPowerCurrent(delegatee1, VOTING_POWER) + balanceAccount1Before = balanceOf(account1) + } + < + transferFrom(account1, account2, z) + > + { + getPowerCurrent(account1, VOTING_POWER) == account1PowerBefore == 0 + getPowerCurrent(delegatee1, VOTING_POWER) == delegatee1PowerBefore - balanceAccount1Before / 10^10 * 10^10 + balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(account2, VOTING_POWER) == account2PowerBefore + z + getPowerCurrent(account3, VOTING_POWER) == account3PowerBefore + } +``` +10. **ppTransferWhenOnlyOneIsDelegating** ✔️ +When account1 is delegating proposition power to delegatee1 and account2 is not delegating proposition power: +On transfer of z amount of tokens from account1 to account2 + $$account1Power_{t1} = account1Power_{t0} = 0$$ + + $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance_{t0} / 10^{10} * 10^{10} + account1Balance_{t1} / 10^{10} * 10^{10}$$ + + $$account2Power_{t1} = account2Power_{t0} + z$$ +``` + { + isDelegatingProposition(account1) && !isDelegatingProposition(account2) + account1PowerBefore = getPowerCurrent(account1, PROPOSITION_POWER) + account2PowerBefore = getPowerCurrent(account2, PROPOSITION_POWER) + account3PowerBefore = getPowerCurrent(account3, PROPOSITION_POWER) + delegatee1PowerBefore = getPowerCurrent(delegatee1, PROPOSITION_POWER) + balanceAccount1Before = balanceOf(account1) + } + < + transferFrom(account1, account2, z) + > + { + getPowerCurrent(account1, PROPOSITION_POWER) == account1PowerBefore == 0 + getPowerCurrent(delegatee1, PROPOSITION_POWER) == delegatee1PowerBefore - balanceAccount1Before / 10^10 * 10^10 + balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(account2, PROPOSITION_POWER) == account2PowerBefore + z + getPowerCurrent(account3, PROPOSITION_POWER) == account3PowerBefore + } +``` + +11. **vpStopDelegatingWhenOnlyOneIsDelegating** ✔️ +When account1 is delegating voting power to delegatee1 and account2 is not delegating voting power: +After account will stop delegating voting power to delegatee1 + $$account1Power_{t1} = account1Power_{t0} + account1Balance$$ + + $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance / 10^{10} * 10^{10}$$ + +``` + { + account1 == msg.sender && isDelegatingVoting(account1) + account1PowerBefore = getPowerCurrent(account1, VOTING_POWER) + account2PowerBefore = getPowerCurrent(account2, VOTING_POWER) + account3PowerBefore = getPowerCurrent(account3, VOTING_POWER) + delegatee1PowerBefore = getPowerCurrent(delegatee1, VOTING_POWER) + balanceAccount1Before = balanceOf(account1) + } + < + delegate(0) + > + { + getPowerCurrent(account1, VOTING_POWER) == account1PowerBefore + balanceOfAccount1Before + getPowerCurrent(delegatee1, VOTING_POWER) == delegatee1PowerBefore - balanceAccount1Before / 10^10 * 10^10 + getPowerCurrent(account3, VOTING_POWER) == account3PowerBefore + } +``` + +12. **ppStopDelegatingWhenOnlyOneIsDelegating** ✔️ +When account1 is delegating proposition power to delegatee1 and account2 is not delegating proposition power: +After account will stop delegating proposition power to delegatee1 + $$account1Power_{t1} = account1Power_{t0} + account1Balance$$ + + $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance / 10^{10} * 10^{10}$$ + +``` + { + account1 == msg.sender && isDelegatingProposition(account1) + account1PowerBefore = getPowerCurrent(account1, PROPOSITION_POWER) + account2PowerBefore = getPowerCurrent(account2, PROPOSITION_POWER) + account3PowerBefore = getPowerCurrent(account3, PROPOSITION_POWER) + delegatee1PowerBefore = getPowerCurrent(delegatee1, PROPOSITION_POWER) + balanceAccount1Before = balanceOf(account1) + } + < + delegate(0) + > + { + getPowerCurrent(account1, PROPOSITION_POWER) == account1PowerBefore + balanceOfAccount1Before + getPowerCurrent(delegatee1, PROPOSITION_POWER) == delegatee1PowerBefore - balanceAccount1Before / 10^10 * 10^10 + getPowerCurrent(account3, PROPOSITION_POWER) == account3PowerBefore + } +``` + +13. **vpChangeDelegateWhenOnlyOneIsDelegating** ✔️ +When account1 is delegating voting power to delegatee1 and account2 is not delegating voting power: +After account1 will delegate power to delegatee2 + + $$account1Power_{t1} = account1Power_{t0} = 0$$ + + $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance / 10^{10} * 10^{10}$$ + + $$delegatee2Power_{t1} = delegatee2Power_{t0} + account1Balance / 10^{10} * 10^{10}$$ + + $$account1PowerDelegatee_{t1} = delegatee2$$ +``` + { + account1 == msg.sender && isDelegatingVoting(account1) + account1PowerBefore = getPowerCurrent(account1, VOTING_POWER) + account3PowerBefore = getPowerCurrent(account3, VOTING_POWER) + delegatee1PowerBefore = getPowerCurrent(delegatee1, VOTING_POWER) + delegatee2PowerBefore = getPowerCurrent(delegatee1, VOTING_POWER) + + } + < + delegate(delegatee2) + > + { + getPowerCurrent(account1, VOTING_POWER) == account1PowerBefore == 0 + getPowerCurrent(delegatee1, VOTING_POWER) == delegatee1PowerBefore - balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(delegatee2, VOTING_POWER) == delegatee2PowerBefore + balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(account3, VOTING_POWER) == account3PowerBefore + } +``` + +14. **ppChangeDelegateWhenOnlyOneIsDelegating** ✔️ +When account1 is delegating voting power to delegatee1 and account2 is not delegating voting power: +After account1 will delegate power to delegatee2 + + $$account1Power_{t1} = account1Power_{t0} = 0$$ + + $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance / 10^{10} * 10^{10}$$ + + $$delegatee2Power_{t1} = delegatee2Power_{t0} + account1Balance / 10^{10} * 10^{10}$$ + + $$account1PowerDelegatee_{t1} = delegatee2$$ + +``` + { + account1 == msg.sender && isDelegatingProposition(account1) + account1PowerBefore = getPowerCurrent(account1, PROPOSITION_POWER) + account3PowerBefore = getPowerCurrent(account3, PROPOSITION_POWER) + delegatee1PowerBefore = getPowerCurrent(delegatee1, PROPOSITION_POWER) + delegatee2PowerBefore = getPowerCurrent(delegatee1, PROPOSITION_POWER) + + } + < + delegate(delegatee2) + > + { + getPowerCurrent(account1, PROPOSITION_POWER) == account1PowerBefore == 0 + getPowerCurrent(delegatee1, PROPOSITION_POWER) == delegatee1PowerBefore - balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(delegatee2, PROPOSITION_POWER) == delegatee2PowerBefore + balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(account3, PROPOSITION_POWER) == account3PowerBefore + } +``` + +15. **vpOnlyAccount2IsDelegating** ✔️ +Account1 not delegating voting power to anybody, account2 is delegating voting power to delegatee2: +On transfer of z tokens from account1 to account 2 + $$account1Power_{t1} = account1Power_{t0} - z$$ + + $$account2Power_{t1} = account2Power_{t0} = 0$$ + + $$delegatee2Power_{t1}=delegatee2Power_{t0} - account2Balance_{t0} / 10^{10} * 10^{10} + account2Balance_{t1} / 10^{10} * 10^{10}$$ + +``` + { + isDelegatingVoting(account1) && isDelegatingVoting(account2) + delegatee2 == getVotingDelegate(account2) + account1PowerBefore = getPowerCurrent(account1, VOTING_POWER) + account3PowerBefore = getPowerCurrent(account3, VOTING_POWER) + delegatee2PowerBefore = getPowerCurrent(delegatee2, VOTING_POWER) + account2BalanceBefore == balanceOf(account2) + } + < + transferFrom(account1, account2, z) + > + { + getPowerCurrent(account1, VOTING_POWER) == account1PowerBefore - z + getPowerCurrent(account2, VOTING_POWER) == 0 + getPowerCurrent(delegatee2, VOTING_POWER) == delegatee2PowerBefore - account2BalanceBefore / 10^10 *10^10 + balanceOf(account2) / 10^10 * 10^10 + getPowerCurrent(account3, VOTING_POWER) == account3PowerBefore + } +``` + +16. **ppOnlyAccount2IsDelegating** ✔️ +Account1 not delegating proposition power to anybody, account2 is delegating proposition power to delegatee2: +On transfer of z tokens from account1 to account 2 + $$account1Power_{t1} = account1Power_{t0} - z$$ + + $$account2Power_{t1} = account2Power_{t0} = 0$$ + + $$delegatee2Power_{t1}=delegatee2Power_{t0} - account2Balance_{t0} / 10^{10} * 10^{10} + account2Balance_{t1} / 10^{10} * 10^{10}$$ + +``` + { + isDelegatingProposition(account1) && isDelegatingProposition(account2) + account1PowerBefore = getPowerCurrent(account1, PROPOSITION_POWER) + account3PowerBefore = getPowerCurrent(account3, PROPOSITION_POWER) + delegatee2PowerBefore = getPowerCurrent(delegatee2, PROPOSITION_POWER) + account2BalanceBefore == balanceOf(account2) + } + < + transferFrom(account1, account2, z) + > + { + getPowerCurrent(account1, PROPOSITION_POWER) == account1PowerBefore - z + getPowerCurrent(account2, PROPOSITION_POWER) == 0 + getPowerCurrent(delegatee2, PROPOSITION_POWER) == delegatee2PowerBefore - account2BalanceBefore / 10^10 *10^10 + balanceOf(account2) / 10^10 * 10^10 + getPowerCurrent(account3, PROPOSITION_POWER) == account3PowerBefore + } +``` + +17. **vpTransferWhenBothAreDelegating** ✔️ +Account1 is delegating voting power to delegatee1, account2 is delegating voting power to delegatee2: +On transfer of z tokens from account1 to account2 + $$account1Power_{t1} = account1Power_{t0} = 0$$ + + $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance_{t0} / 10^{10} * 10^{10} + account1Balance_{t1} / 10^{10} * 10^{10}$$ + + $$account2Power_{t1} = account2Power_{t0} = 0$$ + + $$delegatee2Power_{t1}=delegatee2Power_{t0} - account2Balance_{t0} / 10^{10} * 10^{10} + account2Balance_{t1} / 10^{10} * 10^{10}$$ + +``` + { + isDelegatingVoting(account1) && isDelegatingVoting(account2) + account1PowerBefore = getPowerCurrent(account1, VOTING_POWER) + account2PowerBefore = getPowerCurrent(account2, VOTING_POWER) + account3PowerBefore = getPowerCurrent(account3, VOTING_POWER) + delegatee1PowerBefore = getPowerCurrent(delegatee1, VOTING_POWER) + delegatee2PowerBefore = getPowerCurrent(delegatee2, VOTING_POWER) + account1BalanceBefore = balanceOf(account1) + account2BalanceBefore = balanceOf(account2) + } + < + transferFrom(account1, account2, z) + > + { + getPowerCurrent(account1, VOTING_POWER) == account1PowerBefore == 0 + getPowerCurrent(account2, VOTING_POWER) == account2PowerBefore == 0 + getPowerCurrent(delegatee1, VOTING_POWER) == delegatee1PowerBefore - account1BalanceBefore / 10^10 * 10^10 + balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(delegatee2, VOTING_POWER) == delegatee2PowerBefore - account2BalanceBefore / 10^10 * 10^10 + balanceOf(account2) / 10^10 * 10^10 + } +``` + +18. **ppTransferWhenBothAreDelegating** ✔️ +Account1 is delegating proposition power to delegatee1, account2 is delegating proposition power to delegatee2: +On transfer of z tokens from account1 to account2 + $$account1Power_{t1} = account1Power_{t0} = 0$$ + + $$delegatee1Power_{t1} = delegatee1Power_{t0} - account1Balance_{t0} / 10^{10} * 10^{10} + account1Balance_{t1} / 10^{10} * 10^{10}$$ + + $$account2Power_{t1} = account2Power_{t0} = 0$$ + + $$delegatee2Power_{t1}=delegatee2Power_{t0} - account2Balance_{t0} / 10^{10} * 10^{10} + account2Balance_{t1} / 10^{10} * 10^{10}$$ + +``` + { + isDelegatingProposition(account1) && isDelegatingProposition(account2) + account1PowerBefore = getPowerCurrent(account1, PROPOSITION_POWER) + account2PowerBefore = getPowerCurrent(account2, PROPOSITION_POWER) + account3PowerBefore = getPowerCurrent(account3, PROPOSITION_POWER) + delegatee1PowerBefore = getPowerCurrent(delegatee1, PROPOSITION_POWER) + delegatee2PowerBefore = getPowerCurrent(delegatee2, PROPOSITION_POWER) + account1BalanceBefore = balanceOf(account1) + account2BalanceBefore = balanceOf(account2) + } + < + transferFrom(account1, account2, z) + > + { + getPowerCurrent(account1, PROPOSITION_POWER) == account1PowerBefore == 0 + getPowerCurrent(account2, PROPOSITION_POWER) == account2PowerBefore == 0 + getPowerCurrent(delegatee1, PROPOSITION_POWER) == delegatee1PowerBefore - account1BalanceBefore / 10^10 * 10^10 + balanceOf(account1) / 10^10 * 10^10 + getPowerCurrent(delegatee2, PROPOSITION_POWER) == delegatee2PowerBefore - account2BalanceBefore / 10^10 * 10^10 + balanceOf(account2) / 10^10 * 10^10 + } +``` + +19. **delegationTypeIndependence** ✔️ +Only delegate() and metaDelegate() may change both voting and +proposition delegates of an account at once. +``` + { + delegateVBefore = getVotingDelegate(account) + delegatePBefore = getPropositionDelegate(account) + } + < + f(e, args) + > + { + delegateVAfter = getVotingDelegate(account) + delegatePAfter = getPropositionDelegate(account) + (delegateVBefore == delegateVAfter || delegatePBefore == delegatePAfter) || (f.selector == delegate(address).selector || f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector) + } +``` + +20. **cantDelegateTwice** ✔️ +Delegating twice to the same delegate _delegate changes the delegate's voting power only once. + +``` + { + votingPowerBefore = getPowerCurrent(_delegate, VOTING_POWER) + propositionPowerBefore = getPowerCurrent(_delegate, PROPOSITION_POWER) + } + < + delegate(_delegate) + votingPowerAfter = getPowerCurrent(_delegate, VOTING_POWER) + propositionPowerAfter = getPowerCurrent(_delegate, PROPOSITION_POWER) + delegate(_delegate) + > + { + getPowerCurrent(_delegate, VOTING_POWER) == votingPowerAfter + getPowerCurrent(_delegate, PROPOSITION_POWER) == propositionPowerAfter + } +``` + +### ERC20 Properties + +21. **transferCorrect** ✔️ +Token transfer works correctly. Balances are updated if not reverted. +If reverted then the transfer amount was too high, or the recipient is 0. +``` + { + balanceFromBefore = balanceOf(msg.sender) + balanceToBefore = balanceOf(to) + } + < + transfer(to, amount) + > + { + lastReverted => to = 0 || amount > balanceOf(msg.sender) + !lastReverted => balanceOf(to) = balanceToBefore + amount && + balanceOf(msg.sender) = balanceFromBefore - amount + } +``` + +22. **transferFromCorrect** ✔️ +Token transferFrom function works correctly. Balances are updated if not reverted. If reverted then the transfer amount was too high, or the recipient is 0, or the allowance was not sufficient +``` + { + balanceFromBefore = balanceOf(from) + balanceToBefore = balanceOf(to) + } + < + transferFrom(from, to, amount) + > + { + lastreverted => to = 0 || amount > balanceOf(from) + !lastreverted => balanceOf(to) = balanceToBefore + amount && + balanceOf(from) = balanceFromBefore - amount + } +``` + +23. **zeroAddressNoBalance** ✔️ +Balance of address 0 is always 0 +``` +{ balanceOf(0) = 0 } +``` + +24. **NoChangeTotalSupply** ✔️ +Contract calls don't change token total supply. +``` + { + supplyBefore = totalSupply() + } + < f(e, args)> + { + supplyAfter = totalSupply() + supplyBefore == supplyAfter + } +``` + +25. **ChangingAllowance** ✔️ +Allowance changes correctly as a result of calls to approve, transfer, increaseAllowance, decreaseAllowance +``` + { + allowanceBefore = allowance(from, spender) + } + < + f(e, args) + > + { + f.selector = approve(spender, amount) => allowance(from, spender) = amount + f.selector = transferFrom(from, spender, amount) => allowance(from, spender) = allowanceBefore - amount + f.selector = decreaseAllowance(spender, delta) => allowance(from, spender) = allowanceBefore - delta + f.selector = increaseAllowance(spender, delta) => allowance(from, spender) = allowanceBefore + delta + generic f.selector => allowance(from, spender) == allowanceBefore + } +``` + +26. **TransferSumOfFromAndToBalancesStaySame** ✔️ +Transfer from msg.sender to b doesn't change the sum of their balances +``` + { + balancesBefore = balanceOf(msg.sender) + balanceOf(b) + } + < + transfer(b, amount) + > + { + balancesBefore == balanceOf(msg.sender) + balanceOf(b) + } +``` + +27. **TransferFromSumOfFromAndToBalancesStaySame** ✔️ +transferFrom from a to b doesn't change the sum of their balances +``` + { + balancesBefore = balanceOf(a) + balanceOf(b) + } + < + transferFrom(a, b) + > + { + balancesBefore == balanceOf(a) + balanceOf(b) + } +``` + +28. **TransferDoesntChangeOtherBalance** ✔️ +Transfer from msg.sender to alice doesn't change the balance of other addresses +``` + { + balanceBefore = balanceOf(charlie) + } + < + transfer(alice, amount) + > + { + balanceOf(charlie) == balanceBefore + } +``` + +29. **TransferFromDoesntChangeOtherBalance** ✔️ +``` + { + balanceBefore = balanceOf(charlie) + } + < + transferFrom(alice, bob, amount) + > + { + balanceOf(charlie) = balanceBefore + } +``` + +30. **OtherBalanceOnlyGoesUp** ✔️ +Balance of an address, who is not a sender or a recipient in transfer functions, doesn't decrease as a result of contract calls +``` + { + balanceBefore = balanceOf(charlie) + } + < + f(e, args) + > + { + f.selector != transfer && f.selector != transferFrom => balanceOf(charlie) == balanceBefore + } +``` + + diff --git a/certora/report/Formal Verification Report of AAVE Token V3.pdf b/certora/report/Formal Verification Report of AAVE Token V3.pdf new file mode 100644 index 0000000000000000000000000000000000000000..556c81baaa87d3e0d0604e4f1dae8ef31e2f5780 GIT binary patch literal 679642 zcmb@s1ymeg(=A_tw+Z-S<{KbgEJ^Y@F=eXmoQYM_Xu|03g88)CNshm_yP90(NtB z0Wc~#`dHi9fjM~CfdB(WC9s*bgPWtPl@LJQ!3|;ufHnYB)B!pGPM|R-k1?MC6F}U_ z$qu3oG5u)m#=*nI$IitIRs1NYp`^$Pu(P&>0AwL%wvJFiNh=pedk6<75Ni9MH$WY1 z0d}$eFC`{44pkRNb9XZcbQJ2g)?g0kU;*|}Y3PxE4bR%qK?>{!0WeAlf`A|pPyocq z$p-}S@UZ}a4A2i$PRY^y|1KgbiUx5o|Ho;r|90LC4B$lLkWd0}C_B2?gYEuXi2J{V zG`yW401h1$QyYkx8ybhYyQ$m1TIB7)mJl=!ZEJHkD*%X>AB{r}Vr^;V2H@j@j@!}B z(M8<}Yz9@8f_PY)LDXa=pj}zJxvD~3BpvOY933DIZUCs3jJ2H`)O8LSJE)IR5Hm+} zs8vOXgC*1~Cy<|yS5y?>>IOYc_Gn&tXNHmb>XSd;jO(o5qpV3N!7);mQlWOC!Ts#S zv-YUbX}G`AYbXLxqbLw2u`hNm;+JKHIo{>bBFMqP8Tt3*dhc(^D^Hy-t#S3_e{C9H z%!=SHrNXe5lO*|>6!BV3`&a)+1uHIsJ4}w101F-S_7>{~3;hlQ{aF(oBXs}e2>a*E z8+V`L%b%0BkgsFv+GtZoozD94DMevFf2?WjZiTn53Or;9nj#w?OwK8Gaxdek79iIq~67wIf3zU=1k~>4VFX z{GwDS`zp+VR*g4lHHnY<-CJUT*!c;=$;n&^Vu>EUoEY zS-abro6k4R8^2s10?`dLR-Z>k~o~C^7EY-MH?@UQgWMX(1J`s^E+*~%wBw8J+BgV+z`5t!-iHJ9C;hx`)*g*6JN;^ z*Yp06q-l2#zZ_6Jr0b_wP8y%%k;R@Qf5MP~0%V0FL*`KKi@DbxFzNBsZ3`Ee`>-|a zu%NP3BzlNU?It3HLaqgulFty_N0YT8uZikcUz%~D(7kTb0yyYwRQYxlwc*c8Y4s` z%55Ib6*HSu?siGeSxKg-VIS8X0R=$7N-ZgdJ!tD&&(QdtFl!|>Qy`1>VHuzHov~eb zl~ZZE8Rcbp+y~6Mx5XcoBcA?We>$+9J!m$-5ifrpj+5SU z%B;~Jt(5+y`>bp|{GoX#=54AlvC91(XX()hTtaM#Za;1m+GQDbflHh& zu!ar2{g|xWL&c1+{7i*k`<|RGGODW#ACUh6F>^*E&I|L*^(8|@wEAroo+8X_n0~*} zDkl@=yNi8lo!|GlIW~uluG!AH!$(K+5O4V*PEn~ZFM$(Fw5^oC9b?yz;b^akHIa_+eX1Q*!<;w$Rkh1< z*1W+!pNYW(dM0VEv){LqJ;b)*T!q@jKm5%Q7hE-lpvXmcR-JVPTOFW&AzVsmV)PF8 zcdk0T3}b@H8G1%8udzu6{zT=d+iQb5$!$Mp|J*A9zo%GY3Njh}eYb?(N(`5YXPREoA4k#^=GU+x zhsi>aCG@^_NhaB7ypCQ_WaHS0&oK9bKhXz46U0nGMnXYGArq*1IWkgFMItk3%7du+ z=khYjG$n|L`O^vv-Qbpp(y6FlxW`lpXRqqsNJWZh!c+~|X2`PJhwscc?VenZ>pG-~ zMmkOvQJFxunxOmvo4DpJYdQ7QU%cve`wA$pB^HTJ(_C>)E5uR4V;r~!_SwObxrX_S zI7c%jt`;XuR>XIwJ0I{%KQPW@FaiyFDJSP==y77=n*01Fve1|{Ww-Gz4VFg!LO58^ z#6P)VNU-7y~<3ZvmFxHy=dm66R7r|2BDQdvZ z+*RQgmabW`*h$@EOIOCLGm2GD7U=B?AK zlW!hr0{qB2p~|$(V*%2s)$Aq1%A{qeoTh|es#4!P1tn9J$~wiJBF3T5Ufk1c?zUNb zOS3gJEV!uf$#sS1c}ll~Z{gK88P*zh@W8A(o>r5y^T89lQ4-s^LKKsTH0_UG*RMbB zXvzt$L@Idw>`4o7T4m6CNCes=EX_r{p6ybIdTS7`KAW-3v}UZ;H&Y-+EW6o}=l{U5 z$zA#Li@zw>vkHA_YPl+hGyAU(Yq7rh&$B8)S~=6oXgD>JW(ykk%FkElI@LB5h2Jz9 z21b3KX6Mj$;G<5uT&m;RKzb`OMc-w3D@ z0tPzfBm#XAKiY*xj-a>g3R*=g+_&7ft#{gu5zp+MA0v`^eS1`K)`G2~EqpJr(bdLF z59LxP|7M*hMY|!QaLvw#kKhnP4nWJjWBpz@G87n`@12lpNxDhTIwTsf-)DY1i6EDB zl2b>>{~i!z9v|Ss`8$wdb}H0lidt77O5z{}+bf+&Oquk0HChXkkzq-V{0HE0CAJ^L z+hzt#UVtYOC^VM(YHAie$$FbUQuQ_R39*k;Grsmg6@HDe7G|?o-9iVoFQpbj*v=zOy0hYYx&aFxe#L;m1L}E^@+;UHN8Hcz88xuBLAHx91n4 zz8e&Hk<5X+In?F)LY<02Em3ekC9+)l>oHu6`YGJU3}vftfAbgO?x+xw_Nip5L2*SV zq5EYj6;wxzn5TH33w6w+@;qy%+z_*hWYbO<@WtWf?2JR`-kAu=#sPxd{DZ9L!Y-6c zT#Nd{4fsCE;CIRX#zHr@QIK-we zG(S;n*v2^&zPhqnoB511I;b{F-qunDjl@b8*G&qXjqzPM?ENY;7;Ci%?C0_faUb>R z{++gHJ3hD{pDgz%e%z2$YEvTv#5g*n_)Zb@?2E7a0;!f!l zp-k3$II^vUy*81+>+`+N`;EYx-Xu&+%(c3@14J3{3nX-@$<%#lGKR#eb$fFJdJ60p zqFpnnjoXR$(Sqa$mTMD@zNfq^$D`r(o57SZXuC0dDnHBDRz%}P6}xztwQak(#j3z| zKfG<(7BZ=%#xR}y%%-2P9^Q*<#3}rEZ(53YBgeQjm4DnRzruwZ$LPcz#Y@e0&#iZZ z*wERqCsD~zLKql7fhGQDJz_8_h{899uD6N8@`jokb`b{k6pHb|b%&O$T16 z?bxQc9>w7-!aJdo_}C0v&~HncrFq?EW(yBzHq_R=(l3>eN>5<+DBT=+28uB6E*WB` zE^E_qInML9y-Rr_k0e;M)Dx2A#YYr$M{6;~1Z=M2>DNG)KdQYP;e2I8jfim#@!k&l znE9&4qnk6TTWdD1$s9xyGvN(4N-v=HcqPkY}-;UU##2EQ#^5vt;$%rxwYKCwXH)dQMOIO&*W)NTptqkp&DFIus8nZw2rer!b za|)YgJ0^mjWdWZS$p^8!(bhzC03kr*h$ZyIM7JfhvM@`&aldafRA}iAAzOYsnh3U# z^I!te?d>v3FFPfrKWO|GCC9o6lLGm~)LhD&PW8jH#>(o@)jL=bIK!xsMb_bw-P}eW zqwi-5t6xcv5Rrxr4t)=wHOp>eIgwTZ=3dQckqCXp+_0V*-Uw16vD2yJt4Q!ac>fU; zhF8~A#N^dB7$W83#ilV?b@EtLvR^)2pv%;#ivibDgcj0YaGM z0y??DHlO8%Q0lvxh>Qk5Nwjk3Sud~O*p}cyq#^t>`!on!7=^8vSaD2(79JmkU&bhG7DAD| z>J`2FyYpV&B`)e2hDPCJ*cV}b`3pRQoFe;R&iJ21#=5E>JYT;35r<7mBwj*J((ZSH zi6avZl&+-c<1DH3%#zdQYh}Gf%P^&tj=?Uws0cK=(4$b$_e-3_XG?Le@-t_)OrIsg#y^g&rH2ol&^vvMrld_8z zn+G`dA$~y(aH(_966+5X#{7A4C-BVzWh7_VGC|M@_zXc=P{B=}MLUXjoO2u0L-hAm zeUF=`xMnrwQ~QG~5G06CBiVyUbBd2b^B^F=9Er$+=tJv{+%Bvp0wRp--P!#nhE1bT zAOu3Q;95nEf+-YnT?l^d>njxLDzC?zIn66-x}4k7FH6~QI`6|1aap#iMW<1dA&z?- zZL4JQcwdu`V)3z0zuYT~fN%~c8{C!vD@b{Z+=PCCX z74(>R`0bkfW`ACi(lnFoYfT+RF@hrrB7vu2U`RT-CXrJ#n*PUlNQr50;O}_}g3kQ< zRmvsq%T6Fp0uc1Vr z^j0+D?Vk(Gti62#mVEWZLA|M~X$?N=#u*-AhWH>X z9AFjppewHhh4=6W{28giF=PKgFuW35pWYtE&wi)Uos}MC9HsB)|O+41Vp^3nC=`$|SRQcXN z->$?zB^)>9_l1P0eX?zPK0V5PTun~KCQ^Bqj72c(V$^mI)LR_vcidz%YIpvaxQ7tm z=%VXPhFJ1R_)>eLoNj_I(Y8YM?^yQ;1vAvOjsmC72~8S4C!vlsL`4Y?Ld*aj;ddz*qZbs^95L} zvwUO_1#<6@rdhYd68SVOE^ZNKQe7^IR)EI8)GyZSvSTXm? zm$I)madN52HH*x|WoApE0945x(q*B&Gwm4~#`KTF1Z5~B4f*9DuP;b<)b_R%C`j&= zL8L_9bwzSYUe}I4P;QhLw2G=cWppl@&AWDxrH$J^34_W!y|^jpiR;@nRw>{mPc#>v zII}S#=yv=SS30ox+VZpp=iA&(77iv^vZp93f2w>d+bd}84JqrQUT?mnQZL8}hZF$y ze3TsB4juU=oKc5(5C?@)tS;aJM0AP7T%6!_OHkl%<|%YiHu|m3EL*{YxlYJ|Gp82i z8G&#L6dp>PI_kVsBBzc+QDq_-ak~dN8JlDonz5keX0NqzCY~vBqp*d8 zlPb_nz54NWSWy(@@cdn;_A${hsvDc%sN)u|hH(MEZ!pa&1|)VpFIV@9!E{|eBD;!e zOdCHlBu~K(6uT#Zg$3|nVMT)q13C^{sq_&HWzPN{;4sqg%yt;lxfAvEH+(A|hE6T1 z$Cs?`Ap&AUnZlc{Ptd!b?nV$*<&O|RGVykr@oqPAQ1?f_#JeoY@`8o|M|+B3qQ zhcW-5P>S6tzHM<6ruI@wJ>*Hp4`mS$x$EFmhmw|YjlYj08?o3)@&1;sjg^Z20dtF` zvPvV?=0d{`tFRe5lKKZPcabg;^%(Z#dl*X4`LGnQZ{WwW$mZ_v5K0l|YlK(oIFnWa z1;WuWtqmb{2FJ8G`$3Cue+$2-e5mwnpM&KS3Lhxuvl=v_Aj>z_a<1>F)e7EXF+ev8 zEisy5P6&KBJAVAfrQQ)kbE~rT->4H*zz6f-w$WMr9R+iPsmROrI_W*3R9w2vL>fYh zQzw>H(>4Zn_mVmVE&(mOPgJp3J7v3rxw97Q;SA$)WGZk!l1Umqwr;&ln@KTmqj59c zb>80~az|q%M#<8R5pyBY;$rMX3&fx6V;167PYEDcuexnMNOU6?M4oOEdUQ4WZ5rb| zg1jj^EsQyvEi@GOeQoO%Xp5i2 zpJQ;PV!KR($|uXUPo!Uw;wAuu(j^UsK2}~*0oNBgmK%PT4^cXCtx@AzY{R;&NY#&j zb3LQ=?`L!*nn(L{4oC~{E(cl4Q0rU=qPoy&j;~vsD7_<2XjT&PG&VX39lxctp8p$l8b(v87B-cm}ab#z~qxMIUk?v$8Oa_vjVd+nNgwf1r9fi&W{k zG5XaFqWi07^;vz)-oB94oU0RoUjil1!jWGnb(%e*mcq)?xbJf|{2?wCs zF~#8UX%sMeX{9XYWo_RjmCc+-F2hU7FP^c-O^~Lk>}VT3c!`oW7nL38I=xV3>)(so z`)Lz^5?5toGuQ|p===CMy^DXm5^E9wY#>Ce*9|<OOWV{bb0o&j8Ce z#`S%W73%67ZPE5e4jNyki>L!8q-_kBS!y$dehR5okrL8W|7S#>Nv(+Doab-Eg~u| zfsbS&e3blmTi4n=osdDDBK%eJi$$JxBQD%W3f0a)^X2v6(_l2oA)GI=z&N?vktV(i zQ{MBi1j%>UVHX-(eMYOZ>gMJhTW?>TAhei#_fb-KE}4pV$d=-%z>uqeh54fh3aaHO zxjS|l-#VcFY^5%vvi_U@Xp79t1pKC3(c8|@?Tpf5>!BY;*Pn7cshz)i!B>84K9W7G zqR(c3_K@^`V&Y7@28WaLANg26ZCQ*z)@N~iF!?=W?{|p=h_X7V!F3q{Hfgm6(ps>$ zYJEH~yRV>);lWxt8S_k3{N`H!>6|DbvJgl@Mq=Z43A9<$I#!cS3!U8c87vj{dv!S$ zl?qF4LZPW@wPpG_0Q<%h7iUW3oE?I#Y5GAu&g6t=GoQR8%W}f~hsR^n5qrSX^nSqe zqmm|}iJyt^4zEKwJN~9`&8eRR`xNoo=vG1NLsM`4y(8~#7+ zNSkdQz*<)bHB2#<18`3HcI1_PO!Ttv`o!Se0QXWP?+aYm$0V1su@>ut@EA?~N|Q)Z z6b$I4rV@O!pk+FXHVQZ~B769K$aE$43AFB+oGE)uxBe?ulrPgi>OucuX~na)G_MVm zg5FKB7HmV~grV|XM@LOd{X=^FI&Pqk0T?C>!}ocrH~9G5Gb zQ$`I|oO-&hg&xFlDyN_LOr-Aa1``W<47wnENVskp;pt5m& z9k_LsH*TR$`%Vb>=XScDk@_MjwCT?fD46$Mb z|D0dz@;~+%o=(C8;z+dKmxNO;7`kVf3?f3nE~f;ayB4;ma>IvXTO@z}3B!%CZ12lZ z|3a6Td~TIbhN0C~NbV`37m%Z5vy6QIB{z z>cl4+j4C@e&V=Mz-z2JmUA?GhOyiNh)!cpk9FSWWaNxuUz(3ZTP{wE)*8D4*8ifjo0BA$?Y0WmZfCnjYxm6$8y6qIaJX78uAsbuw5g|QiD#;gzNx2ADun}4}b;-j>6xafDty z1qte6K8`G}g>-6U2^r{a(4xiTMhy85my!3lDHV*BmF_L7Z2HpMk7GyBi;bJ{o>$oV z7B!;;{CO$o5GdzBlibdi*w&`6??ImmIevuf(DZ>*3jARdlomeow~dtAM=Ez0!o63_ z8`!%i37a{Y@tP?puOm=g>4a7qY33cIyY%r1dzeVm_K8|QGvDvqf2T;SNRvEZpWc;D zEM|S2{dp{?0i>Ox^W#~J?|ON;4#hsw{Q`_EY9buzPuJ^qMi3-CWjPD_EhFH)zvqZ= zh?VWkgPD|VB8APQSoszrz?4oIhq^tpimqhez{os!|#VX37M8p!kd);<{ab`2dycN+pHD$@+`#97lEw&d=jtnAlX1g=5 zhtu$?=CEvBipJyu@krg}LofMv1bU2~m_IkXCYleiqZ+x5wGjmaP-(vAMnWvniq$4s z1Flobk#ZACp1~~0A;fnavTWv-*&d=f^CnrSN`!$qWJB3zbT-U+qoMSxZ!FRQNBg?> z4)p00+r`(bvBqqb&8^OHa?>Eh;XKyM;@b6Zho)>@sIDS8_}4_~D9@}w(9&an9-fc- z{q{%q<}^dA3;g`h@^E;W9Kn5L8nY~R)f7U`jN&G=(1*wYU@3KlcGWqfZb zSrZMO$JP1L7XxEHd{0IuUv4KNZ6EhXf&E6(p`m#y|z~HhiybmRR z%OdnF;fb9`9kx{BIO|DB{?qRKuJv>2d8f5_?AFkZd2oFzPvX{bkH>wn_C@K_)a#9v z^j`|U3jvB2%$FCIqX-&*}ZD@uF;&8{w}Dytqhins(h_aX@$xK zoV@Bfm4keKczmhuAqaiPn`pETJNL|69pR{hR-!Ox`=xP%VDLp3UaN(Y(70y)_qhn%NZx$E#B+)Jo5fniKH-EBcFEgnzX-C1-rN!Sg&chJI5uoV! zEO3M}XWTo|eiE#_U9xe(%Vx#9=$5xlRsor8!t;qNwg0B(cRH~Wf-A{pvdl@uI$DUpxT3MR z&c5Plbe(LW^k8Rrxq^EMZ_1ezH)%HjD0ni%B>b&fJTl_doi@34)9>unxpvpV>e1q3 zF-P`PF^cwa?FT!pnFRaqn!*6cRrX@s6TF&RGp5dOe?ylJBMt~h_6e+0(=AHe^s;+r zYjJ(xd)bH~$%UDoG}^CBuWOOhvM%-BQgozE`O)Zgt;TVmrZi)JS{_~wX+7%{%fA(p z3xQDV11@+wmmHeU@-lphpfQ2#g;4G=!p7|Xw#U*v>DrypFasqY0_4qo8TBYcm)hU5 z0Kl;%I$Ac|>TZWY7_La3b#~?rQ@&x& zd#HgnV<mNjwipLIIgY1 z{m(W%ZnwG`G}!)&3Hl$TPz&)lIv@cKJYD0w4%f0>aD31mzoZsJpqi zo4KiiUH+j1Lji!k{+osj`VX@5e>wmE3NaTiFFS|_zysuB7vTK|Uv3a56kh-jCm%ca zKMns6;Qkxee}QRgY0hQ`Hg)9ygFPT@ZjQDP2R08b4k*s{P&zojsi}YkzW^_fj zKM2GFHa9hcfJ}MJ%(?hYdB6}ZUQQ6i%>4g=0Ox-o@V_y!|33s77yrL8-~#Y)f!P0n znis$a{5K3-|G(k@~a2>(HyKC}f2s;SQuaqd2@o5QDdYWq@<3vu{jS4t} z`M-*fnKh%{*m0llGg*93Zrw2G5q$riC6>SWJs7Exba9hCS*ekl5A%`ZoO0Dl1S?dhP$=x6MQz`)0&`s1soH<2wbhVt0Fym#&W zfxVtvlcntZV-e$-<+lBThq1TXLQC??vF*xZw!~?&MR-HDh+jO+%(jmuoHbS@DGPK zW4$K$A2gS#xZ`1chIR9#b#h2eoJ9_>BXoLQ_ytXo#L{Q*3Ca0j5F~Y0GvSENdA#C* zr9r*+xpMfH)ZEx7rqq9HqPS$L^n#=nei&gB5W{@7H;?&*Qyi^LNZL`(9dNyU8Q3Et zfW6`0N2rajarsBmYv((GUo-9_^J-YB$qC>W(FxrT6tKAbA{+QTtV1EmYvmNXnBbQf z(1t(BptRCLLozEQu#T!e79@)JgDAMcBzPbWtlVDx3uiB~k)T)xtrU_D8zvANT3rEP z^eP-EN5d})MC9Qth^FkkvuV)qZ7YcAn$~XE zCP({mJV$VF39CmfR_W)IO~u)R1|svM3AnQTl$=>;lP2Dhc2laisi(e^Si@fjmhw($ zyI`0Fq^Bj50h#Ro{Ol2VVR$$_f|I$#dcY;W8>kU0Q29(#u}@uJn<876LAS$1)+FvN zISgQJNI zaUWow!9UlIlMR{UEdQK>{&6%fRpDm=nU=em;EA*erV7JLa*TjXzEyrc#JQOMsC0j7 z-h3^7eUU)BHnw_x7GY$!if1`$l(%jf-LNIvW0ohkdQsk^PEc<`o~LfP z*#0KI+(T0345?rlL%4;~$L7Q3uYmK)ml<_rH%=|S0zoMib5d>c8B6)uj^p=TxwZ!G zVjPF0D&#YU-=;nxH;XnP?pM%hPd%`c*c0=bP4l`C>(e9B8k@v(E=9-G4~3a$$x^ZmlgI4J4!5UXq0v6a1GS~%7>S^ zy@SHQ$Ccs0XN1oIfA{{*4VM?bY8s2aOwWyPhz><#9nfe8D`VJEu)5*P5v{JE%XyDo z*OOsFt4`hIzf+VE>^1XMAtrEd;wY&Z&qRb1ADE z0C|Q(1GFn@4;^J9AdORq<-YUs8J+78&UZT$w-TxS$-j{BWOv;j(k`j{8Js#+`1IU? z=xsWuuuvPDf^Jd2P1^5QxLI%BM#(GQ)}(^^1GD}>bp#+bx%!ydRqIQcR4QJpyBsax zATjOynfO~cp%%hbK7ZtJGd3zS;vyg-;joOb6qgKx9+3Z#v1fwxnT@U7zIa*?uF%+L zIn;4Ze5JwjfFpaC2>WU6xQSL_7@z+=A|Pv_YO(mp>=`a+KGRsJlc9PI7dNY8{gO!z ze&#Yb77x8_Ivh_TO?*>^bkMmFhqJedYn}luc#(8B*|86pKQVW^Q4yp3&eRX_wdWl4 zPMF{Bz~B-xidC4&S8sLw{&jh2Upyat9oWIEU*CQ#Oxt|=khP03;I_R}RUC{!9}hp~ zG}xs6SE6swSk^~(AsnWV@#Mq(oaAZWLjBM2O9#A->GwBmA=#g|1bNgxOrnGc7s_CU zO|Bky6Hgh~YnDYFxqF3@kz?>;Q&AjZWAVzDS~dgU%f}j_2u}Gr(|jd`Gn2ui-aaOF zCX~tos+q;{W=x>)=^O(IKSsGr_gJ;5?MunNa(XvOf|9ToJ0$MD6DikNl3uel%~y0( z{?+9X@~;UfR`dPheInQp&0SkWs0FAl8PbA;?rhD86}G#s<6XIu+>$r=$z3SXya}ss z707spTP%}>MIViMYfExvSyg=F0a9HoPZh=HCtqJShzI4dbAFCa=H)9e<6=cV-YKvq zls!$NNk?)}3?<9sVL>n1ljaij7$Z%vQx|Ph`f&|&@e<;*{)7%M(1i_6$MV11O*{K^ zs9xMPgnDB5GBu$Y|MQD7b6X85F%=K51P#P{_F8p@?=$ndQc2pU(>pH}-190%Zu;Tr zxD<-0pCRJ>1mPPb{eB}Es?9?D!)uwD0-#Qw!yglW;nh}K?FAc1cZM6cmdMq~;ytw8 z?5(7)?m9(!LP&{*OsAXheR?{pQu9rexcZLN>yoDs_%_PWJ%)bbulrLxU<~(Mf(d{L z(#!Et-2NjI*ppf^*=*XM$-7Iz2DaFD0#%5v>r+CEpX2)(Tk!_q)SJ?kFq2m3Y%bHh zdB_e20*nLv!!!dD3}|+rboK~z_WTnsMpRf!p7Z4ADr%>Dd66grT3~Y7dQ>)en#Bu0 z-pyH^RtlGEMs%geG(KR!3rfNox;wHK&8cT0yXF4Q)-)4rD$0e_P6B)i128717~O#S zpEBJ^D?MFnxRfLKyanQfNX~RC7Ed|ozAxCwxX;3;c99|HEAHKAD>RufCraUV<}2@5 zpYQ2XehRXi|C@Z!9MhUtBIy&kvyISte2D(>K7L# z)5~bTySZk;2lN=h{`3KE=;LEMh(PojIE7Tylt26TY+sQZ_P-=(29lY459O*BYP_CQ z;ZV-R?Oxl7{Uh+Atly@XUF))~BswC?e*SN(Yea5g zuU8-z&uQH$h3cvECyUhA9ijkzn+wlNzPSE=nWdr1&UE|vLSoYcm?yRRDZYdx7p22*`-eWByy#fv-UyqCwchT5ggZ64?0IE1Dux%1G?KF*aHtt@WbkB zDK&ayZym{m=R_&E6GhsO?t4Eu5f~T!b*sEVg@&FC1U5zs<2g0M;5vqxYqECp@mL?X z5D!v-;Fh7;koZp4{PueX_0FuC`h6+HQ{ve@hwM1Q4RK zX+Dxn)v%;1g;8OOej@(kLM=hlN;t)rN-UT?Ff-<84w9E5A2W|6*izG)0r%^u?yG&w z-MKOx>AfvVinUdwu2KG6?SN0vESROEsma|UHK|{~=0Nn6^GEtLqFGQW6d|>eiKZpX ziwzfjg_oMub2dwU00~P9y|@FBut;xwH9o|UTd=ViIy+KfUg1Cja<%|Sl31i-qJcab}l4N0nQ$b>Krx1C2Zo!8K zwQZ}++%jrBWTsPtQGaffAERczHs_zkiAsv+4p(L26%$c}POYOS)v&{!Vw5-I;Uwd#@|c58*^EU;e;c! z8+FG9?jP9QHGbkzGoEIEQtHFS%1r={szwrI;hRFmFGN9;?)m&yD(Hpf21#u1UpE&n~XVT9wXb({^C<`*S5vl}3P# z<>D;rTk9E+=q$39yOkBW8nty*A{rNdec7ialYg&hA4ng}S@to9cKnyaF*pBck-Yx` zFRD|DX6i~48?H?%Nk`cT33l1WK!PJJ9#gb@{#F4~Y9-t5Cvq+!MH^neeTf)r1XI=> zW4qi~j&9*(n;l=ZfSiyT1mn0<`N5b8Oe&ST=!>r;AZp|#A!<@x8D*2N#p$@_Hql`7 zGy&UDzts0h*$iN+$lk#i1KIQs`{1f9knsRQj(08d<3y(>J~$wW3HLh7%^ty9>zg+9 z-;LdZX|2*hDcyH+qy<;oJ*|X8O3rj6Mgax%m9A4T-bME%7BQ(tkG)}K*a2JcE!+}| zW!MhZ&K=DUa`NDVQ1RvtKf8{r+ySiM$6_sCM{8Z_})b zoZM$}$4_yz_JFuBakSIfzNL4V`@_BfkM9lD22EAVe4MDy!%O*G`Q+v*14D4VXc5`UQnt9XC6jztVmWxXuZ+3Yk8c8vLF$0XK?iscZf z%gX4QT{h`_MDZ^Ry(r^6)eS|zdk3njifji%RAUgkp`D30_F}~#7W^b7Q2zVxZP7BV zRyKl%tT03^7NCO>ix?47g;FIJT3GX2Et6ixQBV|M6>4+_{b5f_6;Zep%t<&+t1{GjanNWj0r#z}JM~M!H zYuZN`LtGvw!ibBrLgeZ1P{!Ruswl7txIDkeqD(La{BB2i*Y5puLWd*~68*qjgw%9_ znC>_OzD7*v_*ond4-r8Bp?XEtnyPyB<*TJZ8i-XUFRfPPxIc=>Wh}!weuzWWVYnSf zfncPa@c7nZ;6Tiq4mV2GQIVz(blP*4W{q+@k3*To7BM?oG79H zY9tEIX2ZDtpT|`pLAp<>b?s&jRB0^dP_-;=^r|#aBwhr5D6oPkOneZ4&Vr+U<4VOFXWQp~bHZ zedcH_nj%cN`jde@fOxB_?5W2FwtK-_&(u$&Q`)Zo=Zx4jR z#T~+D4o7tz!W$09vRRr^DR;F{&ah0+b}dHn`)4v&=sP8>3Js~yX>13D`A;2^45{#t zbu)~Xm{g`hki)1*O62QL%^z>j#QlB38@zqORZG)Nj?(idSi52xM7tzR3XPV$h|Qki zgY&rm{3i6nfB6=B@rNAAD@KW`EWX&Ji2#n%Tmzjc3~WA{jI1=p$+!W%w>K*w^u=Ov zu5YindVF+niRvLj}BpMhn`JT9i@vN@R@JA+q}N(5jHzoKPF`6&K@kX47x5}7sx z<)bb_G;_aNcV9S3F%uWnA6cC-6FDVn)`CHoa0x4g7`fZh|H0Z@M#b^1Y2Ua8cXxNX zae})$!QEW~f#B|z;KAM9-QC?C0t9z|`hU)>ch0+J<~(y|p8CM5u0?m%z3bX`U$uXQ zF2^(1cB_Zh@FeS@ZckIl1D52#R=AKq{!5|EtQ{;3z|d?$wVF@wqIyzuU z^R=aeM|nk3Dcm2AFP|Al1Y->>3BX{?8fG_9E`|Bmm(cMrFH^MCXCS0_=uO$-YJXjH zQ)@wgX{ApO(q6R=Pkrnw`=u41)?M~fE1s9E*i<9`+icQ!gc^oJuViB4@#f-VfQ80m zW{Gb`zzTdLZXGQ&gZVf5>v6Ffy96eNeM9UjmFN`-2?KL+M3R)6ia*BYsccDjm-GyIXn6fxnlph*mzH>3(*Ie9$3^wd7zkhP85O*mc*%!3}CDJ?Wv!_~d^h)~B&|Jt#D($F1 zrODKLG=G_DT6wx0!^5_pFR9O1MeLSL;b>|m`F+X1M0y)%Bkg&4yw-d#n4$*uz3Q81 zJH~o5qE**43mlUkxN15HUu{CADU$o7M3-eqD@}zk z{&O53-$JzP7$=7l_7Y1zs;7AqADy@EyLO83%f#~UQ{ zBmRMV=)w!0CSJd8w*zBeR=RyDY?PpshfuJtypJ=#$l3W?|H(Z8;3QUI$@VtBxUo+L^a3*pxXj8e82)N>0 zinFs^T=nSBaJd$R9w$)5I)5oP9g$Xev)=Nl-O}9@D2R&cA3|>E49Qw48#>syntk!0 zd~qdpTX0D2@Ty-TtJMSFv~!vaA2%=WXznRAT|2+~UDr#qF-_z{u%|Bw zD^@a@nGWBoKOw2Ch2`{fU08f3jB zch8%-&v|o&hh$gjB9HE`QhQBq%ikwgh{BMjYWB+B?kbzUmwq^P6}+xaY;$ErM$PrT z6*Le$LvB*_-COTP3w|)HK;P!XI)G&`{4v7aE56>w6qE8uX-1fN)i8z!H;Ts>y=Fb$ zo8I+#=T2{V385Qkih(!o)Ssidx@yVN;t)Jfsj?^CtdHMW(Gkkmt-5-esX#e85w-~E z-LSq4lYGk8j;-ngW=}j^lD=v9TlQeZ#3Yxb4LY}6?Xe%xXVQ2w{rT$ndi(nbNc^Vz z-a-@VE@feiCjsx8e zx&+nXOCx(zcVA4C-QSz9p}N+aC+L=Y>=Yaf@&%uoajbH-xM_VY9uC`O<@wt8MTmjz z9JhAQiO9N!ty+EfZQQeutJi!S3Pv1(Yt2=n+Z@Xbg7u|V(v{IB+FQ?9R9N=hr^6e@@O=y3(yC6DQ78}EzQ?)q#8U(`S{+=x*Z0*RqG-P8yNAzz&bV$d zJb&ZI_&z_neri;BqcyOh9ik{mdh3(Bt&>IO$k2oJF2+`Qykt`Tj`Qbq@oYI&X6d3L zT{u}t`Z}*sSS!Lu-#-=o+#IxuZrki9;hA@~17TI|yW`pq;9!}v;{X!cKy)}e68(q0 zLRK`bQ!GYXA1FoDX#T5hY~{v06lKD}KuJK5FZ}-ql*Gx-3Nj7QcXXfW7pE-4Zi{iw~m} zO%RhtOT$E?7d4GBhtHvvrk@C8BU28WW;9hWXV1L2aa!#2`7^}x@C)T()BfN}U+|XO zJ*#8ud&iPz#gZpzCkr9ibR>aFHi=QI_P@QOk#+xa(RK9kdNU>X@wgZJd3VI2>n-Wy zCN}pBP4REP_GbwM#i+NMYxlS@pGa%4S*jF?FVU#d|Lk$Sp9-+3)M=^=YIe)o>8sFb zVg{Z1_3_FH^kDn@YOUE=6ds^ZKE2WF={n2zy~T)M`mc*aU#l^Jbvs$AGWd9Z^L^Zb zm27m_5^&rW+7G9{Xx@vNNax~;q7i(5+}j<93`HhdEmbe$zFyEYkpIPfwfX%*uf}*F zTR2doQdjzvGYSBT`Q-vHVWrt=Pf_qqEz+iB6m<6INsyQJr*=$iu^+xztx z3nmeQUaPBBA1bDLcvF#Fs`I+j&_I0CmQS0*Rwqm6BY9P2+c|Ih#2U=yKBz=|?-n$_ z#-RwlPFFBw+`qqE+?=ggWWDW1vfy`@c>Bs zjuNa~f=(y+c00E_7k9o8DgFC955R!2#^q>|KJI;L4RmoXXAMiUh7H6!ncvS_Ua!ZL zsWb>VtmlJq^uN%u4;CcrKMzyHW=}l3-1{zR zyYY3=!iDAvM*vyKc7+= zbX@i#()@Dl^%v*6ofg$;viH87k>s+P?H4v!m^k(B6Dku8MOInw`2IkTZ*sZaBjUOr zFDU@JA8ma|bd@IGMGisB>p$+63=H1Yo9s6zlRvuGYt3ZM4yFd93HA_nOPRSgB;_YY zDFzZb5TlAvGMxtTwtQaf-sS8LKo@Vj)@WCy-vN?P#s__#%kmKS9 z4$0_mL-d7T6c3q1;B|y1Yn&uB4jzNr^<*9%Lm{1$NpS#AJuN22_kP8q>uyo!oe*F? z?s1f%$#?MTFi`=GP$}@Z9e7d%Z~k(B<{qk9rcsg5vr=cVBr@B!-?(P4x_z=+-G@x! zJvO!TN>}vDc~+XH$2xs9|dFibfR}>UR9BZ%~;HKMw5LEj~_$1 z)S%9sOq^RurRZb2+B=z9Z~ws$61n+zZlpE8#rx|ME7MRHw=ei~9*UzsmW?DM#@ zYSl9GgQm%PAwq*^KTed`^LNgl<{4744Ei09o5LCNqz^5Ji82`*5;Z*9f?MX!$K4Q* zTG1@kYS7)U=&Z%|5biuFEj!r&M~X}l+zq41^f*k_){S{!t!PFgNG!ficj#R4{dj9A zjXKg>$dg2YEyz~byr8&WHi?~?9ZO;=yY>-x6^&};Yc;ZbfhF;(=GD}!jTL;y)u}Y- z>O@da_{?jrU(Bf2idh-MbC{yd+rdJxB4O|S@%9kBTS(RNc;0qF@6WHUJ+$$4Nl$Hb zu|fxNK!*RcypFkfJ3wwSXLPwtEHOf@$$6yFZteO0EOLV0?oe&(HXDZNtxz#bUnCKe zCG5FLDStxx2ep%&)lkz3aEu}&uzsf}T1xe;!!7$c)O?i0`xcD~zRv;_-dWQYu`&}T zMIn;iYW?w?qTmGf03r@^evce00hz*DwUOuub|rOS+P z|6|`D>NyB2nkz~ON#E1C9NbthTB@b6AKcE@Qw0@tG;(FAOsveAl8F4V!z~xfwf=nT zug>zi9s!B{E#1iPyH2C+NKl+huc*t8PNqut2wHMz(`3)o>upih`05dij{=SWTs9sW zmLsj@6_%pdl%{hsl~k3OSRf42C<+ADFRrRj#72kAU7hGZY{`-eHf6NJqW**(Q}7~M zFeFQoc)Je85aH=9h1EMw!P%dz@i(<8Q2U3^}#fZC7 zCA(g1epXCm^%$W_zCGV~shbjDz}y44{#I&@>FrN~(PV$T2en8E8s0Mdn&n?=40|A; zUqDTkwPL)W)vY$El9F9i{=910Z8+8sw!M-8%jHE$xNI85VA{gRDlcDn5X#FbE)2HLm z@pZM{s)pmNxvWqwb(h;1=nTbKj-WoucqA%>-*34eO>L&)izWYRF`k0f^n?95$-xiG zL4)nQSgOEbCRdzUjr5grz^qQGob)XXfM1WM8=xK(zjn@NK92hX3n4O+{&%VA`m)vn zRHK9(XwLCxQNY zP%e_lJ%PXT%ViQ?9CK2K!9NLY7fx|7mSl=6PIRNYuLIP?g|d_NA&5UAbg&;Vv>OH3 zp8#YqQ`;gB4eN2a!L{oEX_jB$bK`x}cUM!NM*ln=WlfQ+s4+5VR*rWb)cMIuj;K?q zNpH`jE>jO1mGm!$5HLp3tQG?`eP2YjC?+ zN_>VPpuH+2XMMN2z|H#4gP%{vO zkcPuGjHOp-N1$N?%l0dd(ZQedb|fV-Ht1Za{uR>a;9hq^05{XmqHOri5qD^TxE_;p^{k5V8ePxU92jK)v`oH*WWJ#pYz zSR+GqHkEx*hFeqH z)RXxV43hG>JX`R@qv0Vcur1JVNGTz}WX8fV>I7ZSFT@o!q8{u@3|WK>g5$7e)1XPo z@3ZGEDv1k}xm|G9)1Q|{LQN{QlJeCihbrFzH2>acN+|8(t3ritnC0@pbH*7NIcf6G zh8U)gu?5~OiAq%ZSo-TJ72Bp;Sx;x9=#Vf}Ejn94G+AjttK6?4{H!dv$g_S72Jl1> z{V<~>#(VPg;!u6TyC@lA*PuQqnAekACgGeO6Nhnm!C62bLA;B0#%i^7Aig;@ZLYF; z7s1`hIcFH%Zv!;y_=aX^c$GEjGy`bUqBqp;e z_v>Y!{1#m4f;=>*TkHWQL8u`2ioA6Hr{$1|xI$|$R7d}>$m7%Ku0$zT+4b^;a-~WI zXi~fWkg%l!T5)FFTvD{^hLEyiX!1Rba*~CvE^(|l-`x1PAT&pNZZi%UtaU$Kser~o zQSfowurR3S06{Vmb@DGM{l9p><6wluJw+yCha0v&3H=)Xrq3i!1y)fLb2%rVB#O6_8J=I*z8Wf2NQIW(7mS(`H7qgmUU*ro0RqJw zEM=MpoAElpcE{jVxJii;fn1&vvhO@nvGzzS*!Yv48CJG|OH{E^sZ3O()naacK>XafNm)Og`wJZEbN`Fnb>wvrO_(u5Of zr?YB)5WL5M+$OIulvnpz1kOf@JU_--9WBn2uLdwGC-u6*r>=Y~8#7_{Oin`_Jo*nQ4!~-f6HFt|BLa?7+2?( zy!~zn5L0(~%(j-5Sd;kh zYLM8CzqZ7%lwy5w4L@j|2~pz2bL%WV_OG}WMi&Z}o9*Z(wqSII9GS~SVX5ROdaQW4 zR6{CDbv7T+#4YAF*Fx>r0H%^UFHdp8$5qkqa4#P9-tNq2s+j8_JHMz$3_(=;9e6!f z^M>WtuQqmS_2MjfnA6b8OCs6FIuLx&nI&M(bM*D z<FvYZ3M^``%brR*A5{exNI3Y8AX%{&D7>gcz|}nl1L-|=2z3i2 zytvFY-s0<-2@75Wr~#_{pg6L{a7n#l%y>? z>2wUHxQiRsJJKB~`}F>0Rh_L$UgT34YJRLw(tIgeEQVUzc^7MQfE8RArpi{;|E3Z22OudjMv(m_>7Ddvv|c`N`s zH07b`vq3I6=^FeHyc$OB0uokyDzCrH4*%EqEd)*$7fsz~M)s!;OR;3r5suQIi_1}W zMfafu;S2*1;-jI1j$Y5V$J&|8`0MZ<`N9F6*{T*Tw3r_HP%`)QV zXreesBY;pXzDx^cT^+9|(Q7ctW(qKXbY*^LX#V5MTXS0_Gd1I!BY1Lwn(LLXe~Jdn>W3h{RAV9uf2}2CNZcfq%8PCx+Ja3Z6)_dk8x#{r z==UUK%?n-<@N1Zs?If5N;%k$}&}1ChdM|IooTU zpUPb#^kUeVs|={TmS0t`PswPZmtG0-7fNc~%sc2pp4m06h;PZwb}8O5NlSk|f9qFd zM8XG3IqoMPiBV*Ms|R?Y%H?q4uqLtK1$oi2{*1c(#a%DuF3H;MQ^+X4&vosTM&%cR zJ7!%gPpgaj0v0jM;}5h}4Ti|Uz5hMax4I6Cmv7fE!{MJg2c?VcH)KgpD5xFfUQgQZ z1ksrQ1Grj-a2B9qjJ-LWKs?#2wmk_H5F(<2bS7ktM5B=*G9+lLXS$Ak&z zB8RFlwU}ZG>-eTDiGZs4S}A6GMMGU;;w-P!5r?F!J{&PW-;8hLy8}I+AlqEp+Cy*z z8K@t3hSmQA;#63eQy9KszbY@ZI-f&x)dX{G`8`Fs4j^snYdl?D}e9Uhz?$UL=GBbi>cQ> z9!)o-JTn|JLf$-VFuDHGDI-;Ek$QBtzf7ZoB)2;Db^}1Y8>6oA(=To(@{S6LY7EY> zEWJn7hzT6D0XN`h7GZOo^bT%+Vz&SZLwyjN2=a=p850+L^{z+6DVV%2Br$s?Pm%&b z^r>8j4$2*E6Ev!)A1R*xWF-=c0?0oWyM#osm;XFygC2v}443uX#N+=6d|a+EX~{6t z3b3{fn+?(*4_-6QHP|p9I5o9C6A3HYVu86+$&X#C2! z8e^&$S{I0$y-$->?(0jQl%NT*d$51Kwbw?U z*|(@liIDnzJAW(DCgG8udbh4E`QtY6W|VVFB03LO_SJRz8p9+-yB%AdR>iDCa1kDm zQA`@1H0{OOf&fOv_gw*bl`&N_2_N~CsMVK$Ik(d?Jz3wAE&E$={%lbfcji+>~%ZRPiCri=;l4D7A{!U3aQg zC~aC_y`3$5-4?56viv6%bgrhdYu18bDwPDvn`0+VNy_!o7)(mME@QEi)gpK;H5USz)bc zLMFnkp|XZ`iG^{WipUq4fV2GEX6?j$MI37>Bl^dgK;+ia7ni$}g~(q?Rj<7~TIT&B z_PL31jwu$P>G>Wg^#V$j&X=e%boe(u;F7JjxnJ0Cv}(U+MOH z@ikD|_5N(_VctRKUroaQUo-aiK-UPwL@$Uu)JJ0_+g9Eg@ z?EhzQWIV-csHY8mB*`2mOH-#B@TSVmN-L&YhTP)vTH9I>1~y7rTIyPPkuPw`+T(SdFs7IFZ+3`y=49SdB+j| z5zmxIQk{Ad73IISy~3@3H44vTTEr@wOHS^HZAGMxJQhFtbyw951G=cg<3K$H?dnO;YZsS6vAzs?>Q9}XUjEvZEj}* zE`!S)wNQ0Q>#*hHVt&Z-<=ab!yM@OR#nt}Ek7zpM4{+x_u0K6!HSXr)zY5$r?KWrQM3B(y z(z>YF@!zMVK4va_d=udgLp^tf28i4@Za}y9Ze|VR>ZJCd^tfzHKeA)4X$!2wWk-Z5 z#v8u#cJ<6+9f@<3pXreF3XS>c#v#xPGNo1HeTH&xbI$h z`tG`?bGk`4)pa^AmGSND4l8dcs%b6zOKu@HlfLuPhA1)K({kq2Lu>4KDj9vgn;4R= z2}hWhO&-m#hZkj7Ka^qMdR+fkb58@8x7vd$7x2&K+&&((wBP~U&C$xt?f0`|RnOK? zbF2>U0kX-=iThnRN6o$P;$HI27$i!GUA6j z@Z!&(P%&w)yB2kPNJuoVqb=VTk`(v>)fDvopyVON6kV1Xc(QDCyLR`56E34p?IF)N z#CgMWewHOWSy{x^_@5Pt;1Rg3BGL0Gzx)q%LuC8Wy+w#>h@3|*x&FlS0Jgkbq9mO~ zE`y)fopmhe!kv7UDmPq#pN3#bd|ba?*pLkpw@i*>Y2Np;?HRw^D~{xtPJbFojLWIp zzk?$EUM-^DCm?hAm;?EiY=ua?A!yrduS_w_LuWo`(k*ne8iGVXpy2yC<>?k+&TN9M z#JqmOVG~cqtXp@^3s{JhL1mkqYxw@Q6YERhb#bpKm-M&+Z>Ko;})ZWq<;vl%$8a)~Fg5zRtNFg&Q-{X|qoY0bz z#p2jI<4?tcI;P~l-&J!knF*-!j=P*M!SD3;-J@1Rn!s^2&ku${!Lz30e$Af_Yxt3l z3T_0AWHd)!Or-V;pX>KHKl?QsaZBK39tnvgLPULgcBK-raQl2W{#hn< zA-y%#+o*Q#=v3Kwfv5R7%c4nij6MF%u{*BUs!B79vy#(sp_DYRB8Ea-XnwpUif6nW zgAe~j{og?oIs)C8gTGP}%QSF8%9lDDVt#%PMP^_*2tNjRW-mqG^SS-0eM8U|@=Dlx zd#Bg2ko|3OWthYVXcld))K?1#b0Xdf{|T~2WtU^_?cIm@5UF^a$+AZu3(-CsDm${}>Qi<7L4 z(2uiP@A;^j3N7QgT3Sl_c>z`HI%bVFcquL2y>~6KY_U0fiscKgi@Bw%rU|N-kpV9u zK~zb+LOi}#5q}L~U1kR0G<7AoKj6zX#Lsjhvf_Pc4EjINN#aYI51krDhnJxe0)X%K z)nd(hotw?w9=h`OB%Y7T8!|0gK`{WAn~FEr&(^og$BF`p2OC9R2{uzl-wAfd=hqP$ z;OS$xUqQWzGseT8C;r3n;Zp&xM+VuMfHN+Qaz>h}DW;~>hc5wX&VH!XS{J<#>+VYBE z9qGbtYIRd`NG6o@ADo5qdp-Dpym|1PM6jz$8n-g-0?}wY?O1XwrLaVVw3cmU9< zVchcHb2sMnI}WdDr(+H?t}mZ{-50=l(c?pCEYI5WLPS6$iW5zQWD0m6Ue#cbqIkk} ziq*j#_Ssan`4jR+DG~(OtqD9`5WIA&SA^Dxcq7z}Izy}GuqCNzbyA^`{o=MN#XJPL z8Z+bhaYwG~qa1>C_{S=UyLk+u*@jTk=*E*#5`pqrqAjIX8IdWG0o$NAb=bt!zRgY1 zV!n^1{oFig!Hw69kZd@!D?xWffFky}CgAcxUtItapds1f zm730Hz)Y}-^Hv|TX2PRVoKz%g+-U8~gtm$a#BZIl*ZuCn@@@J z0Egp*fPNagoxo@eNVU^o6gYxMuAva${2Z{tp367b_O1eGRyvCd1Tg9s< zntA{~C-L}Ae~+jZ#Sp)R=(I8ce@>C3A!_c8KbGno(;^%@Hcu#0;P!|$Q|jf&)rUc> z1ez1H1Xv9hMfsH96@(fQDLy4>)E&|~nd?>&AjPJ%xX7`=-*%jnlKhqje&)2iNkUYDL4(Lx4(dCBj~FOdohkp$L-D61Z$ z^FYZlgdr3v=@CfApO(yJ_G8m}Z@xlTBf@?%650fgnfwX%q3^wWhlHg~&Ya}XimzC# z8-esVf5K^&;P3x|D=jjf8c#>*f=P#IJvwq&_nVLNHJErWE!Rm?U+*3tvs5=&us!gb zPNWh?gs8x=ds=f2HYDsuNwwEHC{aWiG-zlFiG*xPALxPx zO93ECN9+=5p9IA*bUtc`DOL@S>tnTPtZQPKD}62qzIScr+!;0F2IPGQJEjE=9~y7H z|DNo8X1o57pr99vEKri2@Lwwfgqp!Rv6fV zEvx#reHc8x$A7PU^6dQnMCxK&fOnwc8G1vpzzq;y$T+AW(}p0*t!Q{k**q~M*H)X2 z)QyK{7fmeT6L@-Uk5PgCtq(i+lzjb5icn9Gv1p6AsM{=SZ1jf-QX1cbVFsHz)(BkxJ91eYnXr$B| z^1af$koqp=z~D48o1BPZ9aX+N>#yccw;QA(T@ z>X@Q3KGf#B>=-2fOUz>uG*M6*9QGbdLQyWdL?siBT}j|+7g(r#rL0G~aG=cEo`Na= zH2b4ae_r0T!U@aD%JYW^k02X$x8o+3VU0Kew&Q|h#QK*n6Q#=mIxYK&)6^IuwZc}#qY)vM$^;} zV976b+NiSX)iM^;F!;!dQJz-(KRQVr)+POKxQQ3|0BqS%x+D2;yoKqz%vKt7Yzq|y zkE{LxS`LDmN3TzY*;7~eV(oXG%=>d|fYbu-1TAhZ2TVfH~B#Y02gU&B|L84Uu+ss4~K z({$tM4F1VA1V13xI3E8AR)i=ZtCTZGf`)$LYJL*R7%a~>8(@ymvJFc!1OG*TLcmg; zz*F>MI?bA0EP$8388SsY@(#!+A!?I6QM%xu{oYs)dAgS-!bFJAZ%P|b|2R-!-X1`S z7cdzm!l}8|rX**5Bj0?mUdk}yul%UEY@Cri*qf{oB}5X0b#ut_`0bR_h38UF?~a-9 z^K-Q24GLo^#;UBbZm`0LN0i7j+A%gCNF>u~x!F|>FU~LbKUyjx&`!tIMO9lOsAzGi z83)YqOmfc#5 zjbaet7mr_uLm5d_*-uM?R|vT5Ch6&0S^d%H1hqDcfJWNfx|4om`OMLvg_~1{>PCRq~)B+;;X~ zU%en(ChS9MiMX~_r-qZjNXNg77moJwcyI%YF_ACI7AGe7MsyeFklcRS#Zk5)@d8wG z&e67*m0I{4;^g}fBeK~nG&t}F8fQtDaiM1%WLctm8JiM)E(+69zSnJ>H*(_yjW0hZ zWI4gcTL;v{D(&`@z(J8KY+UJOKL|DgXA9{@^7 zAb~q5vC5=STh6v938xk?587;U1Br zfQ}P4EYUUjyB=qW#{d$r{=&d27XwS1oE4e_eNR-w3V8v)h7z_P>n7B^vt+8GHiTK! z7=MJ9G{96FVy6a=K{s+a%2Al{s*h}>9OdovLn>d>@|Pd>)kIwA0(t7T5S4x)ae(GS z5rRD{M<+8oKvlV~B!WJ<$3vkBIiK^95W?Bd8|O7due4oHJX^Gn{Km+l^=6-4CFhY1O;xfUFgq^{N{9*?jL|Lixpr$Dd{f~WpoBTI#pqT)9Uw!_hOxa-SS*--+X$|O zAo|e?g@ca6xILNCCU;?$sDCj;ao~5RZ5F*HMJVZBh!s%q@=Fq{IfJb+B3pTSL}6G$ z!)l#M)*yuDTS$FmG+GW52Xe`k=zKP3Kb^G(H{=JrN+_h0qC5TuF6Lu)1zMY zJAhkDiu8P06`5op$80BKV{IGWLu^ELF`5-S-j&Ing2D8zwTq$gq#V*^O9Tw6W*}xS zmHZq-T4c>n#3u*tR{2D%%%J*+iha7AFQ|HAB5cSiVu6Q%(h9a7xP*M2yR^X zZ_yVC0Nc^aXj0wRgH__NDeeG?m(OK9k}15PS4C1i`ks z`C;Qz8G?$u>V0!R(wMBgk^F8JW|Q@uHjas&Y#NWhDB-Yn;ctnuk&=@shf8N8h@Xk# zp6U0(PV8$ZTa8La7vn>Z#P==!o&jZd@~i`%VJ#F1vzsD#Isu{sCPmhd*r?yFVGvb| zTiPc@0#-Z0xG$F2gztE&sD_+h4w75%TN#A-X5AdoNw72{I2ZCi83~kUKZ_x~V&U;t zg}Y;2#Z@{EEr_&X(*=3lXbVGeAM}UA#G z#&U)T*7-pw_AaDq(M^wqaW;}B?O{<$(H63Sgwd8{Dsm7r zw%S!?Pvz(Wq?)GoIjNq6i5=}sHsjx}d#DqzkQ+l{0-s@#n?oTVCqI2&HOR)Q_tO$+ zljMcTP$BJ(p5aC0mvd(x7mvH~6bl14Ip(GulyfxkzcA{2%MlITP>{c~b6eJDz6`?o zGY{PZrS6h#A2!r9TYUCGHjL-d*NE*3an2n69+sr2k|C6e`5R{&a(z02eGoBp6Qptew8AQrNS(C`dKEP%#+wb81P`_=jVlfCQg+&? z@@r3Os(?vh7a?uVY4#k?pkhiC#Ig6pot~%M&=dr%-1@Q48!S56Gc5L<&~K#%kkMe& z42#@a*XRrUknP=q*+K+8A)`7WZ3+Jw!5((pIg)k16ILP?ugc7XQp2}jQbc4ar=UO_ zBe@Z2^s_GW*b?|EOh?dp4@fED`Zh0IpSz?;3qGHLrtzpv=O7y(w#i=BK5BY#@$i|c z=EpR;CnEyUCCEPD#J*ZRWw7ScA69^l_8 zc>u2ebqN2?o#bK#1@m|AB#@o?FBaur+)4KTO(2$5c1HFtmS34fER9@U|BHBl{}2!N z|2iIa;9ro(zg_$P7Y`7?`oE3D+RVkp%EZXg!OGs%%HHC?2!-PxLgD#ehr$NrVFuAD zK{tb)m4}Cu6vz!y73csL2v^AgI`A)u>z}Ydjm*s1c>t#705)?D9%FNJBOWtz&|jbl z7bhox-GrMB#QQYo;WRSk1_3@f%>f+7p#Nv*Vm0F8HfA#c{3k3>9uNhT4FqNcfj$B3 zf1`n#{)K7eH0R<5eFA`Zpr-7g-vO|KxS=4NC_9J~3N!|BLfHTu96$g!5Wry$;)nio zGAPIa{;Nhi7+WI%{{pH0we#;-)PI=_%Es|m$pSf8|5gLoNdcTZe>=bls?MM)!Tv84 z^?#cTYQ)aXWy%Hs^00yspxnl0rW~AHY^FdqHWN-xP9U4Hx#?e&Qg-A2LTgEXXj2K~F%fNX%j$NM)b=wCcqkm>+`$AbPVW$;hr#$#q`%+6!X$z{d^Fy;W78-W0+ z0Ap?rBX$#Z9&QshR#N~F0N?-tX1RIT0U&(=xp~Y1CLq0=|Mji^vD`oe*1w649fYCf zp8veUu>Au}82Aq;-G4@x<9KVK)eb!|D>n?w4RrwmJy0n_uVtwUs- zpNB3k-fs*FyF{FLTpq*O(C{a>q&BiBm+X^Nj9|TbyFT8_=zJcBNG=3kWfXj0v-ryG zq0^!27ENG%{c10RN8oMn1id@b_p&?EqSBE7aw*|ca=cXSu5g&si3wo)x`;>H?j{yA z?X%9$&aj3o&9$roU<_2L8cNvjH>=XILgwXwWxrWgw;UQ1bKF1|=6SAhdW%nz)lh`a zDaW=QapOYDNRVo}JKh+ksV28nRf2<-RvLa#u zqa+6u@#)7d%ha%QYLK(}>Wz_ljn}{s-?Tzm387s>8~TR9X!jhS@_V7cjNlM&62SUe zJd036aJRYbh^mdh`bn6|^JY(7P8)ZZf`91-j$;}5gDI^+`=jN8~T%a?pKQ!gBQ#e?nQ0gUNLxNGm8=@bGhBlqTR$DaQR#>B%X;4>8?D`r&gn z9S_3yp}$b&CVl^DP`>>`%C}<-8WM_5-~fh#F_+QR&X|X^T?>X77B5G8D8Y#x?J$^P zP68L*2k%?AA{4$|VU$D=*#@-mw;<`%Ff2F>qndM(F1SECR2hm-f;61rjA;I8ZFDs; zI);lWi6cp)o2hDiwG<*w&Ca8?1V*IoK|K_5vRVBU5h6kOIM8$vazQQKW|59mDYCt( z{j4H6Pjq8e0Up1iKW&M`f?mAb#(3Uhs)5)tP%dP-rM0lHY@Ybj;zuG*hP#Ffo@gZJ zPpBW#4z1`%T7Bjxrt-f^noDg(&dOH-I;I$LD>MX={pRGWX#?e^A(ZztF7}-2KL^6q z$7Y587+@0^Gn53OwWS5K5o|NoM<%{IKhRXK)Tt&Z)&-LFb@`dq-!Y;75s<>LK&~hh zkpx3}ci6tzx`29Mbw|dBWaVYB?;XaB5~4s(`TE-oEF!B1u}2u5mC!E%-qp)hwA&UY z8%CG0TM3ml0J`PAo>Hi0A7LO@CR}OWQ3c)qi!->f@Pu)Cj9ZKlV>6h=P)|3Q#zDY> z-#$LvT*SiXbR}FBE*NgUdw@UM@MlrF?jk1#>oY z*kYti-xPRz$k#&VLFm^Ru6%7x5&KZ=ZbKMn>d-87YK8o&hv=V;bfZ!E<%8k z1!_#zZf~YYeBK_bNQ}I?Uav2&uFm@~NQwWPSm=6&dP4|7Xj|d_vA`v7{;Zp4S$BNn zD=)ZpYEU@kJE(130)3-49jq7O1(Ze0jQG7m-?O3JTdfojXaZFyJVoBiWMIuK3ZbTT zgjcR)3{Sn**iy6U=M+H|^!wBF7uV|~31Y7zg5RbYrY8mV&+jGn+SPdNRO@YFR6lj8 z4?mZGD&UJkuRLYjslK@&5Up*s$JMnR5|6Yx$9;4oLVyEDJ_x{nsR`}i|8#NszU|Hm zM~_0sA#b7bP|Jq5;%0BiW9SI&ktr+BOg?c#lJ2k$Sy6zvo~<1JP%au$A1Tf*4!0rCa8vp*0PShGFE#B5T7> znIu>_t^Uu9NUIIPRa8|2U6E!rfo8~&6tSMwv8U=^(dAVB4{PTfV{5av`L@m7wr$(y zZriqP+qP}nwrzX2c60juzM14qCTEh#nPk`ng2@YWnII}%a*xf{l;2Lgndj7%lo~xx*J(+d zP^-0X7X(wQNI`q7WTRDB*eqW-7|#wI8D|!%_z+w%52>nLTU`4=N>H0%3NGKe4fI&6 zDTv~>+*t`p!LC*t1Ivjwl@-9@(o}oxB0zNq1}Zyr*=!%e$>ZM7uQ=B}b-VLq1VPQb zktcbhz>z-A#qlFoXLRRDtI4J_>URp>$ANjTciVAoXgA zd)wpl_4)mYT<7%-y_QwZ$XOv2rdL-mKfn@{WfGiu+_+dbGe9BR;LCE3&JOdDEHXfUOWcfV1-Vl+-QPk0i9X!{bbHz60f6`h(Fr2yE%44xnw)FukB zw)Dny3w0J0GqNa5u|pngy|hDBE0KP8uXRtHk->N)Tp`r1>?tIKwfbF)qWg!J$)L7+ zxX49_$jw{2W4y18)?(kj9}P4A!$wnet-?evM+$}21>#5p#QK6dmr^Cah z4&`%V#PqO>_-+0ZpXdEjMK&{%bGa?s#`5&+u10{P<|}47j?@q|;ZbE3s=dFa00Bqk zZO0WAxkTrxZo=L=UNDdZ_H%}{*?wNdPTyTheDg{%(Ac$_36$s>5Nor;<}y(6cHeuu zxKzOZNa^tq+sf2N@R9?rg|AbtA_s@lLjEA07xvRgY@$GSN84`U~yDG z?`;iNJjh_oll|4Zl9WezM3S|hOEa>&=-n^M)Co zHocZr6TD5Pttad!=-_XkPaAwa(?tDMljAxD<(e_z8{j!`X%8Vh``tOQ*)1e!CKy^+ zU|$DA&d~#kPf(rXm}f&7p_5;9v+uBY+llTc#=FJ-72ST8BEw%F@5Y|*ROtE_V^GH% zX&xJ{<&@d`HJVkLozy;rHk;ay(MywRxGyUSSkRl-)roQnz=zABDZw^{e9xnvKK0&< z*T>69xpf{D>N=I{3yIt_%WLN%;R&R22ThFdHAG;UT=SwUXIqZDy zg7m+or&Np_@~mu*O|s>I8q6K(vvi6IAXxB<_ggr;4KuD$KlZd0YK~lqOx&K*ueR%s zE1n`F@%Jk-q7=wz6A&c148tJlXrUN9ZK879!tN=f2})4d3mCg`>E5f47Ds!7pMC#4 z%v1HaB9NY;k*HT{zDf4F%6B*s&a$fvlIWv(Z60^xjmYs}8R8M#G+4v0s|MR5KJ_lPIZuH%uESZ-cmr%Xh zn&~=b0=|pkyUH%wafMA$7`o0-#0^H)YyrNlWG6tu+K>UE&FyiGvIJS9c~n6(^yrte zk0MS=0ek8Xc~zO>s2-!Q%ci;VSjktBDHCP1aBJT`mJj=zbKJq2dk%PF{t-p=eu#)) z2?d*N3ncQCfqAZKQC)s-#j>Rj*q@*>r5qEw!p7TZ=mC$V^n&hL*WY#;ly`s=Rwykf zht-m^|4ONz0;8%Wk1q&Kh=m>T!ss}pmWj^0KLm(geRFB$66CpCv|zi>fK6HSapubg z!g3SSaNYvSS*S|t0noMAd;3W34sWh(2zFAa<6D@Aq~fMud9$w}p=quclT$AYzEV6Si{OlWOS>V1&yS>u`jgwEr5oAO3fjee?j z<-kSiiLi!F_f!6IYpen0y}`y^*D)*JuJxF-yay`3o#jO<>%1dOmG?vwuI&4C0CwK( ziM_|%&zLl*>Z}YBQ#!&eI0{dfg{icxPV++VWiPKvh#^YBph_5m zWcll=&CN9?3k~cxD~m<^TyfQOBk!?6+c0eyXa(2Nd+4IVqyqtor(%iXGB69pE-QNK-&{OCh$a6r$o>l$Q-y8OBsi(UBk6p`=x83dEA{jnj@S^~rLyuI#E;w&(FeMI054wjZ9_%!PQ!hI8CC(LzMYd+n(p zsQlb#0UAOU(0Gqfc)sep}7gk0FRPr zK%o40?%30bJpj>k=(S0fhTK81Lu37BYF(T1e*5nrTO;Vb;?rKQU-17~a>OJ6h(AKk z4jTrbc=?G@?-%pbuE?@8yboRr$^;EwAsl!1xAG$xM=nQe06;P4Rd@h|G1o!Z;QS~< z%A}?Sl&4WMN(^=e0E^xX7zF?uoGX7Gh(=>$2MRT*0Zog-&iIR^rx6TpTTTpsVrNWB zNspEdsTqMbFeeLouN6G^b{Fv|e2k*cOYiNL7YF+H<^FJ$i}G1Yq5?Mst(rGRWHn97&KAX(o$cmb_CgCbnd^>3`L}+# zNgH1>Um@Qs4?AH@niEf?2_hL@F(gTI(bEQ;TkMwJN3G}ZERs}g5g)!5?~kGlXABG4 zl&(u%$Sp4Tzmldsa#>B?b)lL)Cid!>kq6|buWD~M?Sn$2*rV`))WD8Tm){it7t=Xl zXQAym1n`RlDIVLNZ*&+jyqFhjIZtQyi_n8dsX_BKrz57a1e>lDS7%2piP@U=yY!7& z9TiG}bi5W0%o84OXjhIn=RP{o9uLxk?1PKmju>MpV)k&RS3F(|r$hM>{)LOfPa*L1 z9cS+yaLcU*9JJjd>}W?!?qs3c$4P?1IaZgm+phzou}8nU6_LhT-)~YOM-w9OEsioaGDAJVmRHz6DfLsHm4LcYpUfU~FSIUG0`&)6#~>_Q$ZS z%XIo2Za##TxZw3mv)#sn$-={gHf!W^?O$ zyEr};(6{{EsRbTnbu5<@Cm_0)}!O7QC&9J8EX z`~TEpng4U)kBQ+YBJW4TVf~+S1wIqoe~UV7{~h=C|FFC`I8JLWJg8vMdO*o8=jeZ12dS(^|1||bGeG__m6E*`DRu&dx zLt}O#Lt7)`e^Y1wmlQXaf05??Z*_JC271>2y{A)qy@G_p#k*G*aW;e(BZGt-ld^OK z9@a%^51xF}S~{iw`WxKRd@I7e>&57#I-r96awJb5{>Qku5D1O_b?)(fn~Qr?n*2FP zaq9H)jz{tK;M**?F@$NrbjB zF1s0e<2>s;irZb6z@-o9P*R10Yb0%N`MKNdu69#|w0%p5>yjmLIC-j1AyQk^yZSv# zL51C?gH`!+w)I+Xzi9G#WmK~q{L{gK4smniJME6o?8l^tt z_g6(8Ecf&W`Kp5fyu^21`o%C*Q-(s6+8x2jPpl01!RvMA$QI zrgy)M3;n|n{w2B37g{Pb&P{G$uTCZe>Jtdjpb3d+o?N;ddp zk-Ceasp479%v@JhFCX}0k<%#x3ES+dQZ{r48OhZJ?ECNMaNXS{7A@`or0F2uC%hp^ zp&S+|-qfQR)B{eQr1_*oxF1Kqx3%RstP=bUs1BPmNJTdq{Q~C5B>jeRf(kI{2VaS~ z4subqYlIUBeW^EcA{^5cciT}M-a<1!s;Su|A#h*R$e@Ta0EcDaD@y*uu&*17LP{7O z{eBy_8&+BC5lz z20HHHeqz#)sY^r^g2K(w5h%Y&4Oqq1RceCPi?SqOi7s?^fAh$s30VujqV}ZK^mHpb z*Se}S%I~VdLVUWgurwzv6EANlABtRRPitA{Tfu^wj~lUBZ=Nge8?qlT~w5XA=j!j`@i;9unwsz!V>v3QyMz={Wfn* z{rWua?V9ybGgo`5>lyp%?MdFh9L;-q*U4-PA`5|!f#c)XfWzO_%MgSI<9Hmts{W>n zg5zZX`XuB1dVbi~?f9W)-mS`gf2{wBhT;4nyrV`B%uvUr*`IeToo{@kt;gyrSv z$sn6HHyF#X`@=8ysLt@Ijyis-;ZIt{Orf9OnsBG(_zb}Qt@gKu3=iQjf!!D~PXXSqNoD`wMdo zeRoy|9-9lFg@s~FNt%Olq(J#h_g5os&(5YirKB_o;AK00a|&&Lj>u9)#%v)FoOE?0(v5P{kNX+QI4dV%8X5M7K2>fJ&0u9|-V4`z!(;AL;tV|DYrc3US+ue_vsAZh<5f!3^fxNHNb`Ah z3tz6T0dI^|nTOS*eRSfL*u@w<0Yn$7)|X<<^G9>r?nVD_MNFp8Y9bGwZQDxDCRs9-PM>KI+^a7QT_*M2 zc-o24HU`aT)x!Y!!c2(CuwrS4z-w-^!8MoUn`!uik=w2QB!@{%kgG8Jdd-bzYnV%B z<4uB-25`)g(WNhugZqb&`YWDs-REd||jIo_c+^%IF1DW$(zU70eo_Inn2dQhBD zl8;g2t<-Xs4O7^xGgDBF6SqGYFIvvS{!nlk;DyZ3pRLfpDAGICrt9eQ$&vF=`7bKP zA7Cx}rg!6>qAore4?487!q{1e9`kg)eIgeJ-?vnysL81t%#cwtd^-!19|^TdC^=9g zJen_|2@!CyA#qV>vdPX?uXR*LG#cKV&TOTE>U>@xui6&Xy2^xRWyxjAvTcmufK4zj zCssRa!=JCvw^1y!tVRQuc9~KEnJ(1!6q=3dC$>#p!iDDh;5=pGHE#gde~plCVSm>$ z+C?~NW48F_=u2Um&6CmEp}R1{vLf6$f*C|Sv(h*@A2Du6Qa$a)!NIU0%@A^NXB_*sP@r>3*z6A27uV)eyJSd>Tt{kr z`syU>aQUMc&!!P!U#+RlLgvXm;(0Q+GnHQ&X(aO?)SX~9Gd)B&_#*u0ub_5w+qns& z1G!z(1|*Y0Iu&x5PnFhDF~+IC*cK;M z-c!h8VqxXZZHfOt6nv+0x6hhYrqFzT30>_J91li0XVqgD4A~V?|auGd70L8 zr`oGLiF93*>PF_Cpglk2I?`5g~INVn$slZxwZ5AIH)ynE7gHd|q|-j}^ZjHhK1OL^C5kD83I z4L(pCRl%$PIcgm*XV)c{za2b0o$A6gsp&s>IcRs;T1!5xq>Z@ySMxO>8!_lA({-AY z6d5^K?S74yx!TFbtDD@{USoQv-pSsT(JJVdXNd1bWIyPrW3a)Uq}t~D*;chfubS~C zhgcr-ex~>`EQjzMidvIhqx*I0us?^n%V?QI;Lwq(TKk$6$X?Q|R%r@0V!67Fnseuf zJFQGJ3J-PX&A8w)ZT+M}h3prhwWPh)c0&4Oe;ucqsEie%8J_LBndjl%%S%aKV{`9AAtsDkjT-Kt6DtiYs&3rc@b|?DGf`;HL#QvyHW6L zdoX#YeVjh>1{fiq_Z&JDMJv|lMR%JOMhpwqeIz}pj9@{gdKSC_<=pPUlXlgoIv9ye zNP#>fzod+`d>Z4;{5q`C@Mw3)^DnTbo;@JW%`zm|H5zFxI3enoTkSBP@gNH|(9kKs zl@0`lT?$8=V}c6&ToYlNJ9@z2l5be9Rqh?Qq>6f?8^@Et?epCN#lsh!o||Rye*3t~ zq&9D{L5)2gkj&OS3q{w`RM)5`xBV>fp$8)wA5JK4Q^DV~E2h>RRUG5C9OeCEP)*b% z>soXS8z=i+rfAjaq+%kep%81uV4f78b;R0#^WHAwF~h-HEl2lPc-hr!M|w{2w1e*; zc^DA%o)TMaznMLk2?_tE7(k8FbjWZY1SVtFTZ4Z=W50-&&KylvqzAbWu^5(^Obk8KTnOl=Ek02LzNlR~p~C z4URD+3J}V+FFXxGrHw!ND-i+Li?BM5xggqL7P@@cOVSm9O zO4rhy?1Y5pm9xV}qs|_gGf>pnp#;gIhMDXR7>FW7n&6XTMx%l_BV~tvxY;RPG#r)X0ixIW_B%yGRtH#*%u8rt^DS-Q}FqOlAbwlPQvyTQ= z{L(88B&gXKYpS`(@M*JajH&pn)+*SwUhNtV1RFJ&qpRqF56vl}J6Qk}J#}ru1<2N` zDQ~<`XLls&#&3n!!=7E3Pf_L(JMG*$IqL>E7)xC;ZTAHzeCJw-@AJB4+BmL2Z)27) zNU#GK7Q#s;^!s{hL1g!%#c>cZQhH*{ida;j06U{1#|#HQ!y+=5R)NdoeCGd6^`YyEM#3qMoICK52K>d!U1(+*&$cSUXu-j_1IXch;=;$K;M81*>XBJ6~D*!A$DK z<-nSSx$2Z&3WL+6_OdKp_q_>FEnXaV#?C`G=uWPI!^<1WIT!j5uj{uK#wj-=jh!*l znf79UfoQd&y-8>)|Gh6;{v0`bAU)*%XOMP9j zD<%57{QJ|W1Q&gKPmuX9D6Dh9>vq0*_(%ybNPhlyW{epCXk`*iH*thH38Ui>BsoBt zqCw`wz%WhXY(&y|{`|}H@hJEBe2ig0MgwcnF|9>mroapN81x9-oC6?Wl;oNFIVks9 zg0i9^eFG9H_K#;IY_SZ3>QVMl=XAh)GHm;%oOJbFVi>4rHD*0Oy}tAsimZC7l_DMKLR7!gDsxm3~Z4JxMeM)^d|;b zwuAoMdG!}?qkgliYcyx=rkAHZqmVmKR3b)1ICFY_+Ul+ywij#@0M?8N`4uag985_+ zR;(nqP#`z%KT9ivW$iAkz-4{)b25J8ppBxYOn&`U&5aoRBJ4%!V69s8me#+fLN5RL zkMG37I*c9zdFlxxfH2e&$i@fm* zJ1Vgw)L&|8R>WB3ub5<<0Myb*2-=Wo!$G{ulzI9W{%3fOF=l3OsJ1Q&*}ch=%>Dv9 z1A_`%C4X_@ai$kv9LC+w&eS%vfMa4zI?AcwPlq!foorBXWm95z$E!z$#3oC!IXpOV z)pfXnLTgrVuGwmUORYT8LZ#A+h$eyH;hmNB+}xTA0)~Y!ls#CX@!^Y6wb7Ij84!oL zBbvh1GHS&e@EB^*73>=#>@rEI!Wiu}PbkAS>@!O!!#sc-1)2tMXysJxR%KO>-pka- zZVSPRe9|*Av_vfu9JI^sDV<40XgaxjIp0?cdcAV9BsQ&{XZz&&u&ed-Gop2r-so{8t8o(7b52$hb>zsSb2w;2qvENe4 zV_u}Xwk)VzfesTH{n~TE`i}#2CQ5{#TGzSg< zyPMhCdbh!%DL7%1?`q*Hv!$#2B&u%9DRbMo5M3<<1oce}Hy?o8rZ#RY$tE6m^-HJy z^eQ-@D(vFbn8El9ZM8LIkLv9EPA=7#!_*h9Hd(_(h-u5>uB+9b{O!$W*s$7N_pdp? z9Ooi#cbz@dOpIBq&RbjK`EF038;;)(YcUeG^#`X%!2Ks+o#~+DU z$D2&ukC7={-B#PfmjA}2WlMC2A5&`-CL)V?F<7T>&QUlv9atA?pvQF9W&(2aT`(v) z(;%~2F)1ih3C}+w^JpwOh(mJHSNd%F~@2{DQ$xcJZ-zJJ7q}It~*M?z9}g48~7~p>%^)h zZqA0ouCKdfQUcviW_?(w-|Z^p((4g;)Wz4o7hYCpv{_6nA{$Dzp&tK4eN+^0@Oo`9 zy!b4d57kZ-`NlyO-mWfn&#Gm&FtH62$7tLIUN-i2CL-jxeZDCfRm{4l4Kbb@O}9^V z3Q1{^ov^M5$*+pQ&%}^r2H27S;h8*eqmemmPYBm!Z}c5*MEwmWiaK50pT($_GS`Il z7zZwFQmVSi+naseyp#>%JsFgJe^)XOlmXU3AWermtz8?ID`ds=s2XONNG7w_#iUq6 z9N-;JbRl+@K9QbOD3WbP+{!iGd*wC$`LuBwv*SnRhCX4K%}P42h}d}S9Orn)%*>+7 zYVxoeKcxiN7U)9ute1B)p$RQ@x@2lbn_%0%=GvzZPzW`Ux#*Wfwlde!jj_^rC@z(( zINj@t1rGpnfTvi{J`IL0>wS0Ba<%k$Hfm?EW%tHJ&}X5xo7G>I=$}@2^ZkNx6UF<> zyZp(!?Ck{e0;RNViVoHF_N+5Y-N=M17ae7r=tFFv`5M5oyL|5iIGfO%XDg*{Wqq-M zsi2FwG?iJB9`w^MiK7&HqODr^bI(_lQE@Z3;?o@&7MPf0<85r4ZSLzw_3>;Q!R~K_ z28peWCfORw$a+CoM`~`%sPCFJ)NWnyS)8dh1&?P^LbFup-8Jhx~R4>KlE~-}TU5n06np#g^1o7q`n{S|;YWKY=WzfZ3^4NoQbZl6JV~(~c;l6m5NmkFZucH8- zeLbHyOrwddUB_>tnNX5$Gi{;$XY*-L1{?b2Wb&8n?SzlBuxZZ+87miF&PBcVjU8#M zXURHg;ihX=U(3>+s%>ujkmH!kB#+B7?maeo%5Ocs=X0fcH6Ka7wj;?kf%2|F{g&HO zyBT-(85WkQQ$cYt6TcUDP!G$^8)F@1=An%k%+yi?|7OkA)Y_%5anKhH%D-t{h1jC* zplH=@t;dxzx*E1>wUm2ZUiCE1*leV}?BJHBo3V+hO~+-(aK+dU3`_Ow`B9#5DCJ+?9}|mJ4fguJOQF+!-Mij4D<`G$Efni*w|cF= zD+Hcoe;;$e0A(N^Nv{Jimb49KV;3B)&)m`hJ{#$dLrB>w4R#l((gt?3a{4K{ON|1& z?c8>2jiR6JyvD@TA<*tvVxOh}GwZ;E?o{CM7@jMg=oq4tz1}zCqDBc<0_6?oGT^;QC-zVHD9B`eaK#-;@JVmMlFr*#FUz(#f0Yam41c zOsbTB;eaV$=ERrmXYhDwYHDX9#38gRd8}mKkq;KmBehw%b5cGm}x%l1_* zmCQ5b^`JJ!fH@y&AVtgc8ju((-`O4`d0TP4p~e1%Z{1DA{_oQ*_k|~C3hQd0;n{gf zjbc!atlX)}ec3JTPW(tB63?|6k>}V;lO#`)=U^dbfsp64VNa#{Vz1&pqm)<*NbEs= zjARsucS*ppzkJZE85)1Ng32Z$$5UKwf(^)qVVRd=DD@lea(;7ogxeHbsf`n4TCxOQ zi5@!F5S2L>j#|xtM3z?h#A6n(|sg9 zx4k0G;<)YyPcLc3i9|$mRa}(LMmisP%R3~?OHkJP;A%QwU3)AQ&kJ~cYuisvEu(A` zv;en?BoI35Z}-wx$$rRm&k^+ejUFqI~MxQ?7r zpUrgjMx}%1YMZaxjFwp=Lo1%4yTxJX2q7yp92puy7T=bq@%Jo|LUAJ**(W)c&CK%c z-q8`L1Q6fWSK@B7x^0hA>?W~O=5>ZePQv%%d%ITp$peg&_orH-l7zLVsElKedD8PP z%H9MGrfhL7Tq=jL;AH0CQb!8<*#`J|9`owM^@P5ps>`o1)e-02g=CSlX{K_7!F^gm z0>(>*AWy-rsWj|C&;j9S+)(|ORkPz%IJW}}sW0-Qn8%&@BQrSX$RxB#IpIB~a zeJzs=IUv!#*ng^Y5U`BtmHvNOgln^JXy{m4_Cc`;Ms+wB*rg*8(oSNS3HgKS40lgY zz#`;ZRf<*B!v3bd3+^(|2wiD0cf&A?tv7N52r=hq60!p*ZShK~Wd$liJbScH4}|HJ zfCnE|F98Wda6qXe+V$ys_#@Bi8+; zLaQ{C+%p`=KrYpL_Qox>kTgS3EH+tF9txhV*HPVQ*mIjKK}UXls9#M#Dz3R;|JXfc z9g|)S0M&1O(YU}z3nACV=2|qGh1PNIKxQ|d*k!bTS9Z{SjfwG)=i6;IJRR6WPROGx zyeNU9=@gCNDKAWe4|FS#9Pp~l!Cj?qgvs)d7p=js42KW2ZGX{NRyxQGU;PG#uqK_iU0<&=tzp%(kR9wn>uHS%b26Yrz(Q`$xWTB%yh~^ z*xPKG{ldD@ffOERnXU8E@efJEF1>C*?Y@$E&JeL+qc^6$4V>Xu_@D5R02oICjdVK*GL9+H^^!YV=Cdw4GExp4O%_SV?i0CZisfjO zKYz=ivEHT@gW3tbZ!UKGw`Co0l$(cM-LDD)P{5~_;0JKMI)N+%SiK#MVE5e#3BQ3F z3PD}J{x8It>Ey7GhW#ymeMOdh*}i~t5^QKS`UP05LrGFe9WyNau3;1haFCsR0)GGn zYSA-7)qZ`o2`c}7y7Uky$ElRTK*R-D>LMrv4QLo%1y-0eD&%f5g2}!;Bcc>WjDt2p z1r2tC8_5pfE?n3CYDdW(5j)_Dzxe#ObXCQNfb*l{uM@4odhtHYPFUn=5hX&1h*}!h zsKLlhLM6Y4y@1;Q32G?$qVTg-hDSvb(AY?#7XV%%86vv8@w;ZlvBF!IYW|30?T4tfE^M{1mY`9pfoaxnURl~We$H26XeDCFZ5}R9pVk(tvcD0q zXtC5)0?sWC;0&PVSu$4UAnGB3HgXY2QYSMt#)K@@p}7=%_DNDRX?I;5LTo%_530|Bgy!-mMq-V zpgn8`m8Txu*&(zQ6PzTK=rnq=V!O8eeTNiQjm*@jr8dFqxbKG1R)=D9X8EZL%vS(t zrZ;Cl&wlI2xubb-;_qG=FSP6r2LO!D3TqDlB{T#%3;-6QTo%@fr`Vvkt|k=XKLExk zXzSOnvH-wLul9QffmB!dV_lII2BvmC*bfjAJA95=uSYGN1&s3)M~b|MxCuTjoH;R0 zKfFu+)&VLRh9s$fT#OSqg}`tiTZgt zVho(bKwv#0!NWrMCJp-o3s3$73B>R?(P8v+ z1c-2J{2Vxw!t6R=gwnQCG08pjTzYTk7Znm1jdHc9<2ih)ZN~lW=H$>bGUDp=)>jj< z;TS}=}L7`U3oZJCMRY}!2MBFX?gGs7`sM#(wZ{U`#NSQ^nW5WX0FSDXTk(x2+(~hBWYoS0dSr0?XS`9w#mA7q|?vV#MS=ky*yi?+?_U z*I}wYJ~-$x0h&57_M28tkA?;_7YrO77!XWY*voGMs!uOw(k~{=o@BD;F=RS7Em6ee z=9DzQTO%(9{N*N)xv*EK2$f9Altxhmhr)nNBA@ z##Mz78ec1WC5UmF>w=`p-XUX@jsu9`m~HUSV`UidD@ToIXjTZk? zlBx-Zkv~rSP_&gc>MJFzKe`Qvu}vP+V{R7dA0HkLw8)_HoH?kEf_*3GyQs4!m zmL)?x0irMFMM(gL0d^ExLl83pd(3(u^)s%@eMPN9LKYW>Q47aKnHZ_Av=0%!@k@3S zCiL$cXaR!N1;O-^D?Z%{hC%_eAVkqp7n=3%T&5Tip7l>IAN%?4k5;L{&OLGVV48Q- zGsaxfBRZ0vbA&`BF=!Y`ln%`$IZ8TfhdFIb5iD}61s9;480+Wq)^i_WV{i)}`e!n=xn=o(w#3zq2vMFLgxg3|@z{FSSj&|KUho=R`k;#3Ee)Kn;$b zI0QhcFh;GB(2iLn%?Lpq%o14zB!#F;xXdq<*95^A_e+L`6DjYP%TKfmGpJeQj>L}Y zur#0GX2>M}&>v$ctoT%;RyjUZNFWMT6Qt(5blW4ziIDW86T6_nI1?(Z&?Z|Fk!Cz7 zcf#%8;%Mh;f}>#&Cw&{hx3>hsRh#8zRi-cLEz{u%9W1X#{eF>&-P)*+e3w?IWe2nQ`w?^8?|c#F`}F
ESy=vKIu0W<1MLqD>K_~w2it$Ki0~OX82;hourvGsbAH%F{}0CFf8mw< z8`{TzS*s0yEY-FS`gH$m*0-~>au?Tku=(*R)7t4f8JhhY_wm2bu~`0dz!$@hoBSUP z4g(852m60-o#~%Fv;3e=82*E;+vz(vnb9z?{F_bu3lfXvUl3paHw!ff8`BTqbiq{v#u5lAsP}FV6qct zoP6KU&*i>XK7nj2bO4-x5qmd&4ENl1?t$%672j_b4ElawzG!>BZsz9pymw!GeY}uS z-yF@2_qY3@e!zD@`@&QVDzZAw%6VJa&SgJ!<6Plelf4PJ(*!<w*%;uKI(p-B~2 z<>w9LvC)aYthfIg7T@P*!%s9Y_ z-%-Ag-_yy9QC@AnR+6h|gt5=&Euv98au*D9`ILL14pkl}r6dJVVfZ9MRtelsU%b$V zB2Qm8a8+Cg4&thXpoU#&X{>4n;k_!44oQ_}E^Kj5wP+rI-&`utAW7H)yK-jXj3V0B z1@8OL-*L#dOAI^@ROP!zqL2M9w5}%P4s;uSl{$|&i>RlBW(acui8E{ScI1*&{L7XL zB1IZ9!DN(C@a0?wBHPh!9wig#b{rMnK1bCq=0wJk}<07DaqIF%15U zMm0ASRASKE!q+H4(CJ$NnJCkPP-fI36U7MP!sIeg%yKj}nzS>b=|Cil5+dP0m{j@~ zK}&kUaMFLmSQQk=fG+6JAS8-&b0;E&`w?#^z=>!YqAFVMd49QNV&P(8l#dLt8JZH( zh|hLJGh@GQ*&WCcYZ!J8h!*X)NsZizD^bFt!AchmH@JTg!glnDVw%5Z4G~8O6Jzdo z5_9q{W;GYa9S`(xDsx5)K!=QVj)NeyEf^lGudtE#pchf}inQiMX|mlJ;P# z64hGotF{dGPqNlvnl8`js{F=N%>_fgVKNqya@CLudxw+yZKEi*;~ zQiv(+F=~j+PnOM0gJ^W}RnPdfVT@BYGkn*R0~@HddbMVRKLMcvZ?L$%(iyNdEioCG z?{CVx?q9#V%I$iCq{1V8LY`VO*MjsUUX0%?&dT)y!)g4g z$bWy_-G0I2`0Mdn)hTirSk9XXOGKDx7TOYp%2 zEt6Gl?}D;ZLlJN!AdnNSDn}B%bXz+5b z4+8qjLi(+js1;SEy!+X}W?K3?v+iE@2905E#$q5{ir6rUFkzWkFbr1)^P#WMZo=Umqy0E4*Xc`W z`YJ0^s34O7$;gT^;)hbCL6ZihpDrKixdGCE&c57eh)?nEEjBFhL ztGrTjSBU}m&r69l84y2&v2OEAXY!zOQaVmjM{+wP0_uB7FDhv-KCfx728N$*?fWfX z-E>#7jFK?4RCOL+Ze%D*t=_$Y+7(ZN?rC11g>l4SOmF}Z(_LFg`NXvQ`XDFPaimRqt&6JJ6q<3=6kQJTkCk*ngj+(5@zJWO z>b1oxS;vwJd`790q>fcBGSq>*m}Ip5iueypXXb=&i|wUcJA3T?oPh} z>$Npbs)PHQpuQU4`W|}E zNep^$EHL0j1J*DY@Z=DYNPq^=5UErxEHDDp6WJmLN)|CH$N0RkjTIL;E`y?3&NE1|ir%Of0zgr42FEj9 z^~5U&RXK|foPuIl_Pq(P=y-}>6aJ`h%_kJI8}}m=CAOD~Hp^`i^7;5hA*y-!jEZZa zGbRT3kCr5V;TD6!C}uVYX&rzP)sDVxvrwLk>a;i-qXPxHV%pHb=8kBfDb>ghiP!|* zVgyo)>6yMr5{*1d&zcvHK2I?bMG1v-y|BjSFfr9wJur${>3mZy2C!(6Q40kZx95zG zK%d68-<$1FL};`WK6fxcRg-2dNL?y6+fvyE&N*O1j zk_eK=rc;p!X3J3FkjxuqZ2$}jm84umnea$7FBbexW+E97FC;ZX2Nl$UNr}%AA*u6K z8Lf7b2nBS)U6SJ5Z}ag15EZ8#9Xlg0-IFjNGx&mIMynPxQyVR1d}MYD#H?IYH(L-Z zWtd`Tir*L?KD9ASX2k^$(cIy&o91Z(Gz;@V0X4f9Pc*D8h(LQJEP`RuXP%^bl#;2; z{Kh)Td+$(`&JZ{G81^|U_n01ugW}>kYUc4|@+gFsd(G{~C3=gm4c4H*BytK?_$hHJ<@ColfV@st}{jn-6K}mpi z#RUz^Fb>&*396{5L_$;q_4}A^LRg!YVf6_hV7g&Y(U=TA7Vosewi3ed1sI*eSRYAH zU_^*Hv-_CaY``~x69ifNY>PO6MsRJg>uyi(K?AnMgdor`cEqr}<>mXW12xox?fpL` z0O-Yw_hmB=0at~~ar5;-vqHiK<9@rJjTNvA&}%H6Te?VK^OIjpeVvCEXZVw)iL#E=L3BAwNw`x?gxRjnWlv_o4G z!W5TO-LWd$#%CCZVpu}jVC|72*)JT2VpKt zFhnVCu^K_F{=+!cMKHMxX)zpO4R*uanG>j|bis^36l`Y1^F~Ee1enReF*_OUF$nRH zXhYjFK;rA;B(5RpGNsUPri1)IYNaH^^`@7@1PsdEh&0SQBHC6)>K8WyX7e-6IeJuK z`95VCFnY#}bS1VU5UG49&Ldpf*IEZs4gR6tMFl1q*_z8t1S7xWj?`pW@b?$e&?PCz z`Xpo=;ba=4&?$*Dx@8ep4%6Ee(BajF`#d@e$wtVXe5rPwmw8T2qQlJGePEroa1*kR z2UR1qw~8cIGD`5b7vX=OmmzK27aFz&g$=xjy12r@`O5@M;Y69|#oV^_w?VtzDXk8S zqy?dBuP?I>_1~Q;c#d5Y7`~mZzo*K=Hv0iDt!|X}fQgY!LC3yx@E*Kr~ zIWcgt*?zJniqLJA;tonIZlrHDr)!8+j`HTQ*>bHWf|+R$$IfcZgHOO*X<*T!ZFmT} zW$enB(*%$aGc+*eH4tE}8`9vgD=Kryj7PZIY}?325t)RDsGSm{2@ng*z^cDTfGR?o z9ULIkpAs%W#BH<_Zih)o_s>-Ni~^Mli;T0`GRBka8}YO=7iYu}NXLwBmFiwqFaVll zyS0@qf+W;Tn9~r&wjyjb8}gx&xtlGGe!Llpb3p$6ewQ47B$G~-2BOvZ5_ykbaV+&qUhik+Yd1#LCs5g~H#pqL|O zM~vzS&sojGATgX#{9pqTO7d!zS_g(bu^&-lU=+v#Vy>8<{6&CR`4D`TL{=Ovs5W7O z6p0eMBIA)5L9n{s(#CUKarI8wu zP>*E2y?}FGBm){?(d;4_F6Iiz1faB|8U-rC&%t|{pA>oSY6G~WPI~U6qE5+*67f-s z;FO4{z+r#53MjwF2%OY6L@Qg7v{k&#W!L4z-kwU$Yj(=;NTw}2J8Yt#i}hYp=vxBJ z9s~>9wr&a1VM%X1lPiD*eM{5$W>Qz!o-g`JT=Yercx*>CMdxlII^A(Kg*BjSqLzF8 zlW}jlePtGUJj-cq=XSUj;JOZ2{Saap^I;0{C@N&#&?=pKoiB6V8M@RPlq z<(E~9+9TdR6sbh6i>i8HW$a@eG?I)QlY(8RWlMYAeB zo+*ZTRXViB>Y6_;`M#aCJG6uKAI@jGEIl)}^B|vBKQFhy{{H>!LT`1t=C^iM6Vjhn z*}h~^wUjglW;4LyQ?|@gvzF(p{e5j?P1p5lW{t)pq5bPc^WkXBV9QzCE#LlFCG=*H zVa)A@v_KZULEBNj783XS{(5<)SlYVG4uso{*`*QAcfN;&ff8`la68if@rEOeDBTI; zDLYV!g&&SSS+U7(Qf2W~>Bv{wuk`h(4e_=J^^qE1aTb_Cy~V6Pe*X`;lb@K~YCp-D zL|$&8-J`=c()(E7*Bi9Lp3mQBe11OvkBhv%L&%bN{1W7kqj12{i*WqTahu@R+38GN z7Z`>OCGDos7TaJ9D3ycSaCLUK`xbRg_sr?+U(uaj-2Fv3K04gZO-xu-;^p$fGu(NA z*0zV+Jpz7aW0xYmtv+W!e7jv(dpLUx*izkRr}N#W#J!tp1H$0H+j8bkf9#*?HE=1&;F8~W$%09!LpCnNwpYpudAM1cT#u%xfDWQ3g3|JcV}q}_Az;` z7ZbI^dz=a8$d$ffIrMUWe3{Y-+j&iE@M{E{37&wrm6R^t=I?Enwg?GnI4yaW7ty)-R|6{blS7khaT2h=;!p?YVd_?SMhE4YB+cwdRV29s|rQ&;J zU*v~f*`0chw|d%VaW#c`(ZDvl1@1Ds@a+LeyY8Q$7E{Z1`dJ=w4t=N5mi7j4=xYvo zY8m!ubIiU|hdI&oJ^Q_@^tw{?HHNgWUQ;R^Ze41L8XKLaD$tRbn28zp)cBfR-0SnY zv3m*Y>?AMyTZV8=?)DibOu_ukvpz$a_kcK!9p+w+3thJOPoq1BqiokhTN^uvE_Qzf zo(@o$xLsJs^-yN8oNbowgz(+TF*{A1FHb5qQb`$3EO(!J`=ff+BGx+H!g=pSE$G23 z<-~aIu917C-dsLoBe!0q*wiJa8SEum5_RSPHbf|7jIA}ELsMfImRK0bv9zV{yTpv4 z>1EV1KU`jF>^Jt>5>FtUx^f0hw>IUyfcM_C_aQr`qc?@#(Xl9X($%-=cA)l=ZK0@^(7V9jwd_ z-M&miZ)Mfp=6Qk?wRkr4vwuF${Jn1Aze4Wy4OhQ}H6>TJ+8I^Cof zddivHqbFMci?)CgD;(86`=jmB1ua`LBkZND*vW-v;IqGSdbx{vJ7_PQwhdQcww19p z7A`ERZ&f#b(X0Qi*WC)`*pTHtFA=UM&+UhO@^roR_Lb9v-F~t4(C)IJqv`n7L*~rL zA2%rdcR}Vm2alB0+l{ZRtr02tM~QVJEE^Egn#P80PAOWQOftl(V9cgfqMQR?R9e+8 zRun=gHf*G;77fYa2C1N|L02+HvdJ%b(pM`cNt%S`WTjtp(z4ArIs`_n$k+|r9R`8? z;aye$Eij}4ttc^hc_TX0iBO^Z#$BY4mMb1nfz%tO;Y>_xiUejop=%{7NenrdPrY#i%*OD5RY0eN+^2}9cBW5-8edIEC1;hHX zJRCYB=OL1cpL@%A6eb4z9&W}C^A)#7WOj*RT_hKoK1s|b1L}#8!2wwpX3Cz&J7E^V2m1lzbLVkITU0R^;VG(VuA zmyqg!%`~yJbwV3f1WQb7B#mo~V(ISGRZ)svP6>2O@h^TK;2e7jv0yAAUZN;Ay2P4r ziMK$;U0Ft=tcZw>`tvosr;;K-nkuKSlCmtP?n=D^jeVCBUsLR;n9?We*1+y1(VQQG z_bJ<=aM`ncmokJ~ks?LnoV3frcmi?OF_^-#SPUo~Ttwj>d;5k#B}HsewV;*)Cr&0g z;Z(rjIJ?hH#~a0TO+jaBHSIE9qG-5^cg0QD0=4v+(2Me}mH{bYb|Mj#Pk~u<-rh?g zMMpw4qLdO&p{A>%pg2ji;Yh1WcVb3Bs+RJWtDgZv?UIe%HBDfL!c5%pPD67R#ug9DKJE$AaY0u!6YG-(eGAk0<Nc0oO!@+qpV6YVX zHB?hts5u?nlyc+Ci0qxEi8~S85=IyNzEU1yAxZVh?B=x0MieY1B?H!i-1(DD{wOA!p>LeybbE zHS)8KPHo-R1^y{v!Dy$n6W&B=BZP&GuXMmxDr=YZW*)MY$Ub6=-Do9mhqYAR4r@-C zN5xY#Vkf}a8|P&i*ZBwV8~{n z5wg`075^c(85Wb(kyvp)o1LAQ&BXL#gkUD1u|aBPC*mqoGRL6484sp{nVFl&@-$BT zcs8b8*g|F`{(_GaXcnY`8y2h8(XDBY1Oq#Z97o2Gkuvh>=BT8MQy@-@sd)v}^61Vb zN1)U#hipTmb8pz%ay@X}frTqB_GyWm zSWcJe;&d8Tiz;EvtI zvc#^mV%x0r`jBrWx*%uM_R`z_jZCY#3(;MX#F<0d(Pl0Pxa&wzkcRItm z_FVs656@}w_t`du#r6lLXZ5Jy^9Fd*&m*cK`Phg{QI)!gn~T@G zh>Ig$gQ3R(!=EVgnc5iW{A2R8a4=L;{WMFWQHK&_NqMk)uDb;)zb&HODj&kMBX&qr zhTYbe%Y)qZIlEnAz3eUvcy}DNdHskj-5lw=y1b{OTL7!Q6!s>|KW}$jb3Z&ATtd$+ z&hOc=LX3mNivZN^RsQODINhdg{!3rgd5TjHvRh^L&n$W;uMJ)Z_RIces`{Q8ss3x1@g?_eFIHa`1t zM>nYp&KTy*q^lFq;H|13(A}C=&jf||`$#HS;auKRd&D-A5rnha!H}?OH zUW4A%5bw@-AzBWf-mNZHU9%!4yVDK_cqJp;K^UYz(0fRBuscB8#x_gf_6E2m%=x`z zlvn43{MZwOSyxZ}Iufw(n_}-wsSo(mwN77q9c>7{$@10|-i=bp-&OXsm3haxU?>eW zC!-gdaUHaM7iXu2R_zLHP*t7iF8ldzbJVL$b69S=f%2-${1V=;*S{wnTf8^ApMHFn zY|c6QA~Y_TtOc&|II!(AY9 zP!OII)(dyP;P$bF3)++7n6~UDPp(J;Ltxu7=dD z-nErKQ8#DH@!MJF=8%Csmm>a?YkuY+&qWWnpn29NqH`b8a$+(G#bkZ6Sw z$@vW;J+e@_RDXopREL%Q$Mf^(^Tnn4igs(+G-K5|oeC#0^$A7@p4NiZkM)W9%%~t0 zi^RWj@JK!McI$}8PjxMbpQ>S6W*Xn!Oi$&rBLz^z#&0fOj4%SB7{XOt*xEDvd1q~D zRa4%!F6%YK#%1{y!JIgK#FOxrQ!2or*Iinv9t93pbEcVb`Yfs}J+o}hwZ@#Nn|IXb zzIygG^TlQ?91drXvwOSmC@H_G_ozSa5?MNF%^uXIR$(qN{YoB(fxF%MY2|3EYs8=g zS^f3ZU%9SMs^_+Q&k>U$uUq-uZOxizeRQ17O_92c0`ga90bpyq4IF=$>$6z8=r$rm zb(mjSL3{S@m;dE$XEvx^7TzN?*Gk_)NYN+f>g#ny>wPX)mCFlU_>MH>7#*hPG41Xc z_?sOxf8p8EzV`S#ySJyYx96e5Gu}$?yW#dpZ|~u1FRMpG8d&7MsKvHrp;;w+kHrbJ z4hRR1n`ew`7E5(IvXE=fC{I=oJC_330H$M_;C!|16XL9>{}NiTJ2PX(M{voCB-`?g z=xvQR(h)14YRuYkDwtZ2B@93xa^Zopy$k3Rk#MUpOw(gi!7g-5Z`0pRCeSKZu1_}D zq7A4Bdd%@2>{K^@~=;I1}n7WEF&_G7m5;0E>hf6 z6kLb^3QLy>z=Gyma1!bJj4SP~p! z57*NwL!GBN<$gi4$PipNR#G)|)KBb3DOZw`5sQX23{~w}RS=U}!M0?`?5PKm0rtVx z+K`^4p_e(Yd5DAR!i&O+R*6wmTp_4QvtmR$z1-(5M(Sxqd&PnT4o0T%u2CW2RkMs& zfd!%{h@E^1X3R)H{qX_-Va_+qMTVeS<38HN51W$={@{5u4J!&7G8Qsc2xHHKTlm_< zmcS3pEmikt((Xxs>my%h_ocP0?1D+ z-<;w91aR66+%fCD3BC%7d17Urf=^RY3y)=7fnVoWi2+&;fTNDF#;4;8;rExh*l6@% zFgcGs#IqQQCZ@=MsG&qsl|&z>YDLAU-AoKlWARP$+!-jr%|<9jVvLEIO6S1p&@N#l zIH7>j6{({1#9VI67_=HR6ZnJHft04wK!05RMrSA1Eb-$@(0}XNtGb=@&kF+SBA#Iw zPsdX7Iv|`o8YX$ZgY{>?S2L*D>N#v%$2Tq_&vLW!1l_tjO7fJX>lv4bD8WBoK-#4H zQ=ek0MQ56+M^i9#aEe!JoT%=^p(WJEspUCY$NMeVb1G2M#U8IR>upzK(-!rupsJV3 z0L>3`r&c{a$(GThCIX2+Gp=%lJ`=H%yo5U);VsFWl<(3taOgJbwpn3HcfyN%qrcKi z)-XqW{Ox~!n=1Ez;{^QA$)oH)IhU*iKk;<`vc3^8G5#M}i){Zy6aKf2fKrB@3XF{Z zg%|GMG8NhW4JZ2FOziw09k4R}Kw^J@sRT^_^Qc6we;V!V*an^|8G%)Z2y*i z_rJ0uva_)LI5Gd-j;I}v)8**5r@s&{7qDctE4jZ9pe1R2JRoogao^;4$N{k`cE};% z=U3@L>(*q}=`PmPwRlc2d%Y&ysKKt5T%Dy^iSR88{q?#e^Y_UsGZ=C}uJ7;3K_7ld zeN(8voZP!Hgj3QYO>)w#jQ{7-5dL4s4{9If8K8O7#D=*S^os*>!eI3#B`j`q1IK34)zYZ?#BwrY)prqz8J`Zf`3)#-58-;k~7jz zT4Sb&D?lB!04YEa_k^FV&iptMm!GbH`+ppX0RjOnYS9b#x^Mmi9;gU9KaNE1d9a9d zvqm>CtnSx_9^IUmRj3fud-;A9bM#^@8lM$_KrM@WZ@mx%J*eRT*wKS~B#mC0QBIKp zR-Z6?tFv72Q-D?q`W7IsV8w6QfvQ#few1#DN14o`SL}4le4jWub zUVqe0KtTO#$GNDHG?lb^V<2wt&Tt$w01!`cW@K!Kky z@*h7U%Y;>|uv=E|^ zpk#(^(KaNrw1V5#45A`58UX}54h7?H735mN(*3-v^{c{8`T+z%0yLWX42Wq+c4=Bg z{|F1jf9!}=a0I6`Y_*j^3io^!@~Tt>{$R|Ezsm8As3c^YQ3#7t&rz`yp{vnEo)I4n zLbBE+hPy`vBRC1Ms^OxyDqk@eMw&WWWcPvxwT0e` zm{c@9=N(*(SXR9(nSIzpP{6wp~wI!Z5#cC1g1=oBnHE8 z&MHZjC_wy`qB@2?t*M<_%>uW3x+;l_DC)Gbyv$~3&@D26Y^E}PnkYk9uu3j0MV8Ef z-%^dCKI+3ZYZC?;dRt~zh6&B_1AvGkK9!$?pV{7G&(^l!Wdj!)v#Tvt+|W_VmUY&1 z*`1kTg+sM3hahj-d*YqT4!2#j{M9bm&}l<-bGn(W)l8aSA+Jr-m^IP7nqpc)NuueW zb=q1IRmq{blEs?vT$c%}NnH_|=-Ps5tR0ZOkf!>v7XBT;`l-L)rw9;;Zw30_Z{9UzG9Yo52zS0sBXire6u(=Oy18)IDM-#&*8_deSs)HBbx) zOO#lkQ3+~Z3ZKD3bC4#`*MOctlCnq^qCXU%Hc)zi4<(}qP!kvm|6r=z8%ZQU2pmC& zJtO9&0otKnE1(-7JRT4R92_Qp+>z&=f72pTPBQSmT2%rY0sfc^wL+o12&ywU!74M= z5vim~fITLWt^hAfGE|Pffk<#1s#wtaIP~h?d|1$w(ZoVm0@t8^|D&N8v7h>CEkzP| z3Xh}J4{tFb5xlYuUQ`Cz}k!eo%nhH6fCYuiBA*`d1E=(14P*T+#O;svKE?D1{)g@9wKZF7l z^YMb)xsU?xiHOoekw6K0i3A|FI)v{Rp@-$OK7{*8-0(GZIDiO1oTi9$&JY2`2D~aF zq(Kn$bV6jJWzir45I!|+x$+<^GE(akSBVZGf}rv()odvYjAT@Cft33sy2>{-z<}{s zMrd6$C3TH|1!)02#Qrf!UZ{RknyxxBWE7sfHmaxYK-nr~s+J3o@;(-edy_D7GUqV? zARD-vxxpprMiFR++bg|A+^tnLqj`=8fJX5>BM2QuN3Oy-X&VK=*JJU5hpZQypuQvq zOc~b_M-%Qx`)dB3RDbDdBf3Lk0M~W*MH29K7|IQ_tO#V2V0B69NIFv~`_vP-td5ky zCA}%y)WN3KG#giKHemuab9$ju<$wOJ!_n6er`8JV#cU!(?b+o9yU zNCY_rPa-%0fj=&&OPUiPHQ;DBGKHtWJPRY>Z3onY?9Wn!QxqDW!% zP{ru179E@w8c;Z)t@$qD?TgS+Dx>m#kuqozT|>|cVw>1())vOB9Yw3GP`1u& z!sH9p;5!UOUEl)^SHh5{l?@lBa=S>0s!n>MmCLiJw~EKAD4|?@7+|*pC6sJ&KpFE| z6)%9MNb2QxKrANpOPEvuN3`7WZUC5%+gyADEUjYqxM}TvnirW8fDCCCg*)(aajS~g z=!}aV{ZX$wb-~L&5?7U;CMlIgp?H^HP&;WHeRh5mY!uj0kcw`o%2PHs-TG((cYmUa z%U_9+c0MAAck4EmI-G^zwIGr@d+xgKICkA}?zrP#amUzujyRxs%7Kjz<}~*!Xx-F~ARkV)c>F<^QpNBdCzpv-J@6WNb zI{%NpKH;hOzW$|)Z5M&PU?5%1Im$S8EalsyM1(ezMOr+J+rSqc=pkN>_F6tiS{(6P z;x!>Bcs?ii;T5uW9^BK~9=xp$PY0Rq2@qiRb~5<=nqFQHBRq_`mVG=L9d+CeUG>{@ zj4z(`T8>IC=5sT;-apgag1YOe9eV0%)(?^T_{%3(!EOj6g3bA@c)7YnPqU;zhb>#Q zh)VZ9Ol#WW?8!zQChks>dr8u#&~v2jqI5OnBTf@{$4Nb;>2?qK&Sg8mb{B^DIPjqB z1Me-V+I<@>&v+f(a>s);HP?&8=|%8ybv5jeK^&E6ZX@RS^@dAhMJkwdTuM6COB-Xo z=Mm6&9(ufZcsbec4;iuW*?Ow(`pS4o#*p}N&Ye@q-X1W!g>oT?hG$W?_8o;=q2wAg z>M%a5O+dIe!uvSot zSk{Z#pV?)VZz9Nk67x%` zbWFRID%Pv6(CRRMeP6!cv8Xh5h9G`#IT45*<37?5f0_&}pBcNAKz4>&VX>Yj%I#So z6f40-C|q1ww9>NBK&^pZ=-#Ev%3~waqX)pQupQxG2{U`KGkM|bQFQmpaF4kE6{2H^ z>pXFEt;?P6l_(9+DkO$uO zysWFh2?ZurxeW?zg`JGl`?j0HciJwY(8=U=+H?O4$5lSAlU#f!xu|#TxLmre$_=l+ zlJssom+KQno#QKoQ>qg97xyO$H$4B$@19)W#IY`Lj1*={WW43i>*jaQemvv=P|^_Lig3i3wq+ z(ozn+2ioIE2BWisGHU9N3TH{rQ0EDx@&*q$C{au!Uc14*jOywc#xv;~8b}?Uyf#+z~o=@#C#$D|x<^bJ-1`-Lx=ZWyE|N z-Hc?dU87IluI$t9XLzyI{Ru6(NZsoY)oEyKG>*V&6b*QS{n9DecCd)M{o2^wf6?_H zo=m4)QG<{LUwn(5`=8Bt2=OrHZKZR`{yH+(_NNPWKB&UOj3cBB$-C`{(pN1UcX2M) zxK*N8CL=m$@h!&^j^mC+7qUDt{7#v4Ejzity%R-Lh@-e*nU-M&YS*y+bsVz5I~t?990xpP#i&3tux{rf3%ATxL!SDZ84o~50R)hOE^ z4;VT)a_=FzRbySHEGh#DFj!Si8B$*cKsEtIj4*8q1-LY^ue~V3f=U@jQ3KH!@m!-# zncQaJkUrpqX(WJ>#)(SFng;ArMhJD46?7Vu5*W-y3&>K#Wh0Gg1hI(?O&AabI`J|= zp+u+O-e_QPxoEg)Y@I8e5=^H5Od}pJ8gvY)9CC$j6?E1#iWo!$W7sM4FoKpy4HA^) zr^sc1RzA2T$T)Qs1Q6R;@<3yDg$#xR!5mlVU~L=21C%99sg%QrXxM3}M8f zO;Xg{+4>w$pGnDa^J#|iH3qwv2o`tF%ck1J8Y#fP)Tq4%o6@L+h`0+7k##gJQgjgk zcwin=0#SY&ZEXfs5xR~#$^n!;@}Vb_37)YU4Tp=3IEu7_7ZJi};F5?0H#&ZJWU*4N zKLU#qDNz#@VQ6qHsvO2hgK)!r64iYkohEf_8G(_e11F+$G6SWi_yz@AQ6@uu7zE)F zI?dOj6p0aEUoT6R5Q7E}g5@e&10oW6Lc)}3Zdr(|%g--gp9PHcSKOUcI2f-!-C3$Q zn_I29ajb~I!S`bEl*-|bx?z`OuzkA4RfVe|eEw>PP84wyV!YQRq_JVUV3DGxmM9b1 zWlU~4NNl`n8MkJ30KTqTN+C)BR_3J#$_}GP9?g>$n1%%Ca5SG$I$E7%XGM99WC9e& zMfE8KD>ig$o{;Rar!+1p9A#ER|;4x2G?G z*j}GRuJA=dS7KX>O9!aLWyaJA1@u)jSsz_Pc-P3x=w){@H}gHs9veg$*HKNsWovdp zz!gV!ph?VA$4m!|+@Z96MQJ3Y<( zKIy(|_b~ZuKCf?IbJw=)iL0jS{#*^%jy$eG!kZw&yt-Va`7A|$`X1o_?X7n)zlFv| zzkLmb_gKGs@4Zh**YC9%+j(nw){+0HYzfSjp}1H+dPv+H?%{d+pD%bNyst%UV(z<@ zTKY@BCGNU1JNzk!9w?A5d;F^06^6dPt+8t|MDN_!`xBjB>K830ZT@=?NP^biNbF$V zbFUt_q^&xOgEy8BNG9$u9lDZwr)|5&+p1p?J>{OYVN>h*bqaK92X`rj*qYkBcVcd6 zF}iyKU!`Pzm$N~gLMy|EHu5@EI+YZwpH$Ddb2=P3cK1PtLpGF#ts^&TN7lu%-OOqC#@$wt zm+GdF+RGmfe+1YlN9v91Wx8{DMx{HqI*R);jTR>L>3?tN(`wz&X1|$Die6c#$!_b? zYenb!H2&p=MtnDjfvs7Wg<#A6@Id?h+)5iK9(1kEYyYN`<^R{CQX=qpLh^{QI?WJYvDaXfi{T5TeIj(mODJ8#-Z(pYiMrhWGi(;m0} ztgx9dJVQ5;zC_cGX(9~=-fbdfH_^?a?da;g;v%deHBD@+V*#GEe{0#F(LVa3)4Y-M zxWeiQ(5FUi=OJ~hzW)?ny5W_#KSBka%N*f7xZH$P$G6~IqU9W#QUbrdg|Qo$xKR;r zDVS4j^~E&ZXsD&%>CSJaQWtycPn~+u-A-mVeBGYsEfJbF&4Z_}dH4PpL>!BwmOYK& z)!{nM#LQ)FGd)sY24O>5=k2g<>GSB4#$ux$WpG2VTLd<24B`+zq=4qUCPa z^X#zn+2Vc(30gt_=y%R$q0T3q-jMg7z=zcXoWl2=9vRx(jlJL{G-Nb~A4q~cjvb<* znck0L0MVf$;UEJdir5g%kPI=H#t>m3BZ366m83&aWLBC*8f1h=97x(eZfM0Pqk)QW zXM~*$13BFmLmb*qVGOMw{OE*8Tc!d*4^6)XpwZOWJx|h#6 zFkvCA0HQ*HxKZ0Okv~u8wr~9lXOICzY2R8XH~tX%0{HZz^kcIpUONp{l2G zVVRneyH{K}Y!(IxQAQqBMfwarBN9WfJe$^@_>K9Q@PI zb`!&C*6J2snMfJR7JDNsO?b-!gyn2LEfWBVfbG)kIxS6b%Om(Amom8ZVLmcPP`Vvf zV>X}E2oQnfSM3h!184VpxeK`k`9smPloAWje2U=K6IgzZU^a>j84Uq!h2w%qnG=|; zvPQr^@3v)HV7AOa0?tsj%xL`qc{9~Aqf$_|O#LxIF;614%Om_t;4EJWe0=;ar?*eb z?;(rI5BOrP!EU{!^J|_ZOo_9>Eu8MVwe@Ar(V4Ni;I=S#Ikt(iXQ@pv+~B*#JDi_| z7(0y$EafzWvx>S`YT1RUJDn=aZQ4UwrS4a{j8S%`G_6ZDg)`HgsBIc1niWM8tEQig zDq2lfze!4cqPt&J{ri8r)_LK4uS{&|K4W~lQFGp7lGM>Tx+^jsob95wbtc!ddprJV z<%|*6wz(Z`c;~gg`D0#_yX*QxFP}-6O3lt;yLV=!I86Eewdp2rTZMh`y3FRYr%L2^ zt-Mt{Ng!(jjDT#_+0EAfgcDN8?+cibQ&V3mZ4HcKZB`&ou8qC98O2cZxt9oDK<~bEp)jme~Fn6m5Fh z`O&Uxr`&W^wL9uVzr;VlP3HL)scWWf`%V$F`acZSg1(+SsHTm*jwa#v3~vIGoz{*@ zf9w|8?DixU+Pq|mZv4ziI4@Up<38wWt3CRxRwKohnL170&Rmg=(te`MTd~z1bLVSb zbYuHDs?pXasI4&7YeSEjXE8Vq4Ely0iY?=Gg&c#hC% zWRm)REF1|~&IMB-$k~*Kb!@PUJQw{kzHGlRTTtfTz=_9J)+eu3CKRQ+6lrH>;3eFR z6e!CVkIsUP)q873dz+VaD*Re1Ni{ILKq5hboz%0r0`RVXpQee&L;#>0Pyy;VH?49rS4za;5yhuGD0yso8xkM)bq zq7>kCLnFS?1{>P>dR>^)dbkks$2G083aY!%8Wm;REdi}zy$Ck}s*6bP2((vW{Cz!~ zdC5MFV<||JKHTjuc|zVE#wGXJYwDr~PqE5U{W? z|H$`DtQ`LVPU!!s1*RV~(EnVC|Cc}^R(AHE_4_vkpLeTvd))Ev5!I&|m}aEc9XJ}) zh4r@qfg{M%(FYm0;UWZ-v-l1wntTs*@B{J($CrtrTl->|o9#Q@VwB}c*A z>`kua2c?e0OJHURz`j1i_h5h2`)@DLM{d@yXMZ}5-3iNtWM@G0H|T9>eKOX43hc15 zsf>C!uX?}c$fKY}-?{>SLbJKY-SbaJGI3){k2p|n>R!rE} z!GDSO9GYjed;-`4dRYUThz|F=V-IdlKR6+T>VQZf30&O-t44<^K#-RCfL9Jk!fq4@ zU^a~KCF%W77R)mQsGBxC5(M{pQt7X z={YcsoH@@BLNo^4xVQ{J1yYxhd}6?Uh7i$Cl>UU(eb&Jr`F?^ek$P6-N4_`sN4}Sj z(P;sQ&H>XxL<0pVvPFqfL@JO}3yxh>#=677`TKZsDDw3Zf-m-i6M7)>anyt6+JxkV zVke{?=Ye1y^^#-|(ojGux6R--6obUF=f*;`8h`;g2m>Y+`(-uA8bbMY#P!lyeyhwN zf&c-HrXd4z8iHMtR?aWd4Dr9q_tTmX@+3x!K zKJ?$w9epEi^nKa6V?}1HTr+dcoEmfB^AKPgkiQ1HAISk}8ikFB1{Z9R7`O*gq=cY? zCh1#PC+GzNLJBWH49;rEBuWzJBl?b29zy^31U}4f0)PHDfggERSx{jU7;uLKDo0-= z$Qox11!?KSSZvG)c~X>$dN@I!7sHfg5IArc_M5>+i^oFk9maepE0bb+aW!*-zKWO* znCr-@vQTGDTPnAhiCWm+(ms^G3yrjA{E*pw`0KGonsXA7g;F(1x?o$|T_FMuDC479 zKW0g)R83*>Dck9M4CBsr0;q0O}wQHlc(2F`o+j7^7aXQ(6{2`+`QbNc?GC%AbyK2NMH?N&HQ$Y@Y~B6J+FkZW4$VEpx>j`@Z^7iR!-zrS9; zoOCKT=%>^pd0`fO!T1aF6hf-Ea#3I4qp?5x!hEFsqgSl`aG|JlXgwSOg*rNJ7rW}I z{&sLc{ZU5M6m_N9k_8xcNpPJ`7G^Wy%wpQS`F#4?<|c7!r>N1_%uW zXN)L$D4Uowo&f?F4?_Y(uyn&BaYGtRC^dgZ!?|h4t9U%F+=T+@UwL21GiUBdJf1 zWe}C5mr@1}{#V@=aqSbQ0suNOOfm+|wn(?)peKfa$RO{CsbCHPlnQ_$=ScEk(#)tAZiKyk_MSBfzMiMZg8NLgtK;q!fT-BoYzfRj%}ei? ziVOY=fq54Y@gt>TF3DK%kKlYpk&x!i1!j373gVkg8$cf-$;xCX+UY?y&p;sg^N4_i z7a-2URQInCHdw!+hmSAM#Y#)CA@v8;3QPr|z? z3beJzQa6mk|0|>xJxgH?rVg;}m?{pEY(Bu#LVQfk1z^~NBU3iUKG2tsuw4iXik6|k zQYH|9sWLkDS{ZGeTiJ$L&^}U!rAsds97*DgLYj20p%5L#Sffs0V#}~b7|Pta=8A~H zUB(GWxQ0p=ICB}N)`n4$RH5o>6ohy%;Sb%`-tA5|Ti&SbH47ohN)oC#OCAI0A-+Xm zJ_Y;`Fl?=yH({#YAwV4UMvVxuwvN%0Vts2N`p;Gc5CO~?-jNrUPT4lqe6z0^5SE@Ul#fiV7Rs%(>M>Vr~bS_b=KIvYG> z`G&((8HcZ3)e~&r=9wc4x6Fqap~5Fhti_|d40r*a)g#YWUMwo7n~Ql0h;f7tly-

!dpVAmoaj1ARsaj*$+01g-_yQRQaeyfBq&w?TWFvYJwmxHJL zia`hS6a)~3CiSHs@Z?)BP=QB$SOt~pQQl}8tb7ViaU$}@1(lTYSWSK+oS`}u%h-lt zP8Vu8A*{cA6rr!CJ%Y<89(|dLmuWa|?7DYPwb0+ww85J`-Ir~x*{nmIFjH|pAcbg}dFdab zV2#2Ej~^6tCF1$u>fuomMJZ;6UJ44y-vhGbxQYz3(nxZuCda&VMM@S)Cf36@MJ=yn zVTL1%_uV4Wboz41G*+T{>F2CVgejC)x@L=T(|~0m1!Vn6#w%Po?5u=T{$laJ5fyJ? zDJGXN57{(rF?>>0(~P4$tEerzZWwgM55Nrk^uUy$enT<{vc5tU-P^mrAk*9@wlhkhS*d|$BscDsLDDBYyAV#O}aRhE9{(KlcqXb?~VwgQFWztEp{Ew9jCv-RPQ`A4F2VI?Yk#l0W3$=n-zuc zV)5+bdx^sJI5j{uax+fOQ5wDwViCd(Tl%az*~=bf%Fd?PNciN1d^UeZ}e@Cqxn z!R)uKUgI9N?}$G*(HFx=jj-mtx1~GgcH^kdO(1q~s@E)R-%@tys5m(2IucAeAgnNE z!lvzzkL>D;)pvrRrKP0pXh)+DaqsY3M5gx1i9>X{j&+A`b1|qaTO7>6y@r4vEu8Ft zaUI(JQWb%KdFUobeFZ!fwAzB zlhxUD{7s&Rq8Z<1Z*=}yc+So4l!EyzJtvK#4O?bny@ZQq?P^0HY4dxdd_mLb=6pe` zYdqT0I#P^Ffz}pWTHh;${#UeSJ(UgBrq+CU_v}B-ZS{CIXuK?`Yzo}H)tM6}rip9h zr;c0CFsE3^p%JXKD-RRic%%8rOrZCbfN0G$@`YPsQ*h+`kIh zX&Je>>R?GrxBqFru(MUU7Eah_zZpr7eZY}-2_*ojbIFpvN-1TBCb>gPI>k+j5{CM8 zJt?VXOs%Uqy+)v~`Wr(`S462zNzc?JS>^cq9sJ~_1$l3Id>(ZXYzL<+R?(o&K<$eL zZZaO;Nx0Ak?E_#J|8+XWIXum&tfIkxDQ{PvP4^}_npvl2)1?ZBi^gCedI%f4i$okY zS%wPh@E9s%2Apd%CG)nNd2i?YSJUs4hDE1BkG0AnV3Au34QUDAE_G+u*t5x^rT*L| za&CI(e5fm=6cggcm1}jyd?l(0h-nkvBx9+;Zu`D*HM65K`sL61QWB6k_QCRm0u*{UmM;%FpV;w=NBUIobO&78nk!TZ8Zkcz!fdI5 z$D`TiFToB}O?Dtg1Gmtz|Xv38nU1dms8pB`beDQXtza>{((N zUUT3+kSD*F1|C?dP5qilb&~6=4O^48&&QM>&#wY{8%|F3b{iT%upge;J{CXOMv@od zl~xh3sRvPK(=+Z;(=6d#yD<@5CmQp2T_UE-^cC|PPielNGliC~F zy>+e19tY6Ft7<*7A(B1!pY2X`-fz6vu^r|n?Q2fUPB`)uRJZ7a{Hth{{ZyMH+FD~? zl$$LZRxbLD-T9GrhV=}aXz)rNHSpoVDRNoRbx$Mw&=*-mB$@@Ae|*jIt!XR@))Ry|s-Zu?V~ zl=14dm6N3Dl+Z<|x0MLD*cK)$nRF+kmpx>(*Z$yT4uEa08C z{+Yv^afm0Xs``}G)iTBf)5bYw*X@&$Nlw??pk46%8V` z%dDP@d<~~Tr?%{`S_6cPbv+X{+f)f}&^G43R=x>)qGvmtrh-d{ck6oR?1Ib6D(@$d z9THZjWN2H*{+2~|d7$+pquzmrVI%6!uIUUFynbDMr)X@D+A!w^hGH$R&_|}?Jc2K( zVCPmAgiEs-mir9zPj-jbsVH`!RiXD0N1F55c;C=R9D*DXw--B@hF*-J$spt_rU^#f z$*MuM8}ZqzgW>Q~YxFFlnUFmNdF-~RQ-h#g+8y-E+)TL zpX-iDuNsit`A>qPH&1fzgSi4ZAJ-*D{d)p;BTNd{O801?>Jxv#+6^}Y+2xlN@pboH z%w7jJYzlBTnoZB~^U#UlQpQy*iaOp;(3oT!Dq3@$@X5caIrJLd;W{Iyu!ZK6dt3Vo zZbfR>M(>Ebw$9pIVJiQo!+ZB=M9$2JY|_t%W~R&lR<6#Ay;D%N&Ccq*O?MOD-(Twj z7N^b7(qOA~nr}4KYOY2}(P;`AT;%ERPUBQjI={g9;t@p{hb zaoV*=@j7|-DJyAEvQ(4$kd-p}Q#~StZ&L;c7F@ZO-NxF$A{wJ8S8K%yJ7)o-sj=Xs ziFMUrP(%3T6yv%_=Dq8PmA|$|C#&;*hgGv*nb7P|H*X2j{h-rp;SYqQNx5K)rQaby zZ-J$4(7f#&wBWjE>}{p7bz;#SLm^rN?Ks%&B1KBP(ldd>WC?CM(6UjHVc+)be0?O} z#fKcROgk^!*plbG47g#^c@BNWSW`U0yMCPEfa_0zfydkrp^xAd(xQ|0J6E^hJnbUo zMGab{y%2$#4unsdMM$qL!0RIIA`~AeA~qfRuTK%Zb5T3lJTU5RVE%i}uYX<56a4S|#G7WW;dyD8bBxWNGWs@z2|B*XI1w0#)m;OQuWpW{0hfV&SGs;9? z4u2fGIt}*+F*`SbvleY_nQYj4Q#@XDO834CEypdoom#25+n0^3^NNEPcE!YAFSDuz z^}UcE?9e{Az-`gNJq$zslXGb*0s99D`fS4fLGB9j(dzeOLFEd<#i162@0NGsE(!}o z)qm)ln^OmD2+gtfTgJZ z$^9>7Ahg?KHk+LH26D6gk-lbP+xbr#Ya68-wp?(}fa-A_56{+$)2Y?5*2~$;_bbv? zD&n72H8(#f$~8?x*0PV0Zm%5gjyz?75gePAqWK;rY@Opl<_)*^_VC8;`5q?hx8j@e zGOG{98Qifl4i)Y^)>4OEo1(9qKE6C`dNqxE9P2*k^`{QNCZD0-pLToOK5MlLJY(Qc zmC&d7o~O3-p4B`zcAChgdwwLt1icpS)92P6$$jJ+SVWW;N6WR zm(H@Fj@Fk;_)o+pP?aWR-yeA4vAC@NO|sN?(aGz&@4Vz z7U=V-T>{d0;h3V55A)R`Ph)~a7kT7xr;muDVvKhCty%isjT_N>S<2#BveR!7{oCoZQSZGlcCDy9_d(@7v{K-VgmJOj=ZeKvLNo_NiY%&n`n*Fh&RjQGNf< z*9#xqL!6$k_xpF8;p?4WuD9P{S%BOKJKt}fpyf7B0;VU;b#S%J;bdc6U{gWP^T1R8 zkhuCtR$cB$T7$xWEd5|bu#2_mSeBB(+XJZaP#Q}gVN5YnLZTh*dzOMKn@n~?{mKp> zm40?P{|SOy-Ac%h2h*a(G?$t`9Hx{LYjn7HUl9~Wk~HAP!7y55h8uP$KN>GLXsb*D z^RQwL+jlT!LH&0RHA|3>!p^}s%lE})`%#=x3s%LCD4p^KrQaAfmrcKbX=2Q(%ROlK z{k8?zg9$e@O@n2W1^_v+Fwg}u_pP0glzTwZ_uZfrA1>$531KP6=bawF2~uX63oJIM z%|$~2=ys8hMA}j^(}CqQmlO9p*8lYD;l@P!ep>#)2tl3+6@3kBNZj8E9Z$#@j{=H0 ztdO*rL=?+C0SQ|-oq<&%0*_i9{==gZgiTKj3oQE2gfd}?Tc!_BDSQ>gDKA?v@|L+w zU`X5`G$TRk`U<>aMG4hRH+3A0d)j=LBO{WG!^-SILKO60+#cx!;ll&-e!H;hg@h!O zDpqruB?603>5Tx;uhh z`_gGbXraqDPWivF(X-Ms$|WaMr{h!1qrWu0Wgf#~BSGG#g{q!ohL6m3Zm}S|i^1y2 zs3=T}Cp`*&Ot7W?%vToQFNUN|5>vv%|LW6YW0;Fv7#r8tHM$>jr{|S;x(vrmxfc68 z5&~wKNwyAfo2aIgZOp_nbxcdw^-bnG&@y$hog4-;pxW>n^Jtk*&4>ni2f`=o>g3k9 zA5u7|kGM_%0!L>GNS!i-6mws#;-|@ih7C=op`s2aixwP)rq0NcB6gfHmj zeuJfd+1CO&h%!dh}z6%Pcz@QNSQNLw=3&2UIi6;1?AEvw$2tqDtwFf24A-gBDbJ2dg zLJm+%AQ=M_3Q7Z24_E?#g+Jgy(f`>Fu^7f-mWih!$!FnxtR@HA_2>b30nZkiH?!x^ zZm3_mk^090v=6KR`e6o@y#aVukVzR$Ge;*vkOQv<2Q34vYf;}xy%8&2Uw}>>9QU=K z3EBn_mb4bCW`;TFvG);f$(l+p&(Tkw1B3HLH*W+Gix_Nbv}Fi!Zpl1dmtE5xoKiXs z)Jn&63}4f|8=Dw9zhQnRP@Nczc(1QW28lYpPYSb59F#vOy}TrQxsx2a+_QN~EUQ>K zAJfl@&sN#57_>C0!k7|*5EJzxFB(23X{!0iyuobBv)Pk-aGfh2qY&D_LPSkSFoySc zjh=K1O81V%v!cnj1Q)Yem#JIO9!nuAQF&R-mby(_%-@hi0P_6&?4O{Q8N_sjco?8# z{GhuL_rRH8TxeJlSmr=WRVs*I{u8309jKAMx0BPkDJU?+hXw;E`*GkzhX!ZEy8gss zHlNH%1`c*^rVY#H0sJ7w%ecNtC19C(&aCIc1O0S}4*w=|lGq9u5_b(u6N3nAlkg75 zMcn9y5rBbH)uQp#YZ(@jvBDW3FDXZ8s~0hj6Ym|)q)Y>e@DK##OT)eSgUWFdH;3To zw#Zv3fb!`3+UFCC#=w~oN%G|T#KYGsA$<6m;r-LQX8|(`12p>rZW4+7WLbtgk!OjBn!jHo5f?wPNr40R#;upY)>(0#8Yv)Oz zL4|ikG9`IqnKi}2c?#&d7DL9s)r5`~)kJ!zUgGej0z@FBeHw4|dJjN$EDcW{22aaw zeq7vNu0QyAekP%#?*`%VbaV51e&YN-kInFVe(q;~z9v^^{l0qr;Ch>S!$t_2eecgI z!e)KpDgUJlfStKvljeUtKH?a&V%=X^ynf~la?SW+@cw+AALiwr{`ly^jvvIwWChA$ z;9M{Fw7Kl>%&Ac89FzC6r=3U~6|4^4GgG@=8nCeBdO@-^(}zDt6`F){%lAWdEV9Pf zt_@`CX#@j4F5DLyMM+u9A-R}XZZBuK+y31C*_5$t8^+Vt6#MpFp}Yi_AGMijOenn&uqEap!oo05rcW27iM z7V`x~KMt6t9}Zo@TcK%f#1m&CUbqUoXsyrIwKss&%v)jCAB6&w|1ngQ5DTxEa99r( zA*tdLgbJY>dqG*|p{q}#FQsoe>VuIoje0tYvPe%+Pog6 z@}c>Mh7OA9g{|hhQZen1gXS~EfDTb@t*9peVub1s^vpcHJ1^QfCZgvC!M##(CfETg zHJVd<-;v>-RaFm4RI6aFb&QclZ}%2sHA}{HD9J|XlmA_JO$&~0k40XUl~Q9!?G|9W zhGJp6jaU6@=UAc#O0C@4eaX4Bl)KX5&e#&kel};uP`4?Vl|x4%S6;E|T8O%^Nw)uK z2>XU0A>mBnw)N*KNn&bEhbAUnL6KF=q^dIeZHudYVJ7lZ+ZsNnMlNv+xLSA#6 zss#G8FXaIpUu`3i3k8>A<4kNW6>MEXrJ`oduxY3}!Am_}DhsyaTw)xG#8}9m5>yk~ z(52XtfmD9omF_CG_x$3gTL8ru2K_$?BC!B> zt~-1l;4+4g&G94Z^8CT+MA%CsvT^4-cqC=Op}77gJY@GElh5Pg+lr!W^Fc*e?92M$ zU~ke-`pR)H!a?}!BprpeBs9xHh_Km~_;w^O&<-yc#b9sEZQvBHnySe&A1Ea(c2rPo zC_(5vDD!F_=M9?uNOA!s4c9j!y!Xh@p6RtTM4n-h>>x1x&5ScEJiCT*pIl@$qZUzI% z+QKRj77WWmCH!`lJ0L9aVsX5pzli8u_WjYvu5+Vz84>{KD{c7Rk71dZZ(sM&A9$s4 zaN@wRbMoFxkYJ(Gl+6hv{m!CpOG5r=CMY9%ek#oVg*~TD%F~a zOjbBK43RyE#ty#`*t1VoWTMiP$Qz`44meJK`&cDgR+7iZ_m>pSXj&wWr{{lyI?#xKuSACm*?&?l?Gi&rPe z35%5DWwMov?+`cyi0?2A_y&pe0*7?U;ogfd>vW4IjpM*y_((nwHb_ZdwBPXX+o@kH zXw>Pl7w+xlsTgh3N88&e=cNX^+}|My{omM_Ea8$Z3Nd(@5D6%i@zQbfT3JoGSD!=G zBdQ{4zc3EbdHLo?XR9(in#*{YASojxZyLj{A*R#pP2eMm>yej{@!<8J1lj2d?U$HZ z=xZwu(aDP*`CD;hk~GPemZKuI@?xEJ!Orh#{`ZJ2GRGU?>sp@?a~>#s4H$Zpv49JwOM5Bqr8p2Y^0`vZS7|7^@JGZ(6eL@tWP2TXj9aqz|Y4nLnb z59xkdlI^yl2dAHR&d=WY?-uTZg3M+=ondj#3s+v{!`_Y$b%mF$-W^2kS(RItGly$E zJ9yvPz#CxDSrz-5a<+OXTcOUgU}q1(cgS|@at9u4heSggLHEPDZpAS6 z+eixc?E~3z^_=a<43eVX8ueM=yYFZ=k|By9p8SG{r6xwNQ?FQ~_;Fw8W8qSMjgkvX zdr3V;cSBIFVfi&wqK@QOt6gqXu1=+ZdR$hVrlZXxdu=J0w9C1*f{h);Z|iHY`=_UZ z$c{RzDr7c-TG^*b#R7XRwfEx6%2kyS7dy4U9PKh5^uodI-;c0QMxBSLCdnz;s0rmw z?#0dt=aqM|s6&n6sP^SfNdq>?kkk0P!d{_m#UpVo2wi05zl!GC4YlcB$E%immCJG!p0sHd@mr zPSxAGF|iz@8AW1F*km=0KgBA_bNR;@)P*>T3h}g+;*2Pf(l8Gl+@_OJYp#H=IV=WR zB_t*a9^XDH!^^QXgyYlieK@E>t0sbOOPo8E&D+LhIu0z$_^N4AifuP@JR6I&bJPMj`LlXPFpmO>A!-cDUKyS>|x$&Z!;~_k+_Wo+HeqUr>2`4xd zT6?M0Z&U7Wo6v!~n@HNqn_G>vkT$K?3vbrbLM(SvFIv*kL1gG6uKL*s{JXoC`Owzc zq-5^_zIai|^{a{c8^>jc4(LK**|Q=bN%1UNN(*^fJWYJf!lWKGiMky@-BKbSE}}dF z=|9ui6x$Myc4d0lOB*t+a_DsIu}sb_2h+RRZk>X_MX78(JRG=d7tX5ZTqFn=_2r_aCSEI#| z2yrJyzg61G;mLthqdL7B=(LFLs*NN(A6u=8QaK9WblNZ+r7M5SXv`jwoA)auqL9j~ zgri<7&obAkc3^q(JzK2Hd_bvKX^1H5QUIwaG<7i4MZ*%OJZAaC8*c+K*XO;Kz7ya_ zhdoz3jIg|c;HmmvPT0X6H%#|Z05}qV_}!v3=wU}4nO!Q@V?!f10!Q^e%od4CK0Gtu zxppOQ!e@I!r4OO+lQ3|pG4;h%NsvHxzmfYmM~zKCj6EC>GMv*i>RJjg*FcgIFOHKd z3D|L4W;E4N3E(L6YKS%qMAo#X0M;B3ajA3xReryZa=~JTY2$s`OhEe7(WTNgtS|z~ z-gdkgU}k_qw$LZQwB6-&QbaT~(btA1Se7m+#IM7FCj!VEZu6jAIOcCE$`C~yiDr&q zY?>g90|3i>qyeF+(+oPp88Bv=KrIRS>;GVISo#%!3J`Ni2qOqLk}Ywq<6x0snvc&K zj;G(5-wuPA&GtEl!v!Cs;=oR_bQ%}B@qR1j z%wj~H$1K^(gRiu!%SCe6E9IE|y?kG?yTYvg%EZSmKXo`GbARq_67;pr@MgFAIn+Md zTXNDp?PB{nd64O~eYMd`=eU$`z6`3aS_1H7Rbt)Qy_F%F?rov66GYn>!XAHZeH1AWv)ScL zx-dMmp+(M+3Bakt_-&F2T(@VFL@=HLu7XTy6h!T$c;HYAHNpekSzt8QFK!bjC# zK-lV-Ik=z4f1SzYaTgE1IE>5$@9z3EWq!TPanAs5#ZEO7pxiAj5;?U6UWG@enR%#P zEqiry-EkRS#IJ*1sGH1~HLCd@^W=SW2o|?}nG@UYYjtgcTf-3vIbYQm9W*-qHdnd$ zjtSU?j3eXjF~q#Hsxgpt|9rl6=Y6{KMB&bRFUPihSPJ9VW0!~Ka;)(-tPgcyWT?*p z4-DkfG_=Qcz8dqMQOjM0t-c(O=8b=yKA6_HtOtwOT;gmNTjF%4^*&ZyI^#c-xp5Oe z8dL%75iux6UZhm-s4Kao+I_gxLiCwr!`v}17)H*b`W=1x(V(idW&f+HjJ?2Rg#-1K z74Y~UQ(-4!i@1x-F>lO=@FilaPRO=V413|QDeY=y2K=uo3D6H-24fD}{~EAo`(LW* z)#VH=O^lqO=#*RxojvSL@aZIM49rZR=+rEXoz3xC*qNZ{#7!*B%$@O>S)k~I?5yn^ zmFx|SeoquOakVfqQ4|w|q7$@mc9J)76tc6ix3e{|b;f6hqWeFFp*fg;Bk=#F)IDe0 zPDKQL8umP&XTU-HOk~v&noPoa} zK9C#~6qGNr1fqNy&7`+Co6O8)OV2DXw-rTu7Ib~v?0G7a<)` zVf1LYR;NpKDOROQH6}Wm+javdwhUp{@26V_9v)t=)A@Xs@29$HHi=A*m5nV^mT`!L<(w`qx@7Mh>SXfvl)2X?<*T(fGvm#GuG-`II6KNhEy}#}a$^vF) zr*8n|Zyipj@()U7vN#-$#+$7+?Y$IArR5C!m+Dhf`|0eT53_tur!y%4H``rpmLCNL z1;F5NdmGYExrTew22md@qeFZ1MPje$i_}}|6Dd?3eK*XktSjptR#sI@-unX~D%eJ7 z;@7!;-yR#gU2b(V1vs&gBYl1T$9g_KJ|ehp$7y=u$Fw?~%sb67$iLh=H!LQPFE6Le zUWYMkrE+;Qpx3!0!Hviw`G@KG{K&rBjtBTT)@nm?VmX9Hy}#e@?YZsx zu>EbaSS%%xO5KBqO&(6ByZnB9Ylh+=uXnmXCaCHVn*Hv_$)vBQ*>;WJSZ%i; zjxFuK-d&<8U*6cZjwu55{Jvc5?d_ME8^~cgU9UENQu?3XpDthittZV^6%-Immy7os z93(^oWROZapU&jmnw~Aq!J&mjVYAuhXFViKMut+;2N&z0#wqMgWiSm#-AW^TY5u--XCsx! z^sd)i9d9=EQwCRe|JIv^b2g4A)8;8mJSzCh5a`)&&*qB)eN;g;PA`_Lnzqbgeo0hj za-?rx?+)dopieUWkA7cP6<`>AYC(wK?eN9-{Yq3u70dx+mpvefHhhvOVz22`>+)#d=uQpXa zGI+dPt+PUJUr(m9TIO)UYcV}_ecw#Ff&vcUoTfY^LBj$$#)beA#h)AU3e*f^_O4oO zHVf~i=WTVweq1ffkvJ5T;n07nc|$$}Dkqu%8>rMSrnfHT&`~SDNcNVxp7)E980=>= z8QGZNz;zrRPv$B>TBE5HoYO}A{=kE)tgI}^RTr6XXrAA#-l7Ky?RK-H(}G;+gG-!M zm9pXO{q?l!>~*^*&;|U^@vv@|cg!6kfW3Lth7SP~e&&d^Cni`XnmM9PtKFW@Cy|u0 zbU1wUty-(?@p7$GjG_l-m!N`?C^uNG(FCO^)y4^!NLQ$Vfq_Ae+a;TxX@rev>KL#7 zB|pmhalZSBpl&%?R7 zv{Wox*ccT+nTc3JP7jfjNg422@n)ElN~>czixtVvLJj{#4;>nWx>iD6DVxKII)UqC z5SuAw-gdLq0nD(a-DZnpt!x3Vd6bssiQBWuGZ<*=`{sYmTF6gGWd*{TU#3k2B!rB z&f(?4`n!nZEEZHPmdRb3%E`fh*(Zr9@{od&@i|3suvlb>Y)j@SdYzCas3>A3=@Y7K zVl+#X-9DY>`O$vsD@W+)d7hKJ!XG;lLSU~TmG&zZ&v=_iPb;C*>WM2ZXZxOIa7C8~ zc7Tv#l1z+kKEpc3k{Gbt7E(ZMqD*N;1IFU<(P|a*Ap(rG>a{Q=C_&OeB!gnf3#ku< zTy-*bPC<0es#i=yF=qc_wd*vRz<3Ww6|_7=t1(wnoF!;9;7~2o(V>9S^dSTqlX)nu zkSPK1eswHHasf%7fw%0>8Si#wEd(Yt2?KxPAW%+IyGvsOL~}RLKyeF1IN-|Uzp9a+W6g1Pgn)cM?R z0XS(OB}<@u7}kR@&Z)H%gUYwPDWWa0A4NBi*B+@3d2FCR+*ucGB~QOV*y(s8Fj(g~&35s@KwCqk2Fd6Y5hH z!qw*SL6^x%n~2T=l}Cm;d1XRRGp2c?GyLqvj^0`^|h4eY9GwF-yCYcU(fq z+R17gC?BPL`=1g?q|`w>L;TB;@}valts2s7OSvNz{#;700;R6B9EwnfjrCO%7G7{L(X{@L%R9iBk1M=!%S=~vd=!WNpUu;6qhHFK7Q zjdCCb;KcscAdbUFL^!tV{c^o?Fi0?Yw1>aJr#*-9hq{S`H?qv_w0{#!RCb~b|AU}h z8Yh=rk`h~%TawTOJxfpSYs7LDi)1!wTMssd#ERFjUkc-E&4g~!oX!Cf^CP>L$Ee8U zaFv|Vlmt$IzLLi|5z-LJLN#U&oC;eqGId{7Nlr{1nejx%yHw5dPF40FJEuHx1WB{4 ztt|={H4r6iE@Jr9e12RT<1qs91CmSP^etKAi9NyC*wIoVw&hkpKtTdAHG?V>D8wQX zd4S5f+}Ear#kq2O0jcd=T&!v-l#06hxIyxB6RqxFjFj1`G)in*M!5Z2L*-aaspAFM zoFeFPrF>vuU{`Al!$#LaBUu@XmTqLpp9;!6cK=rNL*dB!2vcB?^f_Dc#uXC?on4!iffZ zLb{N$dWm8RjPJiF+Sdt9XpM^S;le~VD&z|DP|RqS-yO*BiHiGn=#aYkTJ3SZtpy72 zilr)$1~m!w>Y|+oFp?VNa{4_s24Vfnqg!jtUthIxA>*iyFDFsE!47C4mVGamIozlH zubA9`{^9+)>xMcvj&cWzD<~o1i-10;MABlIxrvWHfMkxePCCgIP~Lce>B@IR8=jU3 z^Lha?arPb&m5O%x6wy#N=7TfY7ATwhC*x)*8AM`A-opd@h$e+{lEV0pzQ}z_%$ys4wMg8=NKHirBpDngJ$;qKR0Rco8xNO_&}p<}#dZgDqbYP+96(Gg--s){1pHWB|9Snq!5Nl$Y&+Ux5FYT|& zA)q)Seykcx+NF-#SKw%d-o;ws&^j#p8;1hO;LjbXg0l&cgRTe*uVByNDdKaRk;QFt z$04f4iwK8+EA}IckoX5pe(fMk_7adWG@B_cQ&5Z$&*ajxUx^B?Ev0y~2Z_f>2x3ZK z{MQBA+LQAY^!HE|lcAS%+_kZ>fl@A%yyZWo$9rCA3P}?tt!^Cfs=$V2wO;F+TY!Fp zgGuXVN8@BkAxdVWxEPrcGO!LTP%3IrDGMnQi<=N`nm`b03=++6)ttsqnTi@!kP~5# zLZfMHy5uYfa3_!Gi#w}=YkXscq5z&oP>3PK83JyEs@y1fhFok0E`RP?tq?>l71@Xg(l2#-IFae5|E*5qkS2Bpmp1`K zk}R8TY;cW)Z48#=AQA=e7&BCUyeS5dY$vTC_@`WK42Pp7qxSK9)l>~gdTVW6g;;xE zg@ZEg>7o{Bqovjei9|*^=296_4OwJw8E>Oa1CfJ4W^O=B-OLVYq#`Uxq)wqezp)XS z0=>*Vq1*n4K%$ZZdIBMJI7w{mAJtsru@QkYKJm$Xp`$B;mqHXtJ%FM-`?T{bJCKUcGV_%Msm{n6mP$LVbXr*krqYDU$3|BT1{hR;mmg|9%ZU zs68$&;mmxBDaJV7k}sr6CdNfM z5LF&tCHC1484Epvt2M}X6#_WTIR4ekE2}zv8;d%MZk3VT%NhL}`C^R6D=l)$JtD1` z>Ttc%{0HM7z+0fVd$*?t!MbvOY^4p{sIaIIqr7ynq)!2Y;sMV9mW+~HL^M4;4KbEobSdh}K!Rz+ARx8K zi>#9rp>`GPZ-{<@(ZzUibds=4>Q{rVfqIm&rF`m0Y9g0`9X&nwrnH(dY=hbdCN0JI z&|1TG{E?TXFwTXgyZc(~K^VDXab7{njF3k=O-ivO7}K-$7{a^4QPFhxZM6IaIl*q@ zdvs$O5hax}V=|P0+BktB({g^RC4Ex+fbT)i-Ab`e$njE>v4(gK=s!YpNVDXLa&_2X zzQNa+XJHwJ(t_{9I|Zu|5@a$A+$6SKOGnEg>q^vUTk3SQ53GpvI;+$X42-0{rT++) zTxgm_JJF7NY{}EhOKCOAxA_#?*O(taG{Q$hLMYrFhoChtJY?k7T}axzh~L&JQ^kZG zwz+5R=r;nF;6+Mh7Y!sgB+8jUc*YC?*3{d}#|=21&KmWgUtGX5I~fj*te zNbgq#MomBL>+3r#J?jM$RIQe|vy;r`a;v>S@63fTXR^Ims?hKZYYFTz}iZ{91J!SfLIHF!InTx4X(dy`K&CgEO@xMxcJPh zz&u>$0uYFm1(=K5g5MI%&1J<2<>%u8e!zSXh#9vvw>4A%Y6<0n@IH z{-K@!0p0lDW@`9(0V|__$kd3eC4cX5`}suZ?}?Gfafb-PaLsY1#YVzAhWk?Mw$Bf) z&*8^Pol(o8OP7f+FP}Nx|M7l-IQNtzuC6yeGrnBol>Z3>?)eFvE*61n(ZbcWyHQB- z{6|8OPw@WIUj7r37?agVQC52?jwEKtpQj<05P?S$=ate(C6uAKNfXPrl$mHWi3Vz* zgV2}Y>sqttLzfWW`wLc)$K4PL*B(7{^}#=}plAFaQDTu4u9*o#cQI}if^P?+=1>_k z$qtx2!7OK?;`B~n(ojtULO4U8Uu+6B3 zZCV1^p`WgmmjlR7I1DXGXk{Y)Ak#1k$cwdE!~1&seO$p5SUJ z?1}4*GzrYDay|LfFboQY8NLar>9_+1k{J_hoa2T9%eLz5YyF6V;o}3IeL5ZF28H>4 z)HI4{SUmGsL9tee~EPwc}{)NoSD&yf&E zqP58N>ASE!Bbq=4_h!lFh5i22?XgP+tNJ;P)UWYt1cKsjIg#0Y#ieX<+W zt=V8B_c)3x)L%<#?p7dhe)d82kdb62wWpb&caAnVq8}vso+)qGk}CzB^G5wX!lsDE zbflFIa02#Uc$q&CM9@7{>N_jwEDkH^vTloCnYfXqvsTBX^%%Tsq}$nRVO9zWxaS;S zdqI2YsztePZH^C>LHM+n>+%Luojcr-PMQErXsYLrt`rIPB!PxVinJ}VY8?It^+$g; z#(FnisXGN5Ua_gxx&)K1Vz^Fp6;6!=xWRVdqCWF&proqnIH^HDsSGSpE!zocv1z&K z7x}v?^-?xjhYEis}OCzjuk@k_lz&3iTs+EyYG0D3ctxQv2~|5~e`96R*kI~*4MxHFx|hBV z4VQMbK!hynL7S(z1`?uzLYO>2MR<|)hw2R^bYo_VOqnE8k5v8OnNC+TFuV{gBNy8_ z>3DV#aiZvJo}J22XM;R30xr^z{jFxR>Ihggh$7YUo)$fT%Hi#6h(ZdI;e=$L_;SW* zl8^1%j1Y!z@?fDgqo#5=L^I27+WqDzJmIys4)H`ojG2tc)u4Al)!EWlf-KDkh1zWs zIS%Y}pBR6C#tlDd#kTn=%{?VNLrh{P zOGaLSqS*_%;#i5?J}r zx7f5-uvNgQqtiyAv9uCsR(Wb&RNtl!5-jPX*=RR2Ul5WN?WTe-l44+zY1wE>=wWdk zk;eJ@MXAw3{Ug0BU*W=@QH91ZbI^;zf@G!T>EOUrCVA>n()tbkpP6j&aA6}Sc_?GK z8T2vcw5f568u+{%s@gqg&CEElIYS`R zPRy66S8(%fNJN5MNBuuwytk1~KlwfCiVn&mCT7UGta^oa8TS4f`M?B91=IP$WAxA? zyk&(&+78FYu)WYVWol@J^7z6}slBN%&wy#NAQ;ddAS-I9Y`{Mo8HPoe;8LAjIY5WW zpmNX}afU&|=_+Xq2sZ<>80SjxxZ6-+Zd$ow!63+C5oYF+YoRO&+^8RErq+z44w7W) z#Rq8DGfOU$$`53ovB((JED#hmU)e2wv6i%ef<)ydZM&ioLGHFEXQZa4!AkGOBjJ@S z@IS&55zkpG^lPFikDsv^!fPjKIFNpXgsRs(xd<9#I7 ze^n1jm383m0vQizvbB^j=5bRCzY(@_bxTsjTZ@SwZ>q(eu&yj)8Q*^6sngSQ;`xg9 zOXTUEx{!u}BJ6`+LWuZ9x6AX-{FgsRFMSUoxA`v`dmbMB$PU1>5}u)MeJFiH@bBLj zyC%_;A&(c|W?mi!=7J z?R&n2g~AO;nK!vkuDpP@ccMCib}v^7DJecd=hGDoUVg27eIf~^{Pgq`5_0$S?dQwS zpWv%~HCL}ye1?Nb7*Z2d&%u?U>SVj|*t-hqN#ll1L@$39???-d+?h?35PyUOLvG?5 zKT%P4?WpE?olQh&4oFLX}4*ehDf+B=MgIvTc*VVH33$*$N z>77M0*OzIfEbp@Fxi_^hTjW(~Z9Yhgx*eeXfMYZw{k_rfkyDMgp^gNxPT)pRpkZy4 zdD(NM`B~^BtHp)|triBRRY&5gt!&4~_&IxU7-Z@9C!?dA z3;e4m4EzU=z&C>iHb2Boh6R%TUbD9L5?mtTaJ!^kHT+VYefZe(z3tCG*{Hb)TXrUF{}u z3%rUeKkb3vWd?P4CenDpJ&uiCLj)hk*0~ggukf%nDz_Bv-4_YtXPj}{%S$>8a|=Z| zuaP7M+`c_+UlIp)@u~#oeQ%u$Yg9MCwx(Opiqo)*e`Xp;=sdIgMT9nV&Ry3jQ9k^N zpm8q6!BIb-P8(FU*i0|f#L+bakG>FdvBh6&xUNR%uWKlb@zQ1mSi*A45D^y3B5viN+vhm4;vY57ni(|2c* z@^)k0N#9<>W$qEmD?wi@&P`24_tF}5qg^J$bj?Wu&hiMGUAnr4?bn}e)L7kRk^@Qi zxhEbRM~>Aw?i%P5-lQoR?Xo2f6MZmsU|rZr`o2SBSo4N7Kb_3IYmX&xgpji`V$$^- z`%%!F^(_8Yp0(ar;FWS@2Uk$Wz&A`bd(MsQhQ?pQox(bmDTnVrH>9@3Amv+_5iY{>JhYP~+TYXw*=ogZVbH618ABbe|7kDxhuTlkGg zI_m0fB5v=jVqOuVKMKecgnaG~VDDbB9&zR)!n-t8#CG8+f3f%z(oh?5Ed+A@L)ZB@ zQuv{3kC_3AZF1RwcjuVi+fRuo)SE8&l9+`pevH2nFtf>*7|d9;SD5Qgyjt0mt@8fd zjtyGwfuWczF+rVk?nj+m*)RPOpP{>U`AQ#6ckg}pPv-j%Wjoh$ zRlpzrv~*k$zQn6_!D4pBtWT*taQ06tlgdmf1oG9@K85X+2mDq5y^{JNmX0hqf6)LvMsZ8QCb9CqUEZg+(K)m7j(sn&F-Pz$f zlkXCl(w@!tGte*$`-^~|gN@$e@y2uk)P8-^E^8iJ_oUi&GP6%7;=@P%;`Y6>4_+7x zWv=R`ukDuzl(DInew>;zU4@NPVeUNB5le?`x|rlrSe zU}C4d*O^RIOeD(xP$rb!JRZxmH*t)>IjxC@?0pmLoYOKTlG1V?)Tz%9$ivDobs}X* zDTRc7Jtz5PZJ)hOc}MOSOWC2Yr)4A*J*I^u>$^|wh5A}`_Vn0;GY)#J|4d#yu67w|@Yd0bqTc^suS_H=^ zzwN&$3)SMk>g_5J^8a9^D}sD)%*VM!tfE3WW7}PRp~8;E_p`>JpzhCbLsEU0ktk8S zqrWj1CK(pgcaKcM($TH^Qi+ylx;4DJ#q717`WA(c&SXhxAcY;vuO4S_N{^Dd#BTuOe;BNoi)A)R2d|9ZWa*Q#xjz=x! zUYesDNjjO~Ju6{FVC7sz_RpQ~o}zqXYV-Af#NTMldMW4fBVzdUCbCQB3vciT+F@%p z+v}{fIBR`J$g(h5zT^{X{LXJ9bz(i#b+m2FI=EGSJb4QWdiKwI&GXA@&gI3&w|0%* zH0N>>YCCBga?5`IY<;Z#=|{rK{*1llvvR3G>q|#R2+sn-FMl@$pZ)pi^IrwX^+Pi? zzfaS8)IXEd7<5k?;hqh*_jcD}d~&Y!Z;e6E5$UrftMg7OMnJ9zxLR6Y`om?+PIV_W zAI8!9NV}Wxs)y41OUBLBuK}vyUAnFZ^6u$(Hok!(tE-Hn0d4yM8E8%TeQrYLhqN86 zsWSPvtgFjON)bz&=>(x*UIg{|1dyq;C-f|87%Xs`eY4iAzBb>TAPcg73V--)}{ymy?wz$w4 z%HAcNJF^?lz~Ay4*zk^fYMS&2sipICy5D19&tyqt=)T7=g1T_*#CrPAehiVw=VNEg zja;B*n->#`Ok|?&uI({LBsgfNkBe$cXB#ESLxt#dBz|p|)b1V1RDJ`wReW=Gt4f$u zP88Fnzp;Odm7k<`+N-}{=FxCC*6koT#YbxS#=Wt%)sVSC^y|poBXZIj2N5}?%X!4; z@kY*N(#C4ITifS+oyB4-^O$(rH4Ew)TT-u)VAJzC{5GsS5hC}lC@?2$GWO*1^7|iY zQz4GIhY_^xXfR`zUe5{B=a(+YDSW?1jvU_Wh7`y?ZNkh>pZqfYc9;*X>YG4ptGpPM zJ2)r&EjlE~_s%Za3A-(*&(7oAr}@Y{XZswL2|CuS)kwRFI9fcUJto^Q-kG&C<1lcS zy#Ew5aCb%=$kuthf!_dZqL2ArtYIYCG|i(X$fkBWSXOM6`>2i5eU(L~Nsm9dFksXr zFk(mLoqRLdp~ffmZm)R7yE6g{)_9~!A>GQ=g2!+{nKu*(*^0ekJ>P!NOqDx7j9As= z6n*r+_UkgFj-9EhZNI3k={w!QWk;SWC-3UPOIoT1ko&44+-Y`yooSTzMZ}&5c3&gp zHTITG&ce-Fx+yQg#sEd<+P>5a650I^?$BmG&aSflz5y2zT`^-%lEW)EN<`{o`RQ_z z=dJSFIcRJL>W24()vfHkoz5X>Og*Hw*W`#!*QI2G^NAv0V4BidNUP`;>X23c=ydTx zOKe)B@Op`(UHDJCS9IkE`ZFR1r|dXmPo&y6LVmqNft-Qfb!1-eR++7|+VnmzW246? zbOs!5v3_mQ>vJvs9!z33w*7P#Yr|TjGL552M*pf-p{J<+mSo|*Yevql;78UGXMIbo z`k$|4zluz}G{%2h=o4Uy`$?pUzz6q68zoo#IGyA}@Ns0uWIFp=rsd4G(+sHR+JmHzL7l*`fvRj_{ckWUX7)T^CG51b3gL$ z(<%J_V5#%Z>6HHiD*@&D2UY?AApVaM2kd9(yV2`!lL!q8vy?O--%h zXpWCsO^u_JXxkuNLiJcw!4j@sU(Q0DgqK)C6pr-f4Vea@+0c|2rhy9&KY0#v?-O9% zKc8GX){O7y!?rBkT7x(&2Tf0H)5ZOtA&LJ=0_&c04-kT%eg7U*6Ab9utt;$&_{n{6 z85kMKJQ#+Ch9D~4LmFfN2;fi)SN_D-e}8|!y1IID0?5+kuqlK)tR{&Zi+g*GlK#RI zc@=Hr^$5rsVksP|5c9i)R$zld!{OHx}SCs+Oo5P`?fxDBD@bnuqVhWiA(KzRz3(RWQ^bs~(x&tIe0+TP1b%!% z)ca_>)fW`tmak{*he#PG<8o}CyEqYG{p{F8hA)%m%rv9WYPWpH;gq?H#mnkvn z3w$V%N$7hx%AZ1FRC})t-*nlf;DY=6&mUX~6O+Q=LCmv+7oGl*F0XF@Jn{gb8^?eS z9wJG>@dRK4?6$4|anWou1BDFl>=aehJ%lHb@hzWUKiz%Jd2iC`UY<8s39qFEXn+k- zex_oSM%tCOx%wT9rzIz2Z4ILy*g5(YfeQ)>+!ASB-h)u@D z0a38=^(9Fo1NorGj7SU_Aisw$`OEi&jo(i}W@h{6-E*5w2jDh+-Hs!bS{iSq+fniu z`YkU(*uobuXVP3vfY2K_O217W>GYSFGdNdNdFqan~ zw%vTb`x;`>B(jYMM6GcaJV5D;|>D7>$_T{5*$6TrL;BrMTYW6)1q{tM1b8WE&s|e&~>q(CYGjj z?Mv_k;d-^K4b!2*HpDpOp$IRDG>Y{tc3orArZ9*|Ck$ND`2z1%*zgtfu z#g6_&_~m-514c{0-H%ZudkU+;LtrqFm6i20((6Fz?d_$bK*Ss$Hu&Z~VQO|-tyQVm z##piNJcKRQA|!Z6c;vDwGUdCTau~FPC)1X+83nJAsx8L-8EXi;e{8ebY)pln(x}7AL1$s2;MoR z@cij*!f|=q>SKvn@&XtaOjQuYZxUq`O?`I6AW=hR5ObPoBqXG&5zoYU?noi}h@FcI z@Q)in55QiWS4_L$T=6=cJFKs-Z?-hu6`J?CLPGM5$0fcRVobHicdXrfp`=5{5%F5D zXym|)0pwdzRlmUqx>_sJoV-6RCDt8uCTQ5_OJp1hq6%VYq@JFhEz5^2MVVq|9RRGv z&bv|Iw9)3qsGJUTlktKp!f!XQch4uKDQ#_P_B#BsGIlV! zI)-B)LW5PoECTKOencEIg<_mD(GLqn{4E+)*zJ{TvB*tj z0qDctvrroW8huGx8-xz04y~As0b6n%*;tDIcS^G>t!*@C4JS^l(Y_ zd$a0unDiwgkQBHnbUGP!MNt^g*eW2bj4^#4%45g~$1}tv28V@#lD`b;hn&n(KY!Mm z9TKmi-28ENHt3!wnnbcU9EZOf`04q#LzfKuZN}U#b;@Rk8|{Ri5d+BDAG69?b8YLr zScqud4l|dqmX}mjB(wgz#6f!YAw}5V4-*|2kvMtG*Yn#nH$ya!%F`me^OaY-c(`D< z2Yis`6|+PqAHg8b;=FZ%?lYa0N?!QB#A3R@;G4v>PkYsa%}*Udc#P{81oUygcisFz z%)+e{NO0fgWhh@sMsUel*M7jDaWQS0dFy3ZcXgV>l-8A)Jln*v>| zi}JOzF{GuSa0*doz0o3OD>44nD5f@t)*ir{&2^A(vi?yQ)zgd6$cMe^G%EjGod3kG}f!kXR+#C z5f~>_qL84Sq<-cl0sK2qLPqSWxC|^e!uiFYq(eOGu=Y#v^P209fTL^@{7~+sR%CF1 zu^BTIiDxzPi0UnYG?TtKC;qgL3HEUQb#t?7CxV_s$NsbL4@Z+aR-#XN`%PZh+ud|z zuJZ32?fc6I+pT}pj&(ivGd-D9e_I=;75nLm6-VYV5H5v#9W<&ZXU#nWNT7bNti&M? znhtoSihjiw_cz1z)H%KnwOdh=#`+A?dZ+BJS+`;YufsCcE5Wh(R&ck?3jJCRE3}~s@ z3(py9q63xG{;|WWLn^W*0tHlXf@7b-d{a2^IS)gD=fq8=z3y`u1Fo9g>=$FgX7A7T zs@NAC#Jw`&OG6DhSY(SG{c2cKhf4w!t#Fx|wah@&XgLQ+={doJHo?}Sx!TKhpL={> z1LL#0!>NzaPcN1`hLBL|6Zn0GtkfLfESh{fA5JTMR+$V@*l?nCbaYHC#6U|kJTa|- zqrcYSuOJ~^&A$EI$ui*jfE!%Wn}|r_jTXB-7?tq$eN1Tbm^>sVb~Y=8X>bqo0(F=+ zGafyvrsXi5rXnA=Mmjy4>b~|jhKjg)4AN~K{Oa&t+gSzncA|qah6dgrKnz@@`3i|f zJvXATxG0-elE~7>&?D>{#7cIvWwEiOtm`Ccyk(%WSPCcEsoP9Y9Iog!q~J=8OJZYg zG^S|xYGR!q%ja`hu=TkArIw-*LzYJX}$k@N)5! z=?l6qzLN8x)mTHv0*PHsxvNF2Cf_9!wXyG|5j$Gc^MhDlego|7Du zk$%O)H{gEj@5_bb(sn9480OSe=ceB;EGv}Ou6S4CcRXZwIP`mk1kja*X{IOkmWtF(>UKjvNll+|$!{xv^24>0q2~0QqJxxAZhc zOKHv9@<-Ky8l76leLM?|md{$xExa<}`)d69b{)EMbH?a79c#aS{Q~Y`=&~MTcD#O_ zzg8d;8EbOIou?_A&2DXFe2`t2*+g|v=ubQ+Oy_idFv6I)(B&6bvn#=r9uK&+`|n)agb6IM?^3?lN67EKh_Gj>*U_nHSz*Q+!~670DmXXz*(^EoEXWt@ zHH-aYl{yaUK_D^+#Q!>FGL%vkH4h)?8{lxi$y<7K5R zIyy-1bJG(O3g|gaC4lE}yU5|)B z9L;!C{<;byGNy|Y9=NKNa>=P$xVkoc=!&UVS7J(m{v@EudeDE%#ml>t-FjKW8+K&E z^aWmfw_p~wT9+g?xBzty5B~wav#L`?0Dl$hh1bUkEB@i?ox&2Dt&~!y?F5{z19J+T zqSBjDZwdMk{{)0+TC2UxFO91tr(i=410W5o1}CF5_l_xy6Dvm%rX|(=`O&ZbQdPfJ zn&g8?UY`U=GIFWyvsr3vYZ|rB=S9cw9H<1jiC9f;ZBT}Nt;9EgjojU)W+)S`u74?lSf-C7`V`;g-sqyARW7F4qc^gg z>KswMI0^`;rr1dF4t>`C!nl+h0E{?g2K%WLxiL9JQQcmO8ZFKlm0j6~c+I5hM&G++ zjk(mkFb{T>(Il=pr%%|M-3U0cT1%#WD-4j)%VQR8kLbg$Ni-kETbY(ne>(KPJzMho zoB|iQoXOZxRfeJW z8R8o}8tNvJi@lM+r_Z5ckH&h%#@GBh*Z0Jzc@`mE5fznjXm?ys3bV5D7d|1ax^<&W zE#+s;%yk8?AxkpGPx=9s8ipt*I^BB#KocJ#_{+KvzW}QIz}o+ zQY-7krr#@nXJ%5#lAf0fN{@eKM=hh8Uxw%>vnCJXpcWE*$M&uN!C_4<51wW@yQB$) zf8p!bkBFMQ%Xn;lM5$A zh@BF}jrkS21Gk*>e`ot?NB^Mj0&reAZuhO3I*Qqp}&C_y-`n{iUb zK9@HIZ8bl~wJ<^~+(WiB{H|tW-@UR6X;mX0S!4nOF;j@8ieLDGb@`G6i*}q4imF%p ztDn@?)K-%DAB2lmXCqKr94CuL5ehEaGbf5X`2;mpn{C}%SFgb*fEmQMy*CUEw4+h* zo>*^yqi$c(Yz9VHL8D(~7Q_gk#ZTorOEuOE${HGG$7U1zD8-Qbsc)x@MU!5@TV?R0 zjJ1-_q3|tB;j%)v2^3-uif4@Rv@pK zBcu_>xj=ini~8jqTlgQ_0^N;YKflhX`NlfWrmMEpC&Tad8S>=yWXrW|MK2Y#yJ80o zMG|&i^lwVX3euN&>5U#T?)A4u*D{%PNH1T!vqkZn#V)XW!XBcBUt=bX(0qM>NZBDi zK=3LT{gou)bxDo6;1sDKiM#5GTml7jbDZnceay`1Hrmn>8#j_w=i$>jIi4d z(fQ=+CSa6UsnWt5u`=lGNuw=la%Y7oID!W2J&#>6C!m@y@nLu<#Z7C}ydL9O9D8A4 z?v&A;5rZo}`-Aq`e5Z1pqv3I%I(1i5Vyvp;1Pbl^YjtzX`laVe80IW2!XZtM2)`ap z6*MP7%Z<>pbQEe_I(nHbRqGmO1WAkDbKLq6j4Kx3oo{xH=BgG|tcGW7 zo)bTJ%5hIaQ>!1-jIGeHM5SNo5ol0albtUaabIZp=&4ln6~)B@emNQ#tIN-H_ymLu z7pX*EY$z)99aGN?6SkjCHl`BvKBl*APFY*)^0ckINa4@gV}$%ZZ=M-X;70B9JWXF; z=){oFIf%w2^965G%?gMpll{ciq{&k>#*nZ_5OLKUOZiYf|8sUKE1+`bg(t;dYhXFJ z^c1UfN-s!q@lRg3b&L0mWZ|zi&6vKpPW*NklxJ z-Pm};-KHsXCOzhf?~N?ZBmMF{gd zC)SrM@2XzS5(P~Ri)z}>3VyKHH{ed)HnXG7M62LCzj8eo$7RsD3h?n^1PBn>;g+1+ z_(mWuLdy5ZyfrGG+=qdnBIFodI$$wkAMMFTqjP-qYRHMf8Y&%}SMi#d!1e%NFV{yhb2`j`Evh z3~*T?E;Id7R$3}m7y&-9D)}3>u7V9>M-WbpSQEI{E4+5&CubjvLMJ|&%G26WzIs%+ z+57b07=KTGVR*)-kn~dd57Hal{}#0PKcqJxVDSI6f`b0{SwTS{V4#k{KL7>=Yf3sM zWMeNhKYPDbkQFBohf_s{httF}vCt$b#y6ci6*(?8@#IgH3^WL^G&vRxI5w0`)U^ya zE-(Kadfqed{qt_&o7(h#<8c7)x4Pb61~dLgtx321Hee72QpAAizk4EyYiny;US8I> zk&zhz<}}Y_X21;;;4#C@%kp+JA0q^WwGc8wo_@=8 zcXoF6^t=P**hX+%<=xD@<(<5HC+wI0zNx86qI`8$Onz^>4#t?L8gnnt0?)q znPYYYKM9T@5L>ZXCelHGe9ul#e>~83fJ>8Tit0Ol2}4@10VF#r_1^r?NEUcw8pH$y zv)5OLO1I+h*WySmy&*5#GM^##i&dCpP+M^)rweNDy`y!w1EX~e_|d&O-4D($aJ z%^@Q?I}l4>EAoKJD_+$L5t*mP{cc2pt5z`QpRb66+=w!Qd1yB2=ym zb({XQJSXjM;l`W~-ZtuR-Q6EcS>rb^5b!tx_QN;wJ{KW7-E95>xnCQKqHq&nPx4ZT z^4Vg-o`sbamrn%nnJB&n-jX_|7MEC<3fs!(vVBv)q4{1RgF%80Eg~~7W*_yMTnwmu zadUcpnG%c;sv2u4a08TpN-!=*CSujC6BlG>XV3~q8V zGy6mE$kNh2BLN3hK@2I%;Ky1s^6P_x11eDwnL{cHr5J5pMv1JZDD6h#c@|O&nvQW6 z8ITj*7rfNSXwElniO)4PELAcu_Eiu zC1pWv6~W&EBv|IC4)@DsBa9fggeseX{?J6r|FPQDlQeNl;m6Gh;X3)y$OElh@&&$4 zLU{n1)*98pTnHVUE)u4Gy|JaiIt_)yHA=+Do+-bsiMIAI=wSTYt5>hiSALQnMuH#V zon(Q0o6pPh6EZf1w1o^v%kPCGJ|fKwx@1%I-Q#F#S9&U*NuP!J4w;EPKmuY{`p`1- zPC)7p@UHEq9q5KM-$m;72Wkn+yTeIh{m6KGXm6|E_0ePwC#`2DIcvtS0=LnDftA)t zgt5d|6e;^}V33wl5;lY!cB$mW6v0rCV=$k6_s!etLPA2qM`7=CkhVlk70vcTl7;69 zOL&B;G7{V@jraQcIvR3ro+XUllzgJm*Fa#Ih)tjBs)5*eSd0D;yzx^}+y~)RlX*X) z1jy#iN@?~L=zyi=Hs~Jk{sJS=T5(pzB(M>C1py(I`CeMlL;`ln@2A7dCy^UCp}CnF zrj^2yMcYV5O{HPq8YK$WL_9&mS=3m<>=@Eck4Trt!cj<1bio5^pxEy0wRu!0eoZ$F zrxSY=9x?EY zP!fVutiC8l;5tXycK!6nhq#)?shk5q>`Pt+hh#_XeH~oU2o_8WxX(C%<(2Zj7`v5! zCt{yS7yRd!)4DI=`qI*pf2Q`Hf|EMlSS!O8LE5AX8kIxvm7;u>u&{S!5=88+6m}dk z>7-iWiXHpJN`xM$8u~UmM$IbX1x-21V(j2H<6JLoQbyxVXJiZ;f%Bd9&vZEB@!Q;H zV&~Vd^G0X;TzBEVA01hXC<*P7gcFgD-cDdT@%+|p9E3` zxFi@aj}Oi7KhNNt4(jX>9{r?Q0_+!x!|GXdw>QfC z>R*_*h(sS%hL_?LAE`2z1cg;q;B|WZr024TfjxvLOy4nVEoILYmy?~1r{$VBOndt{ zinKe?u?Sc&VITuVb29O8uwtDZvti`Ks_ppo^H|8vr?;(q%!U3~*$pE5B_xP)2EY$A z;v~ANfM{huv-X_O$&>kuy`kp3M3>OWT%dRIuRHzw!D%B80cPCICk~E;gB}<{X z#=PS>lYLn$I@UkzS>Y<;Klu*jm0QNdwIHOMDdq-`k+C?5F(&5mAnWSN9S$d+&dE)K zyXZO95rK5{E66pU=6%E99BO&Gl|+g#{Ckpgswo+~oH9>R!i+RdffVz?!N{DBP_M|n zexx$l2HG>cSyV<2tRm(){CsZ| z>rrV3%N^)M$)`2CW^*$Q`)X?K9oa3* z6pQU8`7(j0p|L8{ga)5Agj1&gGNnQbz1(Bp#l{R80IsM6zgNTM%q54%WNjz4Uw=Uk zVoNBjW3u@|H+3#n4`*y6u8ZKS6ex|xvp7^*Q&Tgnf-89Y0B6?NgyO89k(** zQ%tdHBq0HdfX|Bqs|Ytx{VQ2Kk4VW(7=DNPnuCjEqJ4@{Jj7RBaKvW>&V)E|P^V zQZ53F#e#f7X3Cjh9`u>7NNeF4wIN$iQnlO6SqcMZvH}0%~17!a2xp_-dq0j%4oQ^`T5_@&gzaP zaU}Hg^rWPufS5>KZo*O^2^7Vl&(p`}`0(%)uCT7IZf|ezf&roSftw$Ap{2e3_~__N z_4fSSF(jdSa`i1IygT*SK=Z+h2|mLA8qx3&ZJeHd7S4#7alA)nB1q8At2bBVPN;e%DH z66;)+z}NBimQ1OSV7N{+I&d#;pE5&KzveSf~e$C-m*+B|D z95eQPCtkzsQI~hi2GhhjeQ!CzXf-IV?hI#BnQuAkP7-={QUye!tV{yo$oDuuKESC_ zDXdhM%GA8xsXJ1NY*`E}E_|oXOp@|=%8&2Rcf&yZH0jVsWosJ>e{e7xr$*I0p8$LV zc3u-#>ED9WsPzpEI1J7#eZ@xn7LkH zJGl+K{tsqjkbgTHlW=x+`A1t`WjkjFz*Lut2XNbUaksQ`r+V|Za2*)n*);(JU#d6% zg1z(d|4R`%UI_G`D(OK1dLR0?lHUIlz5l;q->obFMjgrrv9vJbvgC%E@mm3yJ+CFV z0GFi&n4gD7z?_%M%pA;X0XE}}zAK`hPqp%6B0OsHW>J)NQaYG>g>?}aV!^I1%qvGcO z7psB)CZ_>jYi=+%7;0@{VZjTuwwX21mJo9uehY5?zspwUR%Sd9h&kYOV9m<|F|#!1 z23uN#E%><1z`SN)%l~3)LwF!OKx=bBxq;R;hXRex3+3a2@bg&l@%~>p4M6{)wR!(n zYya<@1^}-E;9dU^2oP~dIp}cvdaQS{FVKH_9}E)%@9k}q7zj=sgZrYlZ!Uy#^lsHH z^zl(Ujo@{rHoIK~Fk0#cgl=isr;qqIYmZQ5C7*t!+WGWT?#c#io(PrYdwiT6GNCN< zER6^;#yl+LGgJQ@FTv^}M;%G}{A(=c>&wsu@qHHsy|5%mb@Q2KA@Jin6#qrR4^+sH zKTU##4zNT1Tn}M?S{f>RIffkzphu$bN$D6Y-08lEnT1bwck}fFL6+w*I1*L)i#Ms{ ziX${MkP%qyBIaN+=WB3wIktF?zkUAs~XNoDE*|0PxR~{Ad(Lj7w9^N-aQjsK68k9QVZ)gm=^)F@v zHun|(YBum0ergB?{XX|`V$ihUJ>WI)b__#sX7APD)h3M6w}(*hFkJS~94Z9nEU}(k zc^w7^!^{q_8t7O7VGO3i;k7&A>-4D1D%TA#TnXe^D{FhfC}l?X(y(#C$aW7C)B-pD zACYo8DlvmSF7Cy|#C-Ff{Q>q6a2mM5dA=Bs7hG3@C~~F}SibrvrvcDkr-5##+yFZ{ zM7*C}A~cI(r1FxO*pi7nypi@O3l}p!2e%5FGA)s~C<$2d%Alii$}9~gvB*}GRz({2 zS`4ZwA#y_*L#2qXtJjV!M;3Y*%JDB=29!Geng=0!FaiT%kd(R|#fC!}$H@Yw(*D`Hf zoOMWDno$}2(~z*q&K09R@ylMUy0jj)iX#LgGY7>N@EJ((x6~O|CvKv#%9?0MEpybY zheMYUNFcRlQWoP8VQ+}-o;&^6V*YS*HM`gw)SMRuwSZ80ez&$K&7P)f-QwAox!amD zESvqn#SYf(9KZk*isI_PdI{md(UH|F|2H50_n_RNF9FRnQ*F) zkLSO%S0Rgiaj=&Pq+bH>>E&}nkvB8Fuja+X9+B_QL!w`nOHdv=@MOes2IR;e91>s^ zWs8y!5=!7_A&M7XpKz)cskpeOuQUfJ`q2nr%5YNk%(X8N#f{NZGXP<{Vz_)XiY*fk zYHAAIh+=gaHqHkoaKk8!;Dr$;sY}>2p6I|0Rwu>`VbhyEoQCLuic8biNz$?dHL@rT zG>S5*CWfdD_@f4vu}Iki!lC%NG_d}J88~(X%9sJLdJx4@3_OzvVSRMxa82dXY9$0) zal@~mGV!}DVuXlX^W0h7E$Msmp!VD*Jj<0R67$dDweS|pPLi)<$Q!q;*)bqRSCFpN zSO(!=be>enSVLWsj*A+HrthSk2}i4hVPz4DnndtnQM4OGx*@Y#b4>Vjt{J5{F;Mu} z7ZikatMHagEOBCDDLVZy8ImM;+zF3t%uq@=2iOEmr^+IZakQ5I!QNX2)!Du2o;VwK zcXxMpcXxMp4esvlE(vY{g1ZHG2niN6I0OiCp8UI}dro!FOwXxP^JU(ue2DDYVXwWO zweIVd-^G?m%dfACY;i4_E-p+YMC+Pzk{>y1Y8T2^vy(_Qkbp*5HR+(T4LOvIy$u0> zI$}*hIc0{vBS%iHTPBTF3-N_rGVl9q-jFTi}APkDXYJBeDyFS9p@CTL3iQN1+zvr1c?o-S>9YgTcL z{I#Ps?MDUraBV6SPa3W>Tr-8Ny)`daDw-u09&>DDf;QQBp>se|2v+V%v?04=5#WvwCP@{bKrzdx>e z#;1cH$Fk+Z%ePNfsGcg1RUZvoR?&`Adr0#M;Pp{ME)$}MmOh{kn5sv{W#LMKnNOG* z9-nhckElh>lFMhbpq8;ha=nqNv(>Ng!T5f`Pks+!S?LWoIH8P^U(NXy`iAOpCLDE# z-iCWnFp}ar22qQe2ajK5Z+gEjlkxC zWWGov-!6Aa>qUd~>xuP*eOGtIZ)gK6-;LJNakJeDk*qG;e;9v5 zI0Nb$@1urrb%o7>kFTj*1Y38N4F+N*Doply-K=Q+wW@Y&D;#y3Qn-RJtm%&eC;3=g z19lNSdqG`Zqs3OT;eitxT=_t7xRft~ofSV8(8?GQ`E6{pqIh=^!_?6ktZ@rIE)@mJ z`B>GAofphb>gh;NgR1e2(pnn^LLPYeX>81@pdwGqP6`T?K8!8>R24VJx%td$hhr&! zc?b>jf)U2JJHg1jJWyO0Jzt>Zm_x>BL$ZZT7*iOhT;tpb8cnF8(R6b)hHOAvB%`d} zgfsS74#EauAi8yjnC7KP&t+dY}~ix~oczPVj@B=Trw-2T!8v3(Ke;kn5FK_(=l}-wS@p` zE|;CgqS^_!-ybQiIQ$&N>NjTxN+M}UxQ`^a z)!ETiS4(R#dNr+Rkm9RURaNG)it69s*o>W~zfNc^qH6G}HRUK?^2?t+Uwpcbz}=k^ zFmHg%jx}q%DLFgqqUIUZh*0i}QK$_A45_em_bOs+F?_T$LppmuGLDF$*ONE2otj+t z>xt3W1XOL$W&Fu?7ty%*?m9)JDg6;VD;yM%p&JSoolVt5WJwI%ZdWS9fa$~4m*n?H zxU9#M>Twkkk|5uxCb6=>@nGx6gcX@uZTJLKRAt2CV82H+;*;X2x%VejQ*9CmStP;7 z-@Dx?=NkT3cR(k-O0mu36x{dV*GrKX=n#^iw?FT1yN9VlK9Q5y$vGj;#%Gv6>+CH zSSV!pkU=T9p-0}L>$bO~hNF1bJ+J^<7~_pnl$2z;*2JhC=2xTTHyM}di^1MB zAb!wMwOJLzYkzbGEr~-lk3T1}20|u_r7GUw9;N>{*|673^_zNTfu^poB&a=nA)o#$dcpToLH(BJ z?KS$64klYA^#!fGetPYtsHP%jLv)%^r&?GG6vuE*86LLmz_l)PT|N^;TJElQnmvx% zkgU~7niT>Lg39Urk|(FhW+n6)6c#KzVM#N{HfA&dyBLY;J)Q zkK732o^Yi8b(Ubdps&c@1+8Empj&MA`eo&9*(fSi@a^DdXV}dAwS(rSkS|fCy#NT@ zrmTld+FdE(Bg=8gnRy!>A(G7S58Xns&-?bjCq@eIID?M4f*j9Z@O?#Yt+mrQiwgzs8keIk<*u;_ykR7P8q%B*$6KW_Cbx zDRuK-e^_d3?P9hY&))D#tQf5lvPM)gZ29zO=r%6F3pc0+#+859*AbHvyM?h?Aj*2K zPXl7F5ry4cf(EFhb#1oxJ^3J~%oSUme%F z!9hH?T_X`jPt2tCAq>5jV`@E2hM>E!2jP0zvs1Dp-DKw|q(99+qj{R``73bvHQAwG zH)=jVSATbGTSd@F$c?NI`58pcWUjtSgrWyIO$0Zdk8@Li^W2uv>KsSC9i-G*&SG$h zJOI7P7Rg7}I7<(aI6L?7x%KszW3a&1_rB4G&!1ro1U|W4RGZ5MN2XEP&E{3MTGtAi zI%MgRyIV;Fuwe*b353-nn@<-f;V3r8dvGcIfv?(etDMkd^|FE1VBF}+qv}JBE{r1@ zgeNrk;MJoaZ&a}V#A8z5ezEXNIQhcmWPzo3GyB5dZ7x9Lvff^8%y@PN zSp)~DZxiKKQKkE?LG#oUbEi+;oqipfxg8$W+53bzTr7ZIr}O1L?Hb&7dEJ;hu-`iH z%9l*OS($a6$>yhDaa_fC5y1@B9EX_~IxlJk)c3XkxyZpA>vs3*RHdP28>&=iYb;5w z%Jr+UKgSU+x|s#!giyP@AB}nv%vmoH7dc=*LZHT zZ(xD{Vkn};@!Nl@gnvL9UNA2^QiL(9$+g0TvW5j|mE-NxbVpfZmS$x+2@aMwsnr>f zq!;g?U-R0N+xukZ@F$1iL7r>N{`LFRZ~JF{PhB*570R}zH6^cHhdL3U*e`NRvk`=_2F?}xMHzX@C>G4?Gw>LX+__ToBb02JFHS{d- z{a!t^$rl0hp$EF#Fn|Nr#J|hCRX=duS#jr*&T%oMi|}CZ_nbi zS>oia>YYswNe`$FybHprS*~3b)oCtM2z8RzhI%s(45zP`*=zAM*o65A^QrpL6r;N? z?peNmM1>t%uxJJVVc`s(zgv&zzAn>*ox?Gk<}l&w)^vJbz{16+ctw_6)vAEw5Itc+ zg#x#QwS@SfL*nWC>nlK zm7556hCy#{GkJ5l5b#! zII^^qr|aZ?y>%R6K0AGw`E{)(K=`sv`y5upm#MV-rQN`$_*Sk{$6oe$=Fc8|x_K7$ zFWwHWa|#H_S%VGv2MVA0e%(QiReleS3}4)Ky)7c$F#~KP0&a|U_@;d_n{=C%SnD>v zM9K&xtz1jz6QoEIUB;Ljpy;N1b~cB%m+W0PZCE&AZFLQ8-ZNZ;bBs9}fSpBgMmPM# z)+r!ZbXDFfsh@Ds`PUuyVQi zk?MD)r9wZB)F;^I;Y{)Z=}rHDIp}Xa+tB~HZ^fu|ydmXD81l&^QD$FuNQ8F>eB5mG zbM+3@+*5}#Hp41-E1_lKz5@XYilWuUlczr^HQTqX+FY6Mek&1}zunb(`u2=4;*EMg zS1APUZoXWeYkL@sknkFEe-7LPLHug?*>OMleSW*@+ad5fXoY@p++LF|)uu0-hT$OV z)m@&VGp&OI?Q7d>8Kr4V2A@oZ01wd1>`&Fb&;pqh0zo$BJ9 z;sPUMKNkxF=h39Q_8aH+2WRy%?N=KaPFMvde&4Ob_BaRbPhu$BaDUv??76?T5s+MvU_hJ zUhx~*@pz#)-1Wv37qY&Z|l@5a8CaXq0#nE|J_UOW?wWDi}Nkj`_Ma^OCVsNvJS)f zmWR$ee(}5-^Fouu`1%tJQPj*==q{^0^2dbo>oxT=VK-< zvq!aK3yieGlkeeu^5fh8d`DD!;*oG(PcL(7=)Tlf&zL^kHPZYb1dXc$8^T7!29CFash+B}J9 z48$)HF+_}ps)u4Q0gRcv0N7yb58#sxe<~pCZsG6@4-*;Hp<9QmK&(>!X}8WEdSw3e zig^;XTz0sXrxo!+uQc^k1s^w+c)Qz>w(4n2PAhXaG)67=P&1p>f=~Qvr$^5FKPE{D zibmM!xZ1S-w4PImq;aRcRi5si9V(BHl=)*{blHNQ&oc8RKP%J7i!Y4`HJGBZ{!CbeVwI-95n+)6eVn(Oi@6F6B7lB80 zk$rs%y~`>NjlXC8LIqTkdM^vwpCJxX=5Qw5B6T1!_VhelK-?y{?jV?|3Rzde{u-=e^6swtUUid zxCV4V{%-^G@$s?o{!a~Sg=-Gl3OHl$W!uF9EHyrdvCQ~l#>;wVBRFa2)0T{rl;#mtHdU?79C{h4d>P@9s_!W~Zm_ac3 zt*5=6OU>_my?G=~Q%eglkAAC`ef&e}!36Nt8rtW@L#C$wI?fLn5%GK4E>_3^(>ejK z&-W_ttJJC#nDkDVlFPO;Fs(1CbQwU zr=w!#?C$YB0IQZV_U(%gT`&M|0tPedpzXhRdpVR@0WR%`bM=4$SSukg8mHT23ZDI! z_Jj0x+icK=OeW8__SdQJp)>-5|0fr+(TO32kZz2SOa zfz{70CicR?Z`)qzzYPOc^fh&KJQNnhj=%SdaCPd2lEEP2vN~^fDraw-PDCg^1EVr- zhsD8fxpI#H%Rym&x<;)WUE29knbD{_BZI~I9EZjDAay1Njbd*M7&+TZ^Zh2(^56-) z@ehRNpYLyfT#BgPu#bzm?*Je84&{uED}+5@;K%V0bYVG72~+xZy+58#twhd-3pkdg z)2L|r?G8nn4M#2bzC2#H^k%b|v@BJ?SPOd|qjJ4J%>`4d6wMq07{Ja7ysZ~thDjh; z9smH6s1E{MsZSLZ6#!3Aa2opt7(ASZPBwK1Nt~jOOi;W)^$y(ftIS*J(|gy2!yxU^ z72aZK)6>(_I_~u3`a4;iI01uykb=m>g0!Jj2f&A}wM^cg+*AR_v$wa00lFkM(S8DR zwY%E-PcMKYsxh1*lqN^o05C(?`ua<5j10&nDpCXHcC?f=WToKI^DtLrWIh|#2?}H9pw4Fy@YD^lft&^L<*T8 zv3sdvc6Hhgs!FzP5a1MSn*+C~IJ5-mLC3&g!9=ZG5MwQ!O#BK!#)j}0_SZG98&ZR-@OKMFqM=rDbDC@wc71tmc(2_bAZZaOXDRh_*xhC*Pbt-q zuaZQP;6-IBn|BxA^7VLwA4@6+S$*R=XEXr5=3T7pRnF`Ma0=9#^`fV-vLrNJZH(fS zLh=&$C$h5>(HB6U;X0@pa=Jkoll1d}I2{pGfqlgb*c**1Y;BIX~b8@Uxfu zv;3PSZa2R)(YS<2P#N>4FEG(8Ik8R6fE* z&a><=d8N9;ak+IgC?7Sv<# z!J!d3hjxvSx_Kr!cE|9Dc@TsI#d3NH|6EpGcK4`klg;wbGKl{!#EU9IvV!ADHehch zaLpVz=dU-Yzdrkl_WLoTj9k%A1P9T-&Qby+;O8P>?*jHqMpLZu7!Ww{0W zeu&yvHZ~fMvy@IxibXllH|+J;n1&59xeKG@g?{J3v4jsyEEk}8>Y2!Q*f56LJiXLMw zw$#-=mAJtRFA!9OS1c0LWAeIbvRxowb9*aCfym{3bs!>>Sckiw(6~;whhOFQGH1oS@z{^MdI7ASRBKD( z7^@OPS+!Y$#q2Mbq8f1Nt4B$jBdI7OiLibnTxR=K3Qqc?>r_-!?6|p{UGM=(fn(HP znTUF}uRzpaoyj|6ZzD*G5fM^ciF+|q;@(DI301r1Okrc{kS8V<}{% zpg|opYSaAsDFiqt&-{UBKCWSbdp!-DsIEP9Se&z0LP34+%?$9gb`{JUyjtJUQ47Znzh8$i24f^p!0@(CVR7J9ZsJvA;M?zF> zo<&!~3S*a0Q)`wBU}?h~AVuUXZQ(F6F@A54w@!l4N+&c3!_si=BOJB^7k)Eoj)Q}S zeOl?BigGYw8r)c(kDorVLHb86a-qJIJnK5j3MeaKkVa=;RU{=KOC7h#6hwiO663Li z^Edj9`c%RLj9+z{5!*;OtDM+<9L&lr%sXt|!uKeLQpmPK0iRR!-36N(U?W9Ii-*-I zd*vjh5x&Gc;0it&7#QSJ!XYT-eq8{DO6`CxrC9u2B*hkh0y>np00S>1j8*7ZT!;K) zo|Wu`e<24yDBkaX55GkbV}agD<9PWZooNl_&yHHSj-ZS7Ah9DQBs4D#2WbpLLJi5!hV76M@mq2_~Jm3SDWU{!4vDJ?)0r2s}@r)2f-&-nHP#0 zjIOf;$sE-=AC(vEI|}ll;I5B0e)u>WAv-B$AUTTo-jCcV8pKj;+m!X_flls(xZBfd zc>Y)(`727Hvs<_fQ34`hrq0#YW z3GF`G<1U+yK(}jB+Z^~xWX<+Q#E8FX!p=t(O6jm5iXGkv1YX*>6i@XiK z7w`IfX&;ckk4Di)DBRMJKH4o06U<%{@R*X%x5nawFR2SmO4o|7o-`V9NB^nAF1T zkL%5W+);la^*dQ&GYe`EHVChxs^Yc2z8)1pP=%3t3Hj0w2_}3uUku+`Cbb_^Hz|c6 z-L>UC&courqFMZRMZtf9VMrv8y#Dz;tg-?O9hv|U^7I?dweK;2Dn0`E4Y~cnmH;>- z>M|lIx4WiBRzw194xlvn{=M_Y5*8YHyf@}&-yu$bfc&pHE{PNu@ct6iMM59EfdI8J zFs?$s--UTK^0ZpHP<|H>9}-cBCjoS~(Ls+_W~ajSy%7m{Rqqj%_Fafyd7#ER>{sYB_4a~ZpOx}B zYke+vFwKX@!0_?$Yh;EiU^ANWHtzs%#9N~K@bB`Dj)(PT3woE07OQ~}a13h2flZlY zVxo5lE%{1mF}<^GP^u=YcPMNg{p4^!B67EU?Z2`NTmg zh{S@txU2+~L;~Kbq%Gw;sM63*C1VMA81|C90LkH(eCP-632#+8L6s*!m<$pD+zffY zg)%8O3vTn$W>$+aM0?kDX6!!*Yyjv3Aey{6()D3p>+`C-sVY(Lh654Xk}vRtmqroh z99YCqk{GzFjGX-2;o34t<(^Ni)s9Y>uFTW^=sX$fx2)uuR&UBSh{`#`% z2Vg1Ja(~5PF~m$Cg!;qlW210j&!ICI=<5SgMs}`TCRsXK5Sniu4!>MXN-FZ!0Vhr3 zSI3a^Y~?XPX1PlNv#T}k4^mKUD}HOtW)lY;I?VCNmbB-55;n z!PL`>q#lNU2pAh=k=5S$nM?xiAO$U+S=-9y7oSF4iCtbwL5N>&DXz95r?$hQ-ITXt z@1VI=-A_?AYCq|i@HDHuX!bN31=DwAHDG-S1$|W^6ea*Mqyc9U&|1XS^{(*Pf36Sm zi|FG~lciVkZUW9Iq#SqIw0#_>cWn9q#K#9N0DHuY zPnWLuQ5aB;NT+9Kz;P!Ju6@L;(%hNCEo5qOi?xw zv@7tB_tDC<#`17)tcP#mbY^z;cqZ`KD%_J{GiK*y!+QBhf|t~r34wENAcQUGs4IT_ z8oDPTtPf+L05!z)AfRJuds}g+T#o~%UC?b$vI?JLCB7``01`u~UnU$Ix3acYE*El2 zClCn7`>)E7sO9r2syh`Dv5}|O{;Jr@pUnP}xAuwzZ57Lr;(&*Q#ws0i7=V^YDx!6Y7XF6Dj)|Q% zgA>yC0jZWougI*#KwV;~Dr{DV74TwC$$^SZjlz^)^ug_w8aqQ+QKg1dp%33tg*zgM zA9I8AfbSl>I9)CErZv2 z0Gzqm%trKVImk?Oq=J~3$k&UH8Ig0uC&TJ9SeFAIy<&%?5P-OB3B)*chr_5lz+}i9 z#3KV)MwXs<#7tAhhTH;BS^z-0do3x$QeW5t2InRak#c$}qOkAVb6=eSr`~S68wgZ+aWa0`U+x0>CA*@LD z2;D=IzKU*9;AT;&bwdZPU>&<3D3ra45OZ)XgOl5~IZ@GmVasB+$UlHG@Iq>@m}ki% ztbo=VvBtv&FDrF4b*fcc0sZ3<7mQCdRivESG!;E!%@3Xg4k1*lEl@%nJs%?CU5hlp zcI%kmJHx%L?WXYeYFTjb?@t;TCD`PevTbdE>Rc$m#V^8VGnZ7w+2A53Yn&7099sir zz@5u|kLGRZw!e0i^9t zvkX-bz9Q$j^Ij-?qMEBN5V3`?zGg$On=Tu{sQyGAT#+8s1jedqZj}nVIs4~=la13) zc1Cvd!s{&Ab!cZvidt#W3xJeO#fs<-bH%;A%8P$-+-N!j#aPlc3|CC16#CdNf*}?$ zPWZVXQGabHkg}11yM;|3b4U?GT7MU>zwtDrpiCs+RrIGL#Y#)r;VnLaez%hB(efEC z5|R!xMpcnf;muR8f{0LI)+s>o=2TY$i`5ufxa+dee=<#xF~nf0IpY;fZOzEYIJU}C zUI*|cs=OO{F={;{OhjVaSEd5x9DeAO8+;HYqPIine+65Es zknMSzDcl^24nzG0SY!pt2&s=1A|AHA5i4s&V8{!W{oZ#bu%J0ifYuS4^Bk6BNf12P zH2k*wJG_+z7i{;?(FnK$kM^M{jQ_G_WC|FAg2hAG3K zZXDX3qQx(1tPCLSri4DE&Gu%hcoZ z{ZJkr0hBT1SDnR2W1ESV5{x*gt8JNa0YwnD{Y;r6A_5pT|@UeNLj> zm6c7L)wwk^mPk)KL5#EeXmCN!2b_0wVsXDoC!}-hh<&Ct6{=KD^jZpm5VvxrL8H%g zxg6>;S8%9E-Lg~RCb3S$s%Qx_gi@h>Z7V%QKSzAZGc301u7=1V=iqo@i%kI zXVfrC!mlcY{8*3_B-BjNiEJWS$slDy8K(2ip)OeKF>wvLaLKu2d6M1{Hq3Cie0fH~ zr7{@d(#oXY7PMNXhEN(vkuy9ue(Jk#h@patabKxh*xbnbF4$$5*@6ODDjG*AUH7*1 zb*XqWZX=4b#{!tvi)gzbaQQF}(DB$alhabuV_<%hcOcK3V&#z$BV!e-i4x5e!l|pE zw54L88R*f~%CdCop#`Rqxg4VP7GQ;ssy-tvEh=((=D#fwYo}9Ls~$)`vB9Ntk!7wc z8W2}`02ksnD>o|<+ikous`cldhV^ zSiU<-^e>HyW+%+EvD3ApMEs(5I;`;d^wgfY3p&#JuK+Gc4mo)T+Xd`Y1eu?nvRkxa zIp#@><$AxreBP={H72S`<>9>Hz{vbax$&Ldu=86(zPVZL{QCE1e$wFn__$?j+^r+u%c z3aI4)`iU-dcsYjee$dpyD5jI_BDf(EnsXIHG#H@zdp ziqypoomvNBKp?OZbR7dCabYey81t2?*y`cDJ;q3wj9w$%PvEj&EPa<%#Bhw@mts3* z06c0@-|3|lZdeX*-PJd|@7o66NoA$+26DX!lQxGkWMrngo&51w%r8m>^OQ-6Pm^T` zeYr_41F|r|8}ARLHsMgU1h13%gQh>96+$g}-^8oF1Yo{?_=`5Xj;;AyDf6dKBIHAV zgP-|tQ(XR2eeFL<>A$+%6JfjCi{T9o^(fdN0Dvia2mzm2=5N^sD1#N=3#6IQVoA_g zt^@W>yC&3uzc0Q4suqxZU#zCHm@!sTz#KfEZVmx))WC@wKA0lv;BW>6DI>W+xz4E7 z`fCcAL|j~)>00LoAYuvd@%?{ayaHBJ079_9-N?wOKj`%@1>CBC>=7u!JwN+fGB7hc zUy15N;d0ulhLTi@-vCv&Tg}Jbb7e6nr(ZyP#NRwj9JSs60O^224zJlz1RyFcL>Twx zvSv*mm70NEb%1LBxIf_10c=9{BhFoc_p`a>WIPV)<(MEq%xzgM8u$37qU{^LKHVNI z&*XAc4`@V0&EN%ctY1=&uu`IKVa+s>kVm}l254qKKDmbv&T|3fEZ3RcAAp+6z4HaE zsi3BvwmVbtZ;l#Jmn{H6mf#EIvpdu1?f?gl0S$n86&k<~otT&y;|A0nXHT1s$T~g& zC8cs^;H(EEQ$-JO;1<&t4$gaZ0-@aF_5Ppxv(G@?@BIC;|6ThPNbdj(EieFSt#JtC zrD&A$B#k3rh5T+LGl8Wajy(9D0QH~0towMMw7mRDIkkLL7a_3B3uEU z9TVCX{Cclb0|opHB>bMV;z0EkRayGq#TD?>h()1MNQ-`>rN>SfM>_{%#MyW97r=H) zm6XG3%3jnDkesEjKv7Q>nj$MB1L_cxQ(7k>=>d?RwL3p&3*(FQSm<;A04tRW{)Cr0 zCg6QeJ8!ciDgv^z8?FbWJt6@&V!Nxtro~ehqh6RJ?Gdo!?6W!;>@92T2h7K#D!JO!6H#1?2LsYeRm)d3qrv8p%eCx%He8Hku|eFX|x zZiMT)(e#hYfE1!#URwil4;c`%dp*-YE(Ad)TvM*>M{!hC6j03F_d!kVHV1Jn08%`v~mkF?BQw+$3e%;js(` zdV1SfWz?K0AQf?=3BW-&DbTZl=cYPgOc%7$Y7qLW52S7L@@z$yLuyIcaM8&l38-$z zlS$yT+A2KY%dTgHZ(-sF7a1O_n(bFUl0Av$#y;dOo@VWMHkC-SodVJlgD zGsjNn;NTAK=r&_#S+#KtSVrIIZkiC$dU z0#vKiKv4W~pZjH1|Zt_C-*Kd!9UHQIzyb`u6? z1G{kLHl3*RG&O%i#6}HbHDL;Uc2lz~@o!I}W(vZEI-@VzPPD@o8GtKQxxoo!K(@pq zxj~GO5Z#M@{%=)`pDjSf$m;%M0g#c#2a5V&9SermvjuYeN?Ee>4PW3%PLi3pQX@2`7RhBbgXn>^h^AmKqj%yg+f4D5P?ALDB1Mdx zFW?=RJLhmAWjF5D!b?rzPQL1g-~4GQub_@b0BLF=j`X3eGg|PAIE^g~QFoGL*l`Ir zI~>t$3G>u$2*XmtJe+zdxKzVsar@v^JjHo!;k$Fsyeu8CMeX@mGd*Mb^5XsKOD31t=37 z&W56!V#ID1MAy(e*f-~=K)}Lj&GM6f=r@qOx)=JWC{7K#;t(84X0?g+FeZNd5@!G~ zTxSZ}2j|m6WPTug{ra`SlEZpNwIz|lHU2=RnrGGp$`z)^^7cS?Thbm}&11QC#1j&YnyJu1Zfz@?#5yQ;W%ax4sTI zJ634!w^XN9S2rbXs|a29kGYVHs!?W0<4vTAw1!rYmybaf zoTXqiu`c$5B=eYAK@IYixGtPX&qC6CjEJqrPIF-LoPY$fsB!K$=knhN*_C1nMTf=D znx>y{j>%GSx$H5myvuXMHXB%d?VI2`EC9yr2t_41j6`yD$JRV)EID+HQA4yw%AZ|- zUgV4L_(4;k20Dr~8SN&TCS9`+B%=I3$A8&X+Fdu4K~+WlWf!8dkSy#UyX57Md?f4g zy{{6HY2LV+xsyIwBHw>f0k6@<5Q^cZ1T{(CTy)uT)W00oD`E*ZP4T%jxGJ{Ib7F)C zuuS{n$k`bokwQ<{rSM~NnrG=_c@V z$uI0cDMdNrtq-gtVrm_MmhBBK1bwn*qrR=9W2oT#J_F0=(4+zA`Wn7O>@@|#k0l)8 z$ajX0ODMC$=m(d-g_xm709N~Y$^5@u?)kUc(KpaLWOkZF?|(7$#P=WAwtus~{eMj) zWas@0$s^_FVgEP$ASoB?zX*9e>>Pjpf{pY4&xAal{~+Y?{RKI?nA^h(3bOn!E;Sdh z!~l+vm6MbmAQQ6xU0hOt3P=iY8vliZWc%N<)V$`roNSiXJbV`BmYl%X%gM*e%EiNJ z&JO&E$BdgB06ucE@^G^8aame(TXUIl@v>W*ahmb)@tOg+LQby#MHnGBI|qOv@mQ%T$uXvi%6||MN8^=;f6q8l_)t*yQafWWTTghK~;? zWw?0P@X{Dv;_7{Mqop3Q!F9<2H)40Qr@gbTNHCh_Hwt=t2|qbfGa=* zj8C?d2n4dMU$NvYKzPSV&;gH4_uAQmZUf(~#H%9`gSiYObfKy4ZHJhI&!9E^SdO5s zgssd)T7r##Kukjxkqu#20<)je3)QQV?uJ~;iXSwbyXkk7DZN>tn1XDJ41=2@Q*8nR z^T}7!E_#_0=8mXwk8yFaJrw(iFPSp~Vtx0R77tYmT?dh084NS3jh6+Qv%ujB->wEc zbctJyVOG*!ebuz=xh_U0LBCxzJ};_^oCzOHddn8GoKii#9)_U4nzM+H_oes}mS)c+ z`O!jC44MRGh-B75Oh%woW|vwMdN*9tqApvKyaJlatV7A2W`=ff--$=2LLUSv*0wtk zt5%(7i)07?)}`Fb-2Dd=kqv4w3>pg~oNrgUmg*UWjY&Nbvu64fQq}_-R!2un{Ytn? z#fu&_ID(a}sFP$-GbPWLE-gX3%*VDuU$vdIA(<0JtVS+DGH-1nA_7A_9E(3?BEC&8 zh98YJ$W3&D?A6znMjBo}z!pcbt)gXOv+8!>ssmIMp{QxGm}|!#i(NspB#n>X2O_>t zk6B$Wtwpo5^^wCYvX!^8kQ%1c$2U07wq=Djx2xE_Jg1(dLSF!hSgu`1{;aE4)`o#~ zxjjBel9h5>ncwDKAoYz6r^!%eOlpZ(tjHb=hUx$4hGAj?#yAel3?m#kXUdcdQD%aPx$4>r}58It=xKxaPG% z1w)zV$j!=+E;mF7!B=Ed3V2ALb5Bwb8c*q$W4X63q?tSqhK z*g_2a%LQgb`Gys@Hap_t&APn^Q6e*Bdz-R|)kFF@20wCMTCwH#h3m~7Y?dYd1@S3^ zuLNFc-lW2x_A4SPcj}k!JZukG=jBxBINdvsYm^=AOr`R4CdC{;r?zEuh?MeP?1!ip zxZvpGA{8dd(b!jqoDc~2_E4fA69_IXaR@mtYN(#g41x)*6;xVA{F#&!$7(w*hXZR2 zh@lm4sD{`~xF(%o)%FCJpmdJN%a{0yjMEs(d(CJnxwuYlIU6pJ?B=IIadgU9`ZRfm zm&2EQh&IaojJ$WOx2>ju==;~}F6+hXZBKnX(ZX5DwgtXGy$=;^Jfa`vsGJ$+8jtXlrLqP0Oq_1cZlVNHaV7 z-xVm3j^R+_rkIdMcR)5+dEC1Z3z z7uTuYqK#n$GOVyuWK_e`a}b5*Tk?$aj)a`!oWe7QEfG8UmChVD7y_~q5qD{Is0gj5 zINJk@#LiR;tW*=jTdAFRJPn9Y@meBJi9xptW=<;?9sTtMuj=YpOKmTPzJ7#2%Q<$& zJc_e>gq2M<(aU@tx<<6%`E~M79F)AhVPRLTZL@cDx4@2ifEuFa#|c?BRr<9ALo7&5 zUY))A(2t_7;K`uIn2(__aL71&m{6hcPg47h_JSpF2Mu|dR29PWZ-~&L@F#+js~Zj> z5q=x8d&Z*SocQKdE=eFw*<^kCu&`8Yq)o#w3$)1G^w8q$eK6R&8Wc_GHDmOtyDQU!wL6OS{)EIt$nRHWgrYBq|Gj`u61iO(RiX>x zgx#DF)3t=9pPBK|1x;gwZeuOL%nwvBXPGOxh(6+uI+VW+p>`XiqY)ob`pDHZcj zNqKx?KaO#oUDR%(X_w+}Uq`#7e?-tkC@CgF+4( zQ<$@$ZQ^I`<|FfLW}tub#73R;@2eQ~g+&BaZq)SGfAeQcnDZ`;vHjJp&4H^VMIP zh>hOPw_17jI0zW==;JF=h1KV7ei3mR*G9Zltu^S2{HCq!0=pz<%`E8UJb8ZmcXcwn z=BEX|Qd0DdCtld9|+BxYRw62CH;fCvhTRMs~*C{w9` zny<*19^Z~0nNvEBmTxP3R75j85|x9#xPC9T4j=ts$kwQ(Ik=X+W^w%4;+O z9p)nT>uIVK;q^FI(r=!Q^&H5gbein~Pb6T+bZpCYT(qU^>Tyi=T;@#b4R9SbxD!)9 zs8vpCDX2Hn9;KJ6EX1qh^kV+#im~TiY4jUw?$ca_h~}&!%if^Ct0bo<1Gx!7sO4!kaF)%&Jh1XKm@5 zpZ?L9nSGFAx}R*H+4R$=KpXTH(&|;s*!r#G7-&`SS@}aU$y-C?yES(BUAr-V&Caik zs!jcuRT`HZwbZUU#@fb8YZ^MLBSWBwmku|=(b6ve(@Cbq%- z(Z=v5+Gy$4M~sw(H-SbGsW}3FFrjpqJde%CWVLoow7s zs7F-@(J4W2h1Td8f$%t6u$&NHyQUkpZ!x*awRY&o77AMT8~z@=4l|?_x2iJ4w(p5v zcmHR}&ta9DvM%#v3En{q{@a(?`L^#t++Q)HFg@=*nj%(_21$(G4${XbjycIDt&QI) z=%Z#zef61Y@&0fzs@!OMHBK^18Vq#C+ngJZeSvhmiNt=M;m~ z>Gc?k-@2&T3Tdn>h9@fvy@9|et;ZyQRPfs4vKBpomV;9)!O&a zXGZm5_K(+_FmDh@J!|!y+;!wktqK#Bh|)fApT+l+ zYl~H0(Tf?1;BRe)urW{3k}{Yw^VLKrPa1yheg&xx8~Fx_3zSJYA-DkBQMgxpn99g07wWSbkCkqRnuVCy}7ykU0wwn6i& z`=&#$^{tAdXGB{b-fcwYv-8%|@9)oCOL}(K33`6aCBKuok#94Mc8Ok<-DDWmz<=z^ z2$-EWz&zpXHNZUg)V|Iz>iq87o-Q~4n&B+#inDj@&F0Lnmw$W+;p~OJ|9$rIc}IJO z(X;fcepB9cgDq@my364Fg2M5VyJXbJ!fw$Fp}Y8iC;?hNjK>8*j9kUvLr=-??vn#H zO16ouT==$e3q4QECSNleJmBZRPn}-OU3x;|;D=q>Ex2d3e(uI(c~0p0?&i;Jibf`l z{=s3V;?>6_o%XN(*c+=)8Ua`?6O zS^uioMYjhjNR;Hd2J*rAnZ5@+tm4S$nO?Y;W2V9Ou>KYKeC@p_L@7mQk0-=HJgn&H zOq6?5_ici~->{`c2V7s>-1G6`?TXBk3I21v5C-iE2B9(O&!w8fpBJ}RZM6b{$37{s!?FVq$BG#XjD(w>fWkaHYbvP*9A zx_o!3DPIjfL$$J;| zglK*79pCD-pU-R>Zrsu7$w}MH8)hH1Kgbk6EY$v-W_lY4mwV2x@6$0E{f7P#$R)Ub zwLFzMU+Igy^3&>Fv!@~Jnct7ZM zaKUt@WT@zcd%$S%zyT9nwXkO$KUr^^8MSWnbIvo->gLyhPyRaQX%*E9qKDK^%u{oW zQ|LC$Ch_*Jk}~bp)=L5bg&X;Hve{o%-@xCNE4tqraP9QFELXmnt;pFmTBUoq_uJJs zwU&PzC7v!ZDCWfV8qwz}BK#h?IJ@Fl++p?hRE=o(I~B1cmOz!mg6*P5LAPZ6kHxAr zhZA%`-Qo@ax72EbcS8s}h5p4TdnnaiR#vaST7OQUkabu0d%(x=8UwK(iTU_mV71Zj zi$TNBt1_ab>!5eEu@-~J*7g+9x~GAy+W&Pk11m| z^Z9nDrF3X3w7YwnJ#&-PRq{JBwPHo%jLuK#uqJip=JPaC?5?`kG;Yaf3~HZ(wJKko zvf@7zxqX8BkHJLKPLsgFEEIo|hu{NJCh z@c!?7PyZkW|KkR%e-MLz5QBdZgMSc%e-MLz5QBdZgMSc%e-MLz5QBdZgMSc%|DJ*H z4`T2SV(<@Q@DF0}4`T2SV(<@Q@DF0}4`T2SV(<@Q@DF0}4`T2SV(<@Q@P7y@_#b`q zAH?7v#NZ#q;2*@`AH?7v#NZ#q;2*@`AH?7v#Nhu+5CdM0|Lr^L|4-WNxOq7LlPiep zzswcH%ErR+-@l>!MHaMt-}OvBDR4D_Nd^lAA@UjuQ&}7yo*qLgKYxjV?}*2P$VZIG zoLW=8a=NBdVi3k!_5 z`G+<>z}%`a>{f&Z`VE)AfM8@|F`mW>(6*t7xIa3)>(v-fY`zHiaAAu=t00!69EBch z*IQH>bp8bfpez&P;Y$EN14I&m55G(6kzanpCm}$`c*E_m20R8WAI~>>6R70q85r8# zkHc(a2l>4(%>dBD5crCI0sa&~dCd=-4u5tJ+Z19diw^4|YBt!v9^(>aHt?kb)ztv@&m#Z`lAmkUV zFD{ zpyLPw;S-tMH2_i$z)AuxJD(tLPFJ~|HiWZTOa?vyQBLUj#iF6VMo3r~Ov0zF)6Aoz zBOrJJP@c&QPQ<`DaKGhJnJ=*8z2Sb9fnQ56q+d=Vfo?Y!@Vz)J#_>cQes*QqVlVez z0uezUb|98gHvjUw1nUh{5Who-| zMqel*tJr31sQ=9`eRc9ctUZ7{gYVq~@(bfDC85U#qh82Um#3%uvmHOc-EE6Cm~m$TYyGrXXYu zK!U@;ktyE&mWl}=(Ryy{U_7AVbpkI$1%!5m7Z{1#?pWb)USc2GrPyRw@ z^EwYebot)ARus7nj~AW5>vj-#sW@8UA`>*CN0A61L}Q&Hr7bN#TAWR>au~Lm`{*im zybHmF)o?j$!Yd?@F^wq4xVoj?Zp8bvB|)mP@u>kYp3%ZTP(Fa!-q16Jl8oc>+14cc zou>mp+mCU2tH{$|Tvb5H$*K^R3K6hSAI&X2_&VTwfGkh zQW6p73;*z$VFU2{AA0FxTE`p?`Z#&Mk&s<=*uFGK@!c?_{45qhEOgdsb$567o3o#( zW8`S7g%%NEe+Q*sv;52_u5|)zcxi(2fHBoa)orYI_Ar@hL%FjqsNE5E?n$NSr%bZ- z5-IXv*lP5Ou-sT$RK@ZXY~)@H>I}nKX~QU`A`$YsPzD-XzNQ(G>Yx#XtBEZ6yh6yq zY~Z|;I|-P4oROi&3YlDo6?(8)4Cy<$&I7G?^mVc+r2F|xBHKU3hT>&-K6DWQBGwi{ zM5#h`sX_pl*UagFLpSUas4JC%MrN1ehYX4a`zj+)e&W2jO8G&3sRQ;_R&ZoIe)hIu zamuly9dfAQRg*<>spY;cf=(N+7QsUB2?2#o0y;rvKEsFOFIX-{Yo>S;a`mp9su)Mr ztNw_9P;DhzJ}i?+Jss!q3#i+7<@^}5Y5(8}Ly%Xu0Mi0&@~5MU>T2eh+ML)z&Z3f7 zyzLlX5GR8+c7v}X!HY!50}hFE@2fp33fp^XT9QCp#IYm5jMpiO*lM2c&(m%l1W_>y zH=!HEYNEwYR8`KKX*;GK10ba;3r#Smr@z#6EJFy#OJdp~P71QblJG2G&3C9Jo8r1UTNV_^>H zU+e9-H@&|}PSFR)jDh^YOIHR(uLOs|N!TkN5XHBGfItv$@QqEc2+*-PuTM>}e0=aK zs|5Cp7ry1)LZ*Q_4Zk&4d4TYmwnK4bWrFBz9%EK;q)8EF*22<~bgrUjL6F5{c2z>} zMpCc=;H^*681fD2>9F~s^SyWfwZ_Ys!(tpX7B}MqxSCDpGFie^VSd09a>A(F6g`4v zfmMU9p{^cHz$q(7E=_PqxaBJHix!`wDjIc5%~lUUj!Buy7_;Sfm~@+7Q<%S(6+7Cm zSA`9HZNrP_e}8SSih4GcH$|D=&_<;oy--LA#_Z1mDb(djvC>aKBQR#95kp$S=Q)f+!UG|e}2~L!&msiQ`^>-AtQhi@6^Hd z1A-)^P-Mxf!~y_JUvGt~D1+tQ!QNiN=OBqkNq9emf`f1I`)nLe$o53bLNg`q=dsH7 zwL!6KpQF9+F3o{$0L^Z#G>bqStJ$>%g~m1Jvez1;N*Cz{gjBN3GV%&E$`9*E_dv%m z#y+Y8h_6n+ce2#OP)QUU!1^hJO^J+RBvaXh zdPe65(*^ONRzi$y*C_i{w)Nbc;{3)kCb zbx+QI?nk?4?3#qVoU4vEMeEnWJI*+Y$0RC@q^9_pNRSM+p~%nl`v7O>ufCvvYH8mG@+DK0g)m3~IegQBot4+vh`pBa{hLZLS=f3xnj^j2|ir09Flp1FI zcQ)CiEaqZk8?>9b62r2^v?&N5bjpQY5OdPh)h?${uv06CcTUNuIjc=*GA4Rpi3NS} ze(i1&{UK3edDra13T11aEbc`QxkvSl%hoAriDnmmE>uwfBz%l_6e@;Ll3?us)Xj@y zRH>H?QXiK0U~kW&^T&_Pgi&8Qu!O7wLNAyZQ$5BIUx8t9Q;Oy#8^3z&Mc2~Qe8CLY zx`7$4QXol|4(^l#?7B9%=lSL3vNWH8nM7Yrt=?=D<7hBS6kHSe{VmQS|2Lxqn=ire ziSE^ZaZ+z)ZEx=f!11?UAcF>&;j_fwAQcBw{~eX}RSnfL>5D;TcPts87=51$DY&J< z^V`4V<+taXAfp3WM6RgE$r39_C1c)jYiel$t>Z;zv7q{fFbnXFI*>$VcnB52dNK0mEO&?rwu+oTur)Q>fM|yUrl7a(*qal6zdz-cDL(tsaJQhG_bKLj9gkoKj1TmAy$=89F5PAakfr`IVu(ug2p;c)Qv><2 zhQ^Czat#jT!}#nLNC;Oy)${=1aIjo`1Kb<5%~_Kckm?Tsg(zS_@(uPmzE%qW+XIl@ zW9=GZSptiVJVIKsuTsD4?9x)&(VlN%P(pI(xA$Pt22p|U+X%C06EhlKdDJ1R9#}CG z+pO&EnI231jpZob?(XhJUynj1 zZ#$hq=87~8c15|ig?+$swQHb)>r>AdQoHqMV>e{aIAZkEnC7*{Hwe^L^ zImm(2_x2n`9=9>t|6!)xidQa$(byrVw+tdsC7#dZDX46YZt;^a0U2i$;|WiHI|ouM z2zImTrQ-Ti_;miowR6y&VYWKBrjV@oigNtVx(y9Agb1+k?XIQW4BzknVB^5;8$z06W+-uOB0L^82u@ddEWPsN$4K| z$%Ar45|jchc9kD;g6JxMZ9A_tmtV!Cu^9IO{BQmi%pKUlWr%})wO<8e0G#51A1VS!m+}{dRGF-)YQO;9QSZQK zqiaUYW+n~S`xCv+?Qo`sb8%oiM@YzRb;iASL*^Fjk1#~>Sl~C|sj{D$bXK(3yv~hW z2nHQ+&pNDUi<2^QV^pYt2t1m>7l2u z*@jJb-m@Suq$*s?i65LhZ-f0S|JQZ7I*uwmpNVV%Ij=g@r;A~d5Ec!)?kZ}CZ3ESK z$w7CZG?M6Rwp+#!{rLRoDf+Uh!{AKyEOs&Kv(bmR>-mjdJ6O02s>EcOw9boLG$9p)V@x(JO&t^zpStJE>XuVeL zzQznx0yfpVr8%VskYK`-xvLUFwdO^GJ6!iacPs(%TwjJ#$_cYmL}3BSDWX2a3T04J zM~B|AyD@hmz)0KI6p0=xJr(aI0I4b1kkX)>k~Js|RRAZAOW_Ie9H zeFsA{lSZH5l|pFL*Z8da@wJ{(VK!@%gx~12`(|&&zL=6JRg{)0ANK_(B_{)Huon8$ z-{ZH9r7J5sRf$p;>L0)qHw&D;1>1dS1AO0je$J8(aV7dOga8~+B|aZ zPP^s0odiyV$uXtn`>tHegF zYO-0358Z3oOhLKYM1`1Upc!UB&GP~p>rsjG6A5>++Va2)a5V|q67@;B)yh(aPQ^(H z%Ge>Tey4Fe5)aMxSd}78&&`4GFV@yNm_QUHm^fddRbtVr&E0!T5{BX@H%T76B2_@H z-h_n}+6vam$Yv`GsU0(ey27xswZejyPa}*)lsA8QWe{ynNvPBuio~5g)!H~RL1>|6 zStYGsXZd>xqsp9Am-tLVG7H{e39xf3@5oY< zpTF?2uX!pHqu=%o<7VKlwfCXZv<2*#`BG+wM{!+k zvPbo!>48;lqZ%YAXc#H-LYRA4$%96|^nz5yy@BNO>T+^H2efwt1g&OOTkIU6+f>^3 z*b|?`VGa%FP`J15@fh4FluV~conDF>Yh|Exq$H!NO<%NTi9>~j-EO^w*2NSyown-y z3u#5A@?&f9B-@V!&aS?k8RIfRoxUVGwfrVzSppUh7~6XLFtXoZDLOv6nk z+eS*)@Mz$aTpuk5`hmYFxN~+`?S8iZ(OC7#P10SJSONw+-S`0{qiYqI zc^Pa6q_CVtcaZ&EkqvJ8EKQh-lgT=ADBBY0kx$Jo9F3tU##XUPqG?5Rt;Q-d zJ~~cRyogPpYgWx~d5?EoioSxh{i-sM$Dp}+vuKuZqpo+*L{WPY)ZnY!0srx&W8B#)u zLZtD?lxIXd4_%Q&3XlEA3MPZq>0n4+egK%Vk)dyi$Hf zq(Mg_M zVzo@xrMRM1B>gGYU!#R5+8_8dD4r4*c=?LuB~)tg6uLJ4G&kw3fuat4jnCD=@(q~u zh<jB{wQ(R2F95FdF>o$=i zB7cQ`DMz}jzOJHqOKR`1HJINL?1zxAv-!8S;n@lpl(u`y4>towhhY( z(X93emMM>dXi6?~Q1}>*+%#I!>p{`#`gbM`Ifcr&E|{Z5C6$?Q?@V$~Fk#kojUgv262)0L#?(kZK{VWznL30Z?lUqwzgXsi*jXpSg=9GwHB zdR^hgm~wXPP{xUITW3=a@W z(+}qa5Wd(BF;rjZ>BZu)2B~BOA-{tjUl8k^iH=~mo}uJJX%j8!<6tm;rA}xQlTcC; zqTcY(0<@13(#U(QljF!pAYAZ4Hd7{R2v^u_NL!F$5pClQ7wE;Npo=M zCLoEL(d>}gKB>sfBJ-`O>}T=4?7^Awus5jL8?P07kqK>>`Y4T+Trz~*x%%Er2#TV0 zVI)_!r2DEN4uOrLFo7>H?tVN!M7%n4VM=z#?&IjEro;rQTznD>D|4AZV3OAg5%l|e za2mVDRS+-q=M4eUWx0qR6Dp0MKZ&4k8XKrXvPU6Gccuk@qowaR@)!1F`Gbg8Cv@_L z9DP1}H`#4Ui;V&<4CHWOTs2p zR}}6k%F|v>l4FO6{J& z%q6oWidxv@N8`!Rk-VVf@Q1 z5ISSSXM=y+FNMKvNWv>QU`bxE1~^Ud->tf7A0$6U%AjDWtST3{pV2z(NC$yEfHCnS z$2+>I7LhX}VTQUK^*&#{@OJfYWY{s)zx5>FFu}7)o50VfzTD=JH8&e2L$RqWZGN~C$VCpF zV^s_>j6?G+I<%kLOSak^qnP(RUSL{_tixI1*U&aF_^dUkQMPP_Qy{?`uZt@^>|nny z8DgLhtSDA^qbXtCaF4WJw8TNH8kX_6Q$HZotTPY_M4H~agy+&ux1OK;O@8ymN7W%- zSzBA%Ejhu)L4qQK=2YMf~Tv%9A=Yq%KQ1v(8)TEV{1f)9Vcy! zbB$oEe6!slZ z4ULTBgqAp@3~McB$VR7e4dJ3Sm;5$GvZ6AStA+|Xgrw{^`n9>@Lc$xuR`ZW0a8J+{a_Hi&7UG^s6Hmt`XMbK8IZ_n@nGd+Q7+rFtaHdVa1-y zXhlg)JW43Q*Y7;N9F(=HXsapKziE8_2VXkWQ6DTk~^l;D;ylTcQws!q)}4-X2v zMHE(k6!WA}ZN~2i%xv)BCC9$1Lp0Th*tCXqOr^+r<>bTAX{6Jq=;gEk(Y#HHZo@lF zDi8!%fQ)!e3#tye4%C=G`42VcYx`T5l2&D_P)MB3`W19dVaKV?7@O|!nWB@-(_8g$ zz%=lEG@CEi;K0ZrM7OG52@^(S^_@=s4I_T}a|$iSDN=tpPp+`OXErn>~+2 z5Bp(40~#Otp+{6K5jBx`Kz6m^F}XEN?ar7bC^SrxF?CY+jK`zkDUQ#S-lnv3e6U(Y z$?MZEqihl2#R>tT{zIC!DIT#6I1ZbipI47TG0(%~pjPWkN@$W!6Gy|hZDdNeeFmn3 zPm`T|T4gHD;`B-M7od<>4#EC19C;j6;Whd>*D(QMr zrKEn9SrJPQPAmVhcgUQsv$SD|++>X&<)G;#s0TZeuNyyWIYU>;OQL(C!XH=)(9pz1U=@p%)Uiy%8QRstmw z3q=puMjK_b3o>RTU0vLZXt6nDjtWhV(y1UcG&0dLjP@Mz>NIJ6zjXVkeH?+ms;TM1eUbxMPZMn7M&VPX zWvDGY_zr)k8?o~xw$~9oznhBi%d^c-RcdSjv6-Xo+|YDG+8(w5)f}+1p1G5!2NEd|W5N-ihW*8DyM(|~I^}09RBE$3 zZS;IC(gnHV4@{0d-Qd`Y%1ZGzb}tM6+m{_LC<*Fc(~x|(%)jw}w;DMSa`LRT1H0Zj z&|P{=Ug(kX=FOX^iTq4On&noIQiTZ1Ius{8J*N%1@lZ9@f^zI*?9%@$EgjLUd+gqz z?*iC_fXgTY;V0-D0DT=LgHE708VyWdTpS#3k@5PVC@lCz$o~nPL4&%AoL{5M?wB%# z`Y_1w!1<3CC@w12&Vr7UyUpdV#!xQOi!Xlg*^egRSg$11oMSQ?zw+_Dh_wQe`ad2t^YRhWS zpn6vzdHrj>A{HcE$CRtj>9Hh29!>Bsm2w}i$G@D4I7I`0Kw-Mo!Om`n;Q8C1FTAb^ z@u2W5x&Z`MqS7z5yX#=05pYbbBK6YtL2w#k5V_Szt&Sg8@?~ypNHc509L^!f)aG6H_yqG@a$8zG(@Re*QcxZ1K{*aep*LI=cTt{ z9H1(X`_{lo0Q{15b#xrGH#w^9oP7M~Xhy?35)wSK$g2oUT|h%Hp>1MS?><>lr1gCi`Ld?r^+!^@#m z+o(B**$z1TXNj#T#0F<03h^dnFeJP#;?v+YNoilamK8=63Hi?rDJD!W$6DIj5%O7_ zwm8`9|4TbcQM_`wUfdKoxYg*U$LZp8-IM=%3fyqUp=e+R{in{9>k8HozxS#yhZeTB z49oC=z$68@&4^@6GMF!840IN;CRnIxXbjG-_~{+FQeu8bvX%L~Hf<#aLGW^LEDyx) z&F&!Vg4J?s15F4)$a(8E$?@?qI=|iq`GqsMu0s{zIk61INKsN9eSROXx*w^3iPFS& zDj4XUcI^Z^H{wQ%?*!HH0$$v{0(6)0x-EX-5Zn48Dz7KGX0QsNSBb6ufqiebt!QDf++jRL$5fm<+e)^f%Og5;ST5*h>`Q-o^98?bnI;^*0Yejwk3-sJt z15*E~wn7BfO=A?eC#u}m?LOI z(GTn)tqAer69jaLex3|oOr0t(UPc^e2M?*E2n7f1L~6>e;>qi0P^n{*p9n` z4VeSk*XnA;&|rc>#k+gZGnTab-n{{Ng&kEWgOnpLAiF0d$mC+xA2g37_paMM(nKbU z@k}vTW__?E39l%Mc%>L(1xM*O;60iuQ7eB0f(Di?4B^iX?<*`=?u?Ei&?&>GLil(l zQvAxGvIEHSE_YQrTG0D;bA#0sgSz(^*4!>Byr+$6vTl=u7Z|+E2(yYiNKcCuGQ+$< zu#zWWgn}$>ak4@{@6(q^IE>m*;l~Lrj-`cl3#ta8H3omA5KSAy6F3k@X`%^v+0ZO zE~hJ;`7#f0Kv<{)%Op)HDX$gvWecS}JQYZ3C=P;o>|TV)AvV|GOJTiF1a#anlogj(R7{iI+MZ&M zt$?)Yu6S@0j7OMI|r)cx+Rk9+mra5VtCv>g3p{X*aPXG|56lXMhmTj4P5s3%km(oKLgDlmi6A)%&_}ET)-}(67%;g zZ$eCS;q^l+BKUZXfMFYt49XjWiq#e6lAN%vgi}4ZprSzsAxJCMw zj#d>Zlw`2GKcE-%(y26nb~BYrkijJoVth<`6%-Pk=;x5SQ(zEuTNu*nU}Yt&Dr=r4 zWlU!3o z*G771XldbU!i~OzNi@%|0_$TRr}bCiws60h9r{FZNAw~pO#Ofd@|1?HNc#_!pbM87 zv6{>(48##C?rqRG`|PZDghN!H4_Di~Rr&u@;6uhf}Q(vyU| zTtrM{X5-*cjME3X0h$vUIrYS?N5yNyC8Hq9Fu;0fIxZCS?H8!M0$nqR1yjU2Uqv~s z)nw6%F%cBBWGAnWq;O+c0g1%o7QeUC-kf5PAn9vgiqY|#BNOXmYymYj>k z1;{@@LwBpA8c1{gi~Ggp-+=~^{$tQg_0l8rtnFmhQa^EBx(B-tE^XHO%u7df4YcRN zGV#2x?h^^cHiy;!0Unq0f7`eJe_1~GzZ&tlxOkblS$Mg){=Q|;TxL!Vc4jtUBjh4w2cL2L*IjHDV-6l3RyH1UZZ2*U zHdA(X9t(Chc4IRhc1|-c6CR)KNn=3q1D=H%h#;x*$mVPWS1U;f|K z4{~z7hzHr(S-?kA_7~HkF(>$m?A)AeoWOGko`;3YoQsP8 ztU$@g&hnowHuwK}vHwT$-P4py)SnANmu9 zPc1*}L!E_Tz(>v6_(u&a8-X7U5Uuv-fGC6gm3> z{)w=)k-@=48nKttMCf&x_HOj1bPA~uirQ%vZUTOb22^k)!$!O17grjd8DXTAt#e56 ziL@r1vMB<={;V;+<3dq_#1%wfS3u@CFW90%jxlecPnNK;n;l(>;*^f^gJO!dB$|ms z#6!`Aod}?%Xzx%nh9qSv4E2`D-PG$;lNN(_`qG4=TFa+g@Rg5fz|!l0ReJsvlQh9z zWJB&NEBheT1}y2Awj=qeY;`Y7%ZtU~;N8s{rQ5tzgAcgOeIyHX22D+Rf7aNrBv?^Y z8@Uv*2+6+7LDYZ$qFe0)G2~FrOFka}doD_V6OCtHj!;IX&=*@ZU>n@ZUV|t>`uz=> za~{h#`?pEL_jKw}>P$RiE)5Wc+QBhWY9a}q%o&qb>OCJ(9rE>!KT0PWl=jKS7bfpC zicri4lzJQgm3G|-DL;=Fc13`tTZ1qA;k1i5iP#VkrSogrzc!FW@9!+xPdwVv3uaF| zYZWtNE6g*f!OQ_KGd9)6&z?i0;u*NrX}Zrah(Vx3W<8}Zb;xs|zT z(oyz_PvpqO%2ou{x85v?Rv7W6t;aC4&Jh+d_%V!}*Aqxp1-#_g%gOp&tI|;WOi27Q z4ZnkZ#I-)DZ|74;q(*SyRxMx4_6ct z#Ii5jdn5~L@&(HGqcGYVN=<7>8buMfr74|xJ>4f<(3&a4nj1DALr6W#)|j&X{0~qp z_e$rEbb&pwrjXO&gb?*`(&Ptb6hF8u2%Ty+hzI(}(uCLk^px$!5t-3Hd?pH*rlKEE zg>B}GHWmG9fEVRFCW80+XP68kO7Ve_Jj=O(_g7igLUq~~1 zgo9-Yw5eQ2)zJp6-b>t1zq(>6C|Y9cZ@5m$|OMM8u7a;C{XI)vi??E{X3zTOA$E1;uPd69Vm1B`gvX zRIYN|h481hP^43Cj_AAJUIEoxQb8kC6$G+VcqaKPgux!Z#(Ircuj$jU5yem!ASt1W zKPkS;z@F*}@+Eo>DE&|%j{gkF6#Z`HN}9glr!-@*p#%R1Ro0;cS1R@bM;yIaK|Mug zZy%;GEF4+yqH~ooPUs9BC_2jIHrc$2Ico%$g3E6s+!-?Xw*jBnoIBurh2=!??*gF_ z5C%1!Q9sdQQAM`EHjpNk1%q~_)0K_3M?Na>B4t}i#ietl3f&-wdqLj9P-Mh zhY@2YCy7i@*eTFK|2i*J`6QdpL5b!5R(WJ8a&qeJpP?; zK6^TcGX?wj^V?tNM{l2>o-9eb9&cCuyPtn=hCe?p`0aj##cg-`18+R*M&fisXP0<^ zg5gJea@VgEUgI;xTDVeqf8PA7al^3<^_jop`C9+stlvyNdr0p9d(KheAnIW4Zi=@d zSxk2%vQQy4ecV)O7<;>=PidHQCx9LWZUOO_Opr><&>Cg--HvHnf6+8gS}SAApR%*H zqtVw1D5f0E3=(JRF zbm8b5PtkZ{C<<@n_w0EU{3*iWAtQa;S$dbptoY)U>xZz`d;T`6)qVO^LB=<0Yqz|* z$~YWz+R#N*F(!m!Z(iA$uMvP-?^%RfVnn+X?zr4-t$e$v`&finfU&TUy)a@nsF&weN7 zqEgR^__Iw*tXlS)gyxnYJjAk5eZ-Q4YX{W6A&DKX-`T02dA?;)192ja`jyUgYzh99 zf!EJjE&hlrjmr(Idn{xpE=-fzEO9s^EJ%_&6 zo8%gQu3TyzpGj$Oo{s-K;ctS2s5pF2kMIj`8>f|sl85skaGg(uY0yn8?<((i{@4D( ztfM^E*f&y!!Q?(`ya~HQervs##;E@jdv_UCN3^aB8i(NS?(QzZgS!*l-CYvg-8E?N z-~@Nq;10pvgKJmz?!LQE-!uC3xntiSJqG+BD=P_=s+u)x&ROewUh72!uNw`f*1@k0 z&`GDDrrttJgo^9G!AIEc2IVHCgevb5@n1`5x%?04LkXw`P@_QVxd#XR+V>$}zD?wv z#E;i|d5XzH(+iMq&hW%PS=ePqT~nC4gu_WIVU5khX)9vkyZWnO(L4I9oFt&3+tf)? zyYUP&a)eL@-@E<6HqprZUQT^Dt!ugb`Q>4q7M>Wps9WVOpQy9O^%XY$y5l)H{`JwW zd^2p^EcY8{W9~^-6kp--3>x`q7OqZ0Ng>xDXDwYV-WgScDuz-x_{2)!w(-utoMqKbx zB(H!ZtKn`6inS5z1swb(jHQ5jH>ZFVaS=rM;Ums=H=L;lUNQH(kb}Df|CwBACvi$> zs;kLhosbqGbILDlt(k+1&Puhq>Z;Fq+ti1LQKeYM}q2+>OlR zoS)4pWca#Pw-VfQ0(Yd7endrcL9rvxNA$s3+!GZRcjRk4b=9rd0}k~JbCpI!h+`q zglirnO>rc|<{x#_g*qQfvi-fvpH&HsgUhYm@KNl4~T!-HN z$;-ab;|PQ4CNA)%ecU%uOb(B~e-UB&&>gPBLsGx;siikinM{lNfpXtM?reHAIaZpY zmHd^+`BdDvs;@$N{fw_t%z^O(X~ z%5gGN>;VxpT%8&aR@d&te;s~YBG9%?GiORy=FC=gIaZz*c^Oh8;=)v|DX8Gut)P)k z7@3`H`Y@31Wjts{@5-69HY?E7(mO*HqD9C+@zIG_otcZ6EJnOMab7<^;GtKx2j80N z0pol9%uS7~sZwO??CVN#b4Cs0?Kl4Wbed$5YOjerS~+6tm5F*^wuUKJ?mwf^9hXZS z(^PE?5&^~vbqlGI3t{jcPB@+l5z{L=IB|-j`*t}Fi!>-U zn&ElxRU}fcaSj(5Tu2pY>bq#_Tc3z&cT^bsvh|hk({5mNX4f1yI)X$tp)!6X}XR{q~J{Wjp;FypvH- zsFs6UyUy16pIiqI>5=WL^CY&fh&%MfYOku={oBI2kx!1lsLv9^@mMaNs3rIzd}Cm! zG&1V*zD`6OUR$z%mhUoPLrTXT)!h{_C*+@z&mDT$vBLi>%T;cU)QR7u5oc$~DfWSW zyNWM8)*u-*Dy#MJ9@ceun{Ih?`qbgjt;vN?=Me`f_2VC$Qf;lZ7c2P+MW0`xD|Ej> z6^*i!X4f#5EWe*=ATc&-3^apia9wb2dj7Vwn%m>5ZVXkG`e3(CU)pT?6jcR@O`|sQMqg-r&nn^^H*VeCp=*Lai7UQiqJ9n~0&Q)>!(A z3C>;QB$`z|UE}?0!WYp3;S__pjP<$)snDmcFmpmrJi<={g!qZZidf4XH!?lXtYv2x zm?@wwrcK|z88Suh(dqZKobXU%2|KgyB+}WUj!W54{$?b_Iz?X(!~6WEaL?-7% z&0CyNBUH>`?Q(Lx1Y-A>bKacPXlB~)e1CYxZv0li#LC~0&23@pomI=wM9YX%Y7nKH zs-G$A-40b4OGKs+y->|kL^={abtEip%N>#bvr8N6T;?H{=y9Oa?$A@$ z$Gra8KH!iyhtOFT?Z?z%cR!kjBzQHua#CHch|Z9c&k_AbcZac@M}hE`gY$NN$q;nw zas~GbyJJz}jA3vw4%?=Y&X+t{(^I?2q~&ktwjXqj_j&rj|^GlT`Tzo>NKr?LS#ojxC zDKXPK2lBsyg}ieh?;OZG2lCE=ymKJ$9LPHd^3H+0b0F^=$U6t}&VjshAnzQ=I|uU4 zfxL4d?;OZG2lCE=ymKJ$9LPHd^3H+0b0F^=$U6t}&VjshAnzQ=I|uU4fxL4d?;OZG z2lCE=ymKJ$9LPHd^3H+0b0F^=$U6t}&VjshAnzQ=f5w3TjKY8OgZmuZ?EfRjlZ*S` z7URjq#r7XvhU_J44GFx#xBS(+O0IPnJ(vLEfB;Z&T9SNmBWYRY5?fuzSncS6?*j;O z$z`r2A-EdasaEA7TxS_gKXI*(+lVGo;b)vD@CzRLoA`gjrL&n&jIsUVKOTF|cZLe= zHekkv|KIitJZno!%h_skVi}-Q|8)`fDRQd~x&ur{Q-IhSFGcF-SqC5hX#ZDEF>Pi> z#&bk9K9GK)1LWYmz20>S=UvPQpK05yHYr(?%z*%58QIy{{klJZ+)p4M6xa`ZW+Q|U z1(dIEFI*Ya#`i#i4FJF>Wb-yuRM-HTV`&Ni$o0oxS^ zkwyUaBM|QwdIdl?04excPY*GNrMNW3d?Kr~qGHylh7@2a=eNVrsGtHbK5qX0`bJ=b zx(?#29|^E$^56K=H2`SJgdv82?{-c@|FQ{?+5%pIZQm@J=Kui%0{ zcY{PbO0C zas76EsItt?#g&@n6u7Ap!p`L)3;0%H4;=7ji)q{`gNmsLFVHCtBS|!d3l#h8SjQ}Wkm$S1R zS|N`E4<2pb@c^~r1hn+qTQ?U_8nwSUHXZ;n3ZfSdwnvbc4H%*=Kn5F`tzT|9m3&sN zU+hLAa5R*@Ty8v2$4q?HM9+wP!B!;xK@%Nb1S8&KoDp6VHa6< zwF>aZzIn;wdj;8vV85I!)`-%ke2BUbS(e0#p5V3PYF#k@<>lCw<=b(4d1W!DAgeCW9b$Qof`U_EF_@>s zxWDzJF__YnQrQ?NS8!t@frs79tkA2u$Upjl7E>T-x_q>^wVgZrz7vOmZkS{_j0^~! zz?C~2!WJ}D94pncE5xtYO7NoHdAnH|Z1T2LPRIue{3|I5 z`8|*c$e(*N4Q}UFfUq6Qi(Vwb+?ySaqz^w!bAbFY&RZ50v1SV{TC%!8oFskc_E2j> z$rG=aj9*~=Y9`mM^i(dS z9Ko|gjFv^hYNOom1t#j0rGb&4L188yd(GGeLl+KIBr+W`1{#%6ZFFecmEpwv`(1#ILm5t1cV=a5&j`=_97Fun)Y!jyP+L;6h){$}J+ zjFnfseqF!ZAP$^dj5Cu3ToUuPB#$F6b7zcbA>yOctNGSN)N>LvU@w89A zswi$ORJ$5)T~HAR7@65e*NB^pTttZDWD?s{C@y7+k?1I<$UY$M0twGknymrLm;)I! z77Go^n$lX03gOe2Pm8uA5Sxr2zv{LCSIlXT%rmGCgonaB`3XI?k^-HdwT+D!WIaIQ7f_=^ zgnv)>L5`3@@?h~5c>&m9UDT;$id1Nq-=CjCop=E|c6MKrd=31sLmfC>r;4}Q`Bndh zWT|Bpqb@&!zfN^H+DL&oRwqGNcgxN6B}v0eq7G>1S|q0}LF%&=iwZeIYI1VXzZ%l{3YbkZY?te$ro<(yg$OqrgL{2`EJk$9 zSNS?v|4pK^xUlY~YB6X726j2O1FD1s#orE@)w@?6m1nI{QK810xTBi*~Xr z*t2(iZVuy2ARlf=YttW#R!UBq+jGngv$@ZzbQj2ScNwYp*t-f%G5zutRQfr4^9$*x zIbhTzj$u-0w(r%9qFw|8Ws+3F7Ij5g67*%90?SnsCfOcvhgpM8<}xKfBgIsaD>Gzn zvweDwfrMg;#4DSH%LwS$^&gW`R!y(OpD>=$)nxhGPN)>q^GU?3j|FsE9k-#%H&iTZ z1y6$pCsVy&iFUR7RKLB7ZUG^8jK_muU}c+XE8-`j>c#L-y-!dT2rN8O_?BRwq)Kzn z1d%u`bEqYsM@L78eVOY#Fj-51HkGi3ZVufPbrLKEOgrw`g{d>8x8MUKnYpMMnw@r~ z&vdo^H%v!D+ygJ(7J*FPOJL0Rd+F^bCgRIhe6HQECc7Q|2xTA|C%M>eG3@S_LqhSI-g?jm*(d`+y4ysN8lM;tO^b_)fB`1yZKnYdzsE&+RYwN9j!^TpyrRlDqn0@TLMoQXqFes|T3=P|BuV%=ejuo*f=`|LNIZoCGFjVzV?Z zE-pYKVf)Qw2pB+Q*O!)lxHU3&!mE}AuxOwcI$e?BJ_E#hBQnelzz_EL8-8fAjekH6 zg2!qCQ#GkF;$fxz1&Ck2KSNa#_?sRI2HVqSga88uz~4w{Kyag4nQ%?e&CLyq0&eeU zDpNcTDQS@T-&tQ{(5eMjRak~*1<=z1Ic8T)079%B{}R3p`0)b@hbVzUeVdRE`1+%Qz`Hrfiwtv!;b25>-h>2 z!ik3O2B*snZI3T^zkSE64SP#G%{68}+GX*3qMH59z^p{Ft}*O|(hGLKt?r#g|Gh-l zT-c5|IZUrQ(E!E$ap-k7Ri7HL@Yhn;zGlNo%>eTVYGj@6bS?@qcMEg~&`)JFXb=gg zWm&jhzdL+)n$ee6R-O`RaU+yIf93;{Sp%Se-iOgvl`^^&z_wR>W7K}#SVPFrxHmg) zf3h0l`kgR&hs0&SZt@9;|5T~y{yP%|&!0p^ln#2HH%BHPW%*27x6zJWnl<|DZ5oSY$f@u*y>9%#eBt%AS@7);{> zk(Cu)b$z!=A8sxaAo~hKQeJqxZz~9a#LoJv1l*S5;!t5A&wv$3-vDN^r?yQ7VIb=X zdD|01-CABmZ)8Y8ECr}1zdQAl0nZq*pte{chf%9G2x>MIx~VA^aIj->*r0wn0kuU9 zS-^}jlFxRToi==|O1E_Ym5mWh$e74$t;w;Vr3E+d2avp>9|0Zlz!6>_`}Tn&lxh+R zm*f}_#))GyLPDTuIuKK#;;EIbH2(`EYLivEzQmKuWp_OK*9kK{g3J_drK*bNO1$% zao|$HNkx=Gr76*>jO&AnSW>>vk*)+%jYXr+ACo>}6yk7sCz) z%zm7HOnbWm#%?=g?33C+ECqf@u5}beLr;+MFMP{by#h+~XB@?D?MY>+AekW8(mm&# z4pYcpQ*s9Pv+FJ$MY?$~;a!G^ze@#1KX;^4W!&CsFV#kHt4B7;_``R_upz>*G@OME zgzRe?m+}ld6yk1D23KADMq*+R?GNB7K%bMU9*(OzCxP>~eL^me4Gn3mIvoNg)!5O` zLUIAn*}_H zTuM&q2+VCE!JC`(3lLsTr2O``oRc1bo``WuXT$=Zw?DV7Cn47kKDyHP>9n!ZUGNq- zQ1=I71BZ-k(|0tmR#if1-ZVA#Z2KNfi$Lrgo&75W z1$4>ild>^SMPJ(*OH?^ z-%6KuNp++3v#(IbPiaZ?A&G;y3yvsV(yjlgHfC~ub~Z>`*mjJqC}iM=D6>e3goqbm z`cP(!od+Lr9CKT1>&gwTbMh3}2=xMRKx-a&S5;N_W++lT4kq*Z$kSX_Q?6Lk-8-DZ z1yV~j>^G@dB}#iaYROH&pF*yTL|B$vs*?HQ;*J+wnnI>eSDVrJq%;m9HFybjJ=A`b zln}oHNes}n|s_Kt1k@IPk~aI(lO(s(~vX* zZ`S*qAt0mXB)4CI6@%x0i3#$BHNUgo0T}CPSL!sQDixwGZ=mu7cb){9YhdRj~ ziV2LKq85Yf9%0V29B&Z~izs{JmqxN11xQZ2)$qq3q&A9BN-^N5aw^J%Z2Ji|*S&6r z5+UnOV~)tCdtDLyARr(OEwmg{Ef=Lcz7hHSvbkj1t!>e|E@8xumjS}0LX52?B{QCF zn!#b6;nzCfMEJ;fx;9dV@_5!!q`uL1(6$?>x zY5OneKK?`$u1!U^fD$fIJ;jYL*DWMcsh{|GAGnMxE4E@m0(~7RDc|q^!`Lkp?5$9{l zt$JBep#FptK^h<1){f#=?!rn%DFK%X%h2rp85oV=&ESjI-$BLCQS6=@JMANrAe2Kv zN;X&zbSIBE?<4Ngdx_eMqgEJf(k#lZAz+oD{j+jQq}u1tpHrIsHYqL|>fFMa$RORq z$$R~Qg~(8qKdiB!5`?M~gq0W-;zthS6g{$|e`jTs*fdg+;{+9-8z-39r{H#DVq&mA zJAIcSF6)+Qfmfafu}@nLg~#ZO1(tt15-dRI(3RTUEQSjkZAwJ7Oo*lxov3xM1ZyNZ zHv~5^l`yRfqqC8z(M2MYg>V~)@)25)#q7T1C>DdGdJ9IFV4MnZv<4DrKsI?3Iw|@cnkG*lB=(+ zt?7Sxpy(;!v_P(4-oeKWHLPW}Ny=|LPv@u3kMDHA!ir=7=6a*;yp1%%>Ff-J%l+ z^IpkM>B}j^HVLi;FTE#XKqOm7-LuBttljy7wVwVl6anuOa)8M0)sLDQln%qq2H_+b z!BQ8kb1f+Uma$Z#VEiDZFj4jqAf|K&Vr6;RrHWWX(g|7_Z;V*reiSMI(a<^3df_ux^Cg0j%@^PN!6%*y;fj=9Z%c zxbtKTkc+IAuT`y-`LLwL#*~9Y6W$fxYq-#wN|(MsiJjAn8BWbl6_#&KM6+8NTv78A zSB(GpnuDcVQcBzAEWeM5-BfuE7}eN_Faphflp>D?e{|eed0r?lBY-H`x!sTjJE`Wm z9p#)NGIYKpZp++5o`-TX#9s)Ql&izcr2*@~dLb5Kt&iBw5(bwpAY~=Jq0Yc0 zz^onF%oElY0$A9Z`eu)@cStC1gT5rx7S71uB*uTA^3f8=`lB3{Iuo+sVo=Je-67Ag zDoa2tkGm0TblpmRUO;q|-ui5BgIA6?kY5(a%~9r7im>hQ-idP3hvG`S4Op%3C6^V$2lR_ z{~qFpeOV2hjQ$}}R0y&ZZsNVllT0D7Z^&}t%xynqD#wLrN5mPyXB?m*>jpSx>KGA3 z$ZgU6SQ;*ZWU6@i_;hPoB)z|uvbr<47=6d>m`les#q2+C>xUqtu&im8(c(uKQ~k`v z_>f4f^^YMHIi@|Zu3pKZ*nWo2qW<+&8kP4OZ?4bjBGSS9kp!mOE(k#t&Cd5p>c1#y+P16N#N{_S-b;lS=%F&7D-B>pBXU?BNU*!CFB3kmupGOU}It1hug#=$LYGok^;A8ik zCGsd+*=b0I2}*JdFq_-sk*or+5m-}`JWNbObAdSU1q%z;Pw)76#F)tM)a}m7! zqwCQu&~rX@MqiZrGmpWQy-3~)4DO9*z7RP8vwEDNCb-wO!cE`6FO3|Fs@U+pJtN6< zg0*kSz5muHtH`}<|5zMZ_r3QZTsL|DEw7Jf=L{+&soW_yoHVvk^NFJ2N{wkC`beml-QFJ2N*I zhba$_i6J`=hY1Ui8DY$7YRqNG%?-q4uo!ZgFth!yCSI^Hb8@hnv2k;90^hl~Sb+`j zaBu;U8Z2z={~r=BSpV;bn(beQ`u{ERf&9QopI zT=Eqs3cDbB3PJzX=l)n&nv3^-dye@nN(n()i^XF1^rBIa@XH-hul|O-fOIZwo^sBO zeqkur@+Xu}KUDUex;i^w`k4U_JqOYLYhI|}%ZuM>J8B^6x&;1a=ce9$=p^`0C&zm) zY^sm~93AXz2a#ss7!l}#d_+mrX{Z8!>hEeKU9S6sx%o9-_f8;As?#0@$U_4bh6t;0 zZ;_k_VPcy;4vYRZU2V02&SV$JB9f=s7nIyo@fE^fP>XmUOX?jQ$|0em*}Lwj3zLMU z3jMJw1~)OnbYPAJl=C>AL-?YRK3W8_gXSv}S`gxDZVbwH(zDMWmT?l6s^G=BANBJU z$^oet(fi>4Oue9*@GGHm+clVm5HgUI`75T=bfBcTmK#oi}@%7Bd@)P$y7kVuL8OQxO->xMP|%_xw&^?E_HokdA; zTlCZ+Uyf8pwDs_#V%unxf~>r%bunM@uxxz^So+`8i}XwKobZDRyfWSg{$&-rsfUvXm?+Kj3?9cu8j3oco8Iwy{@Ny;vm*Nn5>2%=o6d zJj4AJ*vq$12ovdKRk0`~GIY%HgH24An$5J+vYp*}!(rEu<`m4e`<4!_tJt@mkB{?O zyxZ$Sf|rdEh+R%_t!Ps@$mf=3H^ldKdv%f(V?r_{onkWcl0BoV)%ZA2H5gLj}r?(gd7=UkdOZgRvdVetNa@RZWjwlUr83U3WU*gxSuE8hk0xRP2vHiC1x7M(j8dTA^CvniUDs9?$%&D zKN!IogRavO#TlVM4}>p9D&8<CoD~1_e_yL z<~D!IoCR6755Ow;8Kx>7NTXT)EC!FuPY{N^O9@I&dKI*d7l?Y1^o%iVke5a>$(R(* ztbILhFd~f{567SnTUbgQ#i61|EQirOp3%Gvf*Q7|$w+JxwqAol5~j(!t8K`q=$}(?Mv3G!j_wXb{SqaAoE~xLLvaogeYcHLWG< zL4%On6(-&_pVlu}{5yI42WR}D2_yAx;a`5xVkER4J0!AOLoMK3 zPf#6-`HPgGcvQyWg|$;)Jv2TOpGg3C2- zvW%GgX5pM-kq$5W)PQ-o8G%rL6rM&Vf+l2QL23-q1tN!sidCRkawyb8Dn$J%DP_O` z(Pd?0WQq_$;&rm9)C-H^9Go_NZ52SGy)%~>hHb=ZFIyEMr-2(Cnk?N*VyVwxhNz-b zwOPzfv)Ojfnr(3VsE*F_Q868e9TCd}moE>*&j_n^0x94f&#ScR{NUHGoB={D|}iVcFu7Cd(n_D=;Rh{z*X(WTWhS}xDw53B8*$vB_!ZboRKyzbLp z6D1xHSh5S05NmK^7?cG3F%@eNu?*3>s!MxHg8ohs+bw-dyE(hs+mwcqYS59EF9zpb>>&#zLd#pZ1sJ+kjOgHxt{6j4C$`IeW{0L0d-;T zMl$H#R?S=t)nx5C3ZM)Tl8ME|CE=T_9K1n7_+pS1_lEtf*03j(Y9Nok-ep0omJ?sI z=nqawf}4i>;s=o)+pQ+oKnH!)Fa`W5d}R3b+$uFbaqSEhjsw~Jnkr<=gYxKdx;d7z z85FZJclFA0ti)lAul9Y4VBql@ABe>&DQ4Mmsae7g%K8}dXEOH2M~d^R(mq7{2tf!x z(4hV*06_@w*1pF2;s=FuNxw4I1O;UnZGj%{BQ#UD6G`o7Cb2^KMd(kpg=jqb6c~c2 zcL8{=zg`?9?tMLUYPA%(c&SjLg$?`FIQtw#he)AgY(1l!#3vf4Ura_Bnw`F|U;a(^eI7Co7T)W3~TOFilf4_D3 zV_Z_Blo);^E>PO?1`SJ+F^bMm$U(CID)4+gv!d^Nzbk>gwboi3ON3~j)4faH5!)?j zb{};PF*7O?)(k?e4WIMobN`|8(7kv6O#tFxUi7E!X3DRx*k|Vfp1*Wq#vqT=;WF@g zY0r9IyBVB?c`EzQDi`7SHJqv{`p~#SHW?kzOqec=I#>D0xtcsEv4|NyH;Q=)OPmF4 zF?#F;Ja#q2=|tX*LG~g}Oa$6?N9Tbp^f-y^P!36JgtucYR^p=BGB&ki(NoclhW9z) z)@QLu+Bv8P@@rHiSD!fHs;)x*r0acv{&kkl^6}-UcXMW+j64bF!`q*?uWwky9dB2$ zUteF3_XS^%i6&9^-p5UFkb#3NDUnJ!ronmH?$VY+TJKu#c-sC+?rgeM@5S9S}- zlA0P;`+rKbGdr@jVSl0gDqpyvk@cEgn8SA(7SA*Qmh#7grF zr&)P4Llda3X0!}hNHMb~5&N983LCEKx(!`MBf3V2+NFo)AR>1wUxjXxWE6+V0i6OU z7q%pyCH}d`Yts2pK{qe4_NqRMyOJHuAB5@?rF%)v9lmQvEW|S42 z|JezPTJikIKu+&yi$2WI5Lvz4U&nXW!@m6Wtxc7!B93uHR3{A;BcM?R5rMH#&{KvNnwMPKay_uBlqF74r zUSJ9}Xo9iS6YVYK`{s<%tTD-nKXlDSv~ht)$+fk2i}!fLfbXyaQ@{_irt#ynPC}Eu z(*X6cJQR1*CE__kZDi*p=h9djH}-x9dLpyyJFW zgLiQM!O8n_vHRgA^+aC6Ep+4cN7l)$m|MV;cn{*lu+<9ZDBoi5KuzmcvfPcI@s~hv zqDkVUqa_~}k;&6Z5VK-1(rDm*?)b;xrduISg@KQ|6E1$GeGqT;6(PE!p$=`83DwO( zUOjqY{m;YLr#64g zwtt;fPMq#_i1m4;4UO)}JPFDeGC4eM2!brV4Z~zI)#x{!yitY_WR08JJ!Gc2);BQe z*Yi;fB43t=7|ZUZp4#CJ4I*9%V(##i925T*?g)JT5x^e_L9nAwF_iN-VZ=2=WXy_m zWr#NsG`t5-FSD70@=r-gA(VgDqn`hGSNFooIm%V?w+9Sr4w?=B1PmOK zfTnyG81z%M-}nd3AoIq7nqLImv&!zbFoDK*m(F0AAWOUSk;3%()hJQAICX}hkp?F; zc1U%4f^}++0(yEI>>LF*7R3!Kj;g!c*8{qmnz=`uG1)3u9*NnFIZS5#qn6u|HaL|u zR1ZEP@>m`?iaRd+bb9i5zaclICSrr1=uIw5YVg({1orS#e0#Stg8N@G547UElX}1M zxIv~)w_m7DpZcxt>ItmG{tigR@|Z8xP0Mj*%tyideLY~FE&V_@z(wIHbtsc%&bQrV zq~4arS^1XKT$Cm6J72`)@$K{#p9JfyL;w|!+{bQO(%hm4;Qm`qtIyxff*5_Q89L3g*F2o#>VyBB&^8?&R1w&mUg2ks1w!9GA zK^8+gdrm8@9s7a-1Dum3!1jK@uKF1CNNnx;l*2zdtdv}~%MY`!D5J~l2^u0Yzw6T| z@47Y!1mkzr}YitP#u8SWf}u&s`vJe6D@9~PRmL%1KOyLt2cCXDgs zKBIX>Sbh;#c^+46>;U6tEWuNy^~!`hlHYZ$X|iQdwamswnAf4;aQ%+FKwnngu3iv; zXDO$<8#M@g$|Qt~_7bo2s5$ zCGJr@PBuajO&Odj$EWy^2{p;=lmd?VNvH4k)03ZOW@pt9Gq?jObgUm#3cvHr@s_5G z$sA#l{wVp3w*dRYHA;IlnOP;WL%q`4QIoY<`O{An(NZp%9krur)aDe}pV3g#rJwW5 zl~Gd<>d}7z!&3~`$n?p8mcQfE(ynuU*vT#doieVh2T%BxGeW#~MPrulf8Twv+^VVj5^laUV`LwLh^Ajs?s8zhVl%n zE|WtDbY*lg>`g`u7p%_9;ys+f!`)-;wi_p%;7HA7YyiYl=g9_f=9{+R+aSg&1cr5Z zDn&pA8(WJZ4ABO~r1b?r9RgG(`WcOi9H^efAbx2)XW;i9{6 z6NJ5_v2~vnlFyS>?5_*<5$g#o_$JqBC-thGte}_Mg+`^_31FF&D#>L(r`)15HGGat zz1#YJ=fG?671sB2ZaYFA@JeB_qYi6Ou-?Nm^r4-(Go~z+S{%`Z$w8pKOAm8=ZC-h& z{ltQauZ8U4z*NG$0@8bF>hy5r=JxXBb4)Xw%>|3uW38!zfO2Dl-Szk^nJZQW-p_Wz z(iQE-8?&SM7MXe5HJ#X5Tc4g+j~huMQ_o-DGHJEqeVQ^B7i`bKVr!F_jUQ7Sye*|0 z8?MjBlZsrhp6?BT-{vg99BXia{hsm%T4ydoS^N zFY$UW@p>=udN1*MFY$UW@p>=udN1*MFY$UW@p>=u0zQxQUgGs$;`LtQ^_bH}8sSpR-f2g&|UQ$q#!s>q`UHARdkA+4gE6dA5nI@Uff*XKh zjGvGEAwNJg3q?ZF#xVl?lVcY(%J+Wi2{cP+v2HutP&bPwpUx}{+MDdn<*sDmTQ6qU z`duAfL$vFtV)1%0kETp=!d(- z8e=rklhtOGahTWPL8&pRVT_et5&+YrID34&u|_6TpWJwPx=RA=9?7w<0Lw5Cun$;4 zU$^=`904w?xVPjtU8%euKjgBDo5Q(j2n4*6a37}z2L?Vp{$)W$-yZgJgoo_m+W}99 z18`dlTe1Pz1~?094}b^+v^N$$^eX@tv-)YX)HC~+G@y`4nan(X8wACnrKQD&YhbIC zNkvV51CXWyWpc5Ny}iBqW|iJSU{A`s=gKv30(t;|Xrc~cSk|pO0MdokdY*oYIQkGG zIR=LkHp3^6TG z{nNn+C?{TkqkMh%E8B}>fWu;K>KkF64}juX>V3mH|FhK(M`f6iX+Tpt2**aFjt@q} z=XL`09RZWY+ZN>faM2!3JiJT{48hNqQh1ozgG@S2xdpUKBt-!RR|`$dnC+F7XOnrN zyt-n{El!N<`iR<;-vm%=Of(S#NX_>gt{oMNx4o{P@aHO zMDQRAb0T&SSr{nY?&1QlvLY3+M1r-EhDtX@#W6&JF_9!kMb9ToY)2my^TLSXH^Tdf z88u~EvDM=Y4za_~S12ghIK~m<|crUPJ;%oPoxYM&Uy?TrIzQE~puxK<6 zKn4xp{EW19dYJ$NrKTKWPf#6zd^d% zkxKG{!+Wg;oyGc8SIiQ@c1PrLNi?^J2jt(x(}Vc_g=%qOa&#yUq@Z?frB)RiqR`F* zgI0dp1`Zs?6)S$c8A}}tm{o8e!A4>ToYb)#&bYGI(9qE7z1(%6%g62wofsdJ!~VpY z%iaGVJd~2;`bkMuRgC9qnvA>vc~)4Wff0H^qCFWl5akt;L_vfDV!*0bz8Ik`FSf4> zu+TJk;*+LVgCuECpFZ@x*(rm3)R58D6QUPJqCs)9+PYR~5k@$x$j=A6+KVO z<*wPp{3h*;)()nNLb#oLjqJva2T)}@d;0~A z%gAuzemO~*@P$_Tl9(Sl(V3Ct-NBHXr`1lpa#cM>XAty#?1OT& zVFLKKf&HU^05DB?eDQY1S9}^8EwJBLQ|@V2ROmY+{UydFjGg{T zI$nx|^IB6=Q&MZ+K&&|_Jf8r(H%PIvW9~yN(ub2o)IX$9lxu$Ms)^llLh8+jfCy9l z`yS{xBNA`oXUzcV_}~a&dK`C;tA}cQ_nMC{#3Y1x!N~~~SvdrP2)pm((wITd!#jy3 zb8)m}k-gNof>y=I57Aut&T|ZvBYwF?4Am-|aRgp4a8Tj}MV2QD%=hyk5{<|ui9&9& z5R{julVZeDvz3*XM?N(HH%K#0#CfG2?xB-4UreJ`onHpnBnHM4Sv+hoIGNEQ#U*8B zyxzC;B={@pw!{w0areFD6ciL?KF{|o-Z;vxp&uM#CK>~o{annkcqKzaZ;T2NBX_ji z&ewkoF6HrJR8>}*klnlC@6DU)Ea+Q$A1QuW@A^Okk zF`9=$Dbk~vKf;V@%bdz5CN^z2?P)oP4i?&||3BD!>!7;YXiqb^1PJc#8r!4e=yu;32C9g<+dbzZ(JeeX9(Er{{=N>C<)S3@WE94e1_71U-^#wqsJ zGv+9m3#r`4l`!*f>&-U)qSV9b>DwEL7uGr|&{HO*S81{aSo?HRF3mJS0kC^k7;n$+ zp-qj8KEinST)1hNv&7J%O7iFZK0FKyU)GX173zm2`JeS~>ijQB=f$hCLyrV6`c*g#E9j+*N8l+_jy6ZUef3=JC89{b)@el#=Ib#aO5#sh-} zlOVaNv~^`}A>P#-A*6fKOy9`3GP_4IxM_M{+-I05)E>tA@B*F+p0s~^I6ysnN- z3=LNRtNyCFs>+TuypLZ#lWiMRYXD;&cUS1~1g1d3egFhRz~&oqaaU1M@$(Z59U79o z|6B$^fDfmluP^^pUmrR}JbiOAy%vu@ivZMjb6hYs^FRVk^I@#aca^{!@ayd6s*$^6 zET=0}guVAZt^pW4EvR%E8B?5MHvz@Z_81W3P-^Y*#%E`-E6GyDAF;?ZtM%W!0|gR5 zbl&J)2qVW43$_7V=H1=hKz_alkKfH1C`s+@t>QTEDvpK86ALwU@Tww(r&~4cxQwlgZJ&#k?T45y|c5k z=?De9oEIBy{IIEo&H!!D+5FJybAPyDy*WaeVqtr`APr6VnD{G7P#0;bsRcLM|Llbw zz8OfQ;CMQ)*!|0$0Rf8adTKz{sg3D3fk2Vka>9aN;|4aZob@X_w; z-Jzf${ag3p1HGPUyfZ*M!Ys~>*zA0MU&H0NG@u2TZ%&AH4nMhsUK_M=h(8K~teFEr)Wt<}U9LA~(_-M>Z;eIko3D5)pxgu5Mz3|fQS9t?;aVH6L4O&WlGlfR>JJ1kDaalJ2Va$J^eOp{SC2IuF0*v!EG-cHUZ0H*@&->zV| zt^n<5@X6^oUF{2;tr)!HQk-S_^F>6_vhXcA!J0JcEntzSfW6YeP}id?C)qvNanPYw z6=beNCGv0(KtuHbD5hFsZ3Q9T4Y)3wn_z;Pc`0AQ@;#_TIQ;LfkdOyN8VJ}2-UE!| zTysC!pagdyL%df$C!--{@GbaE+brSz;z+)b4uZ#|1%GIZ_-R6?FHq#{r zJ2{a2(ecn`emK)>qNB@~t$4HK!osU_0-C)I35H=00U@P+I0+70F$~?XfK`7+fn16u zDl=N&ys=?=b__Op-~wHY7{T{vJZ1o|JXthjV0te~T`6SzU79z5c zlt3VXQ`F&gQlzV?u3k{}M4NrH1Y8Mb9DOZ&$$hU`ye@t(061lBASg|Sng5E|IErC! z6sbWLo{+ieiiv!-8YAv4E~~Fkk{vb!tB3Ej3JnPkvtf)w?RYgCypfEEx;ceLuNJz? z#2_ef4u1XmE7mf7+7IE{T`^uVioC?CtM8Ql+90aLAMILWVR?CEY2ptMJbd`@0h8mD zk*U(W4*q9_PN%=%<|G*%5)7vtM^a=BO$RQk^3x{^YeEM4({^4~rmgqJzhM$_ACm^; zVlx~UKe{&spT0>8X3;GRob?7AVA_!OKJ7(K7WDJs#Eai{O$aca{&^-$)|IH-@Ue(h zJ1*(^y~q?1I=UN6`+=>spR`{hr8+I}fUEW3$25swh~33`UM=V*P?`&m5sH)K9`F$B zr85$1R{KnUeqN2!u9iC8q{T;0aHjtG^CxaEj2cyG8l0hUT#3<&Cq%kL7g=I3^fuyw zNOEJ{f9I8Sbnwa~cR0abdj_M~>=h8C0L@9GRh1asI9>60wxSSNqQ;6uIvck{5mhm7 zbDb>KS%yK8&W40p@&#dWd~5=h2a?z-{;SssIXpL{1rjcXg>r|fnQ1#x?0&U&9r+%w z?=4tW6VxW#>Lhp?f&GJ;I2khuR=Ysgu*I3|bxeNet#S5lDO7UY&rgS8%8d17F>?3l z=dg9Rl}7shgD=`q#|p5eN-#99&-SPAON)!4?rEjNw0lYgD6s??vqTL8xuXA9 zp@((g=S`Px*f;)Q1KME`l;Uz&L?K~oZ|`P^J{B7PvKwA)vdlCTwW3X$oE+iHjG)Eh2mqEJ2iV(PKn&1@VnvJ&*EC zp*(-!X~|`|Bhb?O7vh;fZBRW@U_{nj1?WhdS5;P;=#e>O6-Oe)rb`&dD3e4cTn|Wi z^WUIC{EEJ-G3rF|Zc(v_iDGM7oScLVJ;;+}4Su;ZF%K>nCmAf1h9Ww_^GW4F3B=!X z4n{~Mzwp0h6-A2wQ26o!G6ov%Kq00#Fhqj8FdD)B3hYJ4Q4CD{N5kBa4jJ9Hw;&4%W$y?zM`8Fq|mq_BJ~I0I)wiAsc?o@nv|GMG@9x? zQ9|zsjp!$)jU6<7d$aeNdh(2V?qi?J^>mTY0S=W z)dOij!1PBAb|FCYV=MY1-0pYF#RQU)7&_HRd+W%ExpvRMV)X>PW<1(-i=w=36tOtT zx9-sg8SXo)d}Nc`ZW4!QrO=Tj4WejYws5Y^k|~9d=w&C8x_b6#TxLOH)9;4xfsWnc zAhPeXhg$?uk_3y|CzN+-KXLfx&X4@qK8B838W@W=)RB??K?*}_ZX8(BDVv;BCHlVC zk-^Kyomc@rW$5Evj5xHQD*4zUC=7PQ=yf=^Mp}uLuz_@C0@zDAqwYJ?Z zNpy6yfm@VSKUW67UJ?`aPYPyd)?ob?no+Q|hMu*Ws;XiHfADFuxO!#IcBBGzmuVBD z5kAwz_bIX%d-3;(h!tfgaJKPNkjP6|QFp+e^>3qxNc z7jyJuD912C*n!`KsWX~7!#J&N3!_jkMyi6Q^HzJYQ}kKl+Pxo6p%VFrL_!~hBWBAF z6@An%88RG69Fj2m)6ktN`5C7SK5x0>Z@kVAG;(8EB*tCEO4Tf+N*+0~RqjY%9g0|% z6kiQ@x{GQ3RQ-nfzyXm{$s@eS8!&E127t@Fbzh9F0#|e0QQKhA8%Z0@IYb-}l|~i> zd?N%a2s<+dYOxc+&X&VcccsOJVuXb}Ese{w)~p|;c^n!l<>$|zQ`1S-29TnViZl^q z-LO3A1BJ<*Cyx}s@szRwp-u*Ozg%dM9Jer!!b=yfC{BX7v;686t5%sfiwM%Ul+^FH z--PNQxQL>wwzSGwg(4W13KJAHEhHT=nFRa7N_nCDPqz>_aUGU+%aAE4+YnT^!-$+n z(4^>^RGr|JUCIX3^xlW|g`GQ?(gv+xhZ|R5LV48r9Y5B*k$#cCTLzUzVRG`5;d~1A zV@-{gBBg1}uaQ#!8!Y*vJFwC;wz~h)9N&zaU^me-&u&ly0uOVhQ@Wv6^jJy-6;VV^ zR%!aFi<%G@;*x@b7Z)O2I-x7O((A8}jom(Sk65xG94wP*qprBr?r96v*W$*x z(b5n3Y_|TiVUL_d8w9&$9knt;r%hu(zBI=Sv9sN9ZVyosh=+yuVWG^CLnU5qrQ~yA zazov^i?)R?+zy>WqA<0waedW2vVr{%`SJwE%P^@(7=*xGJS<#%!?^(nRuT~Bbd78YpeU<}-Ar*zZL@O~6 z6Qnms>v^=2s$^qpJBejglt_tE!c~hX*y5ukilZG_X*9n1Tf*@;FYa?NF|`ECmbvJ{ zk0jAS#jFw9tk`mY+syB)e1)%JUb%%*p2ZN5ACn@v)*y+lHHz~7MtSS3QQx3ktmwGe zU}^Z;Q7a-^Nuoz|tSi^vkcLFDD};&tNUF1)H3ksvDiyuPuwCCqi3yafvEY z&w7Z`G&MEVS*%DG{SyN`g&;>7Zl?6SNMgJxLMleCoFxmbSu2D6t{dB{Piikii}!Ng zW`(|bnPA;eS=U%!&n81)REeR|k{D{%UZ4*z;mLX$`Ffrd$MR-AEJ8~sz|)ia&pS=g zh-$YfDYR0N(Ka7sF^9j5ev_6U>s9sH5V6PBH4W!oFU4I}ed8t}XAyykzRKDIxled& zWeW)Rt_;x#6Y-8*g`#}YEav0QNJG-l#vX_M5>2}+R;U@#B=OfZZ!R%TQQFq}ATvox zwuv^pr`lPN3obdXgIO2~pMaN3I*YUkP{Z#7q$T;X{wbfV2X*ai_QD6!h{AbWvD}>I zu8hOQ_`QDw1>QLbIjsnon=xK63u3TPWFO83V`c|>c+s;#cguu7U4U~UW#D6Ux_5^S zTFLzJa#3?+SaOU}SJo)#v!i7QO1rA@EMv=yt=dsS5*k$mdZzDwLikB05r>qEG%Rg@f6ZS5;iWfOSg<%ah3jkgo z!C!c7z1{sHPSNRaSpM$(0;f_Oxkc{M^%PLVR~0Z4u2wK`2D#1%D*A-E+~8tv?&J6o z-VlUVP`|~J{BgxFbP{+2B^5ucPKsM>SS-SLl5cM|_OO5=c;$44P>bzK1CME8F`)oY zE6qxlboF>IqwnM-Zk~|u&!Iyd^yaoXB(hi;!ze2PoVwFWEYZCy1DYJ77o;xky#gCt z;xviNvN0pQiTNXW%d31gZmD&?Mw0Ws60v(E*A&sITy~SrpUpRUFkeJeL?5t2T>w4%SfBq{WKuxPsB=*N| zk5({|0$uT^BLg)yATxLjHiS*f0qQ!bfkqt90Mj*V_~19J04^Fn;gl=Ic#MCdhtzW% zbf^l-@sH{9VLo{?R!Gs@tMHG`+X?0-ChGukzRWBojUQo~Q-~71$`@-sJv-aBx{e>l zEK|dFm#!RE2zzg6TwBV5`+xx$@^{Ji!edV$%Kwr8f(3H@(LEJ(CO~J%_1lA2g76tg z`vDmDcYqBS5Oov=L&2eJ?d-I5Dn9~E4p1;)&?p{*0C`RXCDt>8&0?s|tRH-gJ_&2u zJ&8RSH$amxo>=hUkJ?k)&Z+Z$r=_i;qN1ZCPt3xi^dVz2UfkT8P{PH9H5BOgHcI^W z#uvIt0`63Fb#=A1X-mJKoNP*S?v0U&=}MR0gV)@ZJ;#+ z$LPU4V0;TS!J@I5hQ`K}ZW#hW!39d>b{k+Fv{z?Ck7zs$qXjmMP^w#V?&@pFS~7g60Z0i|dwp z|Ly_68<$pAMv4JNf{I^$XymK={V5tkJa0t;-GhIPNc@PsNl(Bg5Jt@rc<)ii>X}o}l-c@@??+eb>K5 z3@?6W^SRT@9zU}92}9JSq@}-_+)VF3c@C8tMj41r5AO-D~}p zTJLjPAx#Yp3%TDP&AyG+H3R{dfXFQ$`s2+x!)?0pE99e}&CHYNS{*)+MSk7#KZ24BH90J=i@)pnnB<%D)r5D$eKj4hqO$oJ0N>I-`O)-J+91c3b< zE(cww?=l4VHC^Kw95&rxqaqaWKuF>}m_()RkNSa#>e6Gc3-S%>q~#SuX$CTKZIpd% zM0$CTPzeoV^#lFu(JW$Wg{-ZBABF{!@yMV)s92~olYX=y=KF?KpUckijdb@vL=m7t zNck)Dw(LppgS`Q?4F18O+oQIBdjmX-D(vP989W}fl6x@VG*aIA_eN}Ld-5K&h`T$d z(e>O%XGYG<8l475n^l;%ZJp7`%U-aEXE(R*jTapYTlY#-{HKb#?@SqKqa1vB@q@9hf*#1-G{`s22?cX2Xy-EO1vtO z=2ussxit4ei}*z7$-EL~Ps?EX3MRpky%=~}N;th99|VzE9vD$%?`Fe$^$pj+CQUR0 zR@LyUSKX?}mtkHQ4{Q>IzIyV%@!vr30H+sXDDsq8l+4&T{`WJB=!8LG(?Tt4RCDDp1%!}E$8(>lTKxutx76u9ia5l!lPTzXIsb!tc&CY(QV)WHZg^7Wl zetZV_3o9udpSCz#-~J`2c>f27QTsjhOGHGL&c;CHkNilKpNcWkXJG{Y2YJQsr<3`g zykeczJ~-4PRn%)Lq+&n{>3t35Mo}_QkIu}^?S>v(T#2^0?xGYjW=*#S@=Hbkij5FV zx{=({qT0uGm2R%m`~<=Z)$xIb#*(D(K*ui1HKZk_7S5!JypQ6GQM>nN`c19|iyzp0CQ>X= zh-WZ|Oxl;j$m8ZM8*<|; zaFk%TuOW6it%=Qqneb1{$p0Z!CN%$7*b1{NY`?>o$e-6 zCKmM$i_NDkf5Si5-+1qhb=iH;;=7R?UbXYyKl9>NR^{-4pAdy>KjDV1xShUxxO*p0bW=+8N;Tng!|Jsr zHcHR$#m@{qpkh5IkFH=pE+%t^78n)#UkEc`1=C2o3!jq8lZ;;2A-m~Q5CcCNT-*Qs z>Sio#3PsXJAXYaJ#_~E}cAB8~u-xC}nhNSHsIcgtC?m7hs2B*23P@mtwLi~)X>HZX zl7EVz2n$GcLH~SuX8b+=S7O_6Y;AsG8vBGTt!n*sJ!%=e-Pe3#C1B!Um+D0Sss;33 zs2r9oy`l<1)t==C?Nktw3#w}7#R0@1KkII zo(P{AFHiYEDjVFTFCf6tbwmF6Ypy1k*ijv?xG3gRQ)8n@A)koa@LD}A2NzhE1*nk* zt5s%6o3=ogiKO@6V~s!t>6vjD9|)-+sB>zm4G~BNIm#E6AXlBJDbf4)bOQ>c8!tBg z2kC~pdiNm(F~zm1g#61@33$q?uuFJeUsl39vlaMNH5H!%P|NcLUHBKYK*zaZ|t6%%PhrN$aymTn}&$&hn)jzq0eR!`9 ziLgK66jg=CZk5EQE&0DS*yzN{=6-wvZN%0i|=P(k&Sqa zgp(s?#Y`-EYlgDVsr7GYsIX>sB+QUFT<>t=I+ImP^Vo;FFer?P%*Yyu_PUSr^2{Tu z``yCisAa{tUJLaWv=Fb!w`QB)@+EQyWTbCggG%%U;hnZY588*;sRxO6x)-h!xy?8l7K^q?0GuY;R6)TNy#%rN;xT8V>>}nqx z(8A!o6)W8IzeQM+j#y#}Tif^=i&ZfEb;@09cOb4seKgxTH{hAd4LZ0JtqH}%!rJaV z49bP^_nZ~o!#`_+U6g{_+Ig7H8R-e14<34I+%GFgyrvFv?wLI8izToLL#u0SYz+n` z;+9Zq+yZ&h+_I>!rxfnnmhewUyCL3l$)D-Y6qnG zqVfDvV<8TS4x@@T*47E12g+%8WuLMn6ZPL}tX#V8KXZ5|9Tp!_Sw@2~F)_ap4^UT) z-`=@xL%x!-D&WZOqO6D~3~Ki}5$m0=TII74ax?vI@G2_0)`QXaEqo^x8VWgY)0lwW z(Dbh?EF#suTsru<>E3m|uD@`{Gl@pIBb2m{hTlXufA$hNnyc&?u7RNr3YB_nq0jq%RI*KxSm-`baA_aR zf--n#JB|w1JvTiKEg1guPHrC5k-xyGSuM70uxS06DT0_|8F2lX!LWOalfpX>2wcmTmUkUO@&oIuLTz3_FMPPGbDTkDb(x+TeQ>o2a#Yc7(GM(?axm!NUzBMH3o^uy=xSVb`)C?#aYrO#n>wTDz5L;hEoP{mPZ|cYDsk_F6f$` zV^b`G!UFX`gtc$|dO`*rPL+5Is>itBL@_2C!_cm++0M6CypX8fqD$m~D<7!g>RGXG zaYFws1lbIE3PD1ANb0pZ-G_ctugOb@8Gj@al9n4nc1ahANKY}oIe}*gt+0Ph?vT>A z_wOmlq;;`ZSmkvy)J+tnAiPqb^^svZV)tsvcqDcZC)JN}n@(FV%}cZt9Zb*RBuiX0 zIl1gFT+zvanFq#X@By?$T5?71FooYY^vMzX-6!W4(8a%ax37HDzQ69InBPqNk$Z#^ zdw26LcW36OQ3(p?+Uw=9K`{I0%dH@?;iIHa`E%T<0RM4*oQUP%lK+qrSV)Sp1~f34 zHfw16;SOHm3bLsM8pn9YLe}*L)W-_mTJfK)bxzQ%o!&Jr2Pp6{i10=sB=$ZYNpE=j z5ReDoofQwVp~)|HQpppBW?fB6#8A2v_y_4wUPv_+A=JY~R#EJrHRJI`9%;f$R5yWx zb;Hcu%aEtcr@DKhxUW_R8&XJ9D7v&nt9f5Rpc@#yer3 zt;u0&$t9Q@@Tj$RKEiMHCLeS=zK6(9_+hLlexX$q3~AAEoQmEvEPVW^BdQz~ZEtTs ztvJ}0J%D}2R(opNZ4<%TL!cwfVEiiO1sWR0#X+-T94s;v!q>jz8T3PKnUw@jC zk#cbObW2oiausw&-xGkqDaI0Gm@pe%5X|)5IB5Tim!m>{8`FaiuD3}PR3KimGxbTZVn^sj^=1Q7_l*^fG|Yu^Hg1G)~txCAII zz|83VwQFAw3g{jK%e#&etrza;v(Maq5%XsqfOo!hsNE?8RuRI=ungKD1Ce91O0m>PEEgOndsrKUtoVHgOQDAvJz>lFT1pUXH{ z;3Lu@bOVutxkI~O6fF!1S_m&p9%SWjC2f2paYXw+;HkHqU}EGArG^LVx&J2U&?`4M zuXhKca`b{<9q$B;uliG|;c`3bhn3G(8~oBY(Hx(g48jEx`5&6$I{hG`N?_^9SAG<& zb_JHMcE5!AU^oO`S}hF?6(R34>!uPm+%P)$EihFK9LWIKDmV{QDbtZb<~a8;!dl`7%LB zmCfyR71g&`2fYATr@92RX zoDAO!_Cm;phKAVA@M_Hcl=6i>IRQ1O7|m9Uu)Z2>4=Swva9EJXJaa8=_EX*_+HyM| zZ!gHB-!F7bJV8omXlPjNE5OXm4BtEn!aM|vO3J42&k?ol;^fco(3Jhnch62 z@hxiJy68|as*2xj4|P;BXd?AXc9O_%t zJ#1EM`EBqKr+xC6{#k#wC~k2kB@sKS(dl#P$N^HtNQz=X^|TgMF!N(U{?zV2g$G*q zExkK@i=F5qHl(fKnq%fjk9si4{?)A(`a~KYc3e+d8ZJ6WU{CYhg-UIFn(Y^~hBEV* z;wyow6^^LivcDo$wCxrPFm#Mf^E2JMPfb+Zh!LQW!V+OiXJqT{lOf1ChU~eAptTSe zBkv*++&g1Djm^-PCx#9F&P-1qpIn`6f}O2t{)SqCf(IUf2bf<@sZ_`QR*lp>=YRJ@ z-#}i%qjaekyz%5ooADLGm_gqNF5a`B&j2as_0Tc}h93Hte+G&@1kWT(^^?#u!N~oZ zb+2UtJ-Xx1Cgh7^@w`cQsJ~PV@2kN)&j&|}{TOGEM|vJpEgOOncd)*Rn%L7%Af>*} z$^|z%==c6!^Q}3hhg#Infh&=Q)7ipNUmhZI45=?N%P=4` zScXJ8_^nuwL}||~8ulpc_Z4A{y}m!9EQW!Gl0TKN%r;nV&Rr&oBz?^{d&m;7N@S^Y zpi-|eKZCR}d{H^HU`B&A|cZq}zYOfqw)v&7)V(>niBCfjAvrdVMa{JI^l!sd zjEs&5^^20W)gu0|M|{BC2a$5%F5erGCEBx{>TC)ej7YySrC|Mp)|^Is?;XO@v?PvvVH+&uoEG$QCA z+*Qz&X%KV^Tx^0$)>?#JBM;0B$A5#J(-Z71QYOz~4w}p^Vj-hMGOOKpvoHGVD}D4d zJ`4*p@k!vNiO@?%qye|EvulBj1is@O&eZgpcL2Ym4m$lH>6XuXBKjtjn+Pqa&+ z-k@BUwYBky*?sP<9LZoU2E77!rREKq>$kSAFUe+t%C;zSHi3+96wGX?ht-c4M!!`d z(w%Y1+0@>XQS<=5b5r3~%j>MhRC-!k5dq8}6ts8X?C`BBMyH>vHu#}t14Y&_b_}1t zj^o@8X~-xnEDRqTUZ{hj`3SVxk+M5R9FYvdU~)05f-l1C4<8@jandPgOi`)HhQN0> zj;J{K{iyvZ4byxZs6?()MlNwvZ7?aU4ca`TQHn@mCQ)Ynadi$0<-g(K!e^;*$HWu)pT_^Od%69aA|8v>>g@A})`?^Ghm9XlLp2f{;&H^i# zK2fh`IlO~S(fb>${;o}dDh8*aD^l%HOJ1^sB_iwF0_2|k6+-D9bO;%ZBWtpNckg)p z$iIP!IXB2Yw}|R%&7;AVWLv4oJ4{E=eM+LI@nwE_Tvt&+yU`?rmyo$n3hQ^E43jE$ z<;^Y(Z)COMa{98B%N)L&#ArOv%H<260g&U68&b~Yau(!S?I&}AOX743NU4@&(IcrdkcUx%o#ZZjDY#iT>5q*VK7US2xxx z+fKeMeh%9s155V$YYAInmJeMbr084o_*%Gpfp2=soZ`#*U2*)%(3!;s|dSV;cf@&QQc32D0ic2_~w8kcjW?kclRkH}Q8` z1SdjM^guvoLMtx35RB#Waf?V6&mY=3z+A&z${R~oIGl`hY?jFbyO29eS5I%AY5SZ6q76lG=X{DGVu~n65?QECkY}qNjyzCXOLPpJxbG} zxUn(jWc_dS7ZV~LA*5)e0{(jhVDA6({gM9<6?mMyy#L~0Bjx-TDH|6T%YUW+?*E@s zHgL!1(?NGKwMArQ;rV}ks5#ky?v90pg@crpgZCc?9w!$m2M5<{PEs}w?tf7b{@3UR zIaxVP*ext9dCb{)EiJ*1b+=!DTFGvYr}~*5T9|#eUxWrp;fs`QrMzlANl(LepI? zV`b%#cocGXI7OoF(C7`hM)4%;Jrd-DAMRj&R|M-K?(*?@O?W3siCb|nNln=P9PUg( z3RbY@3d-Zv)VgOU;RCGIfeNGsd}_7MBe~X9K{1l|G@pS!%GD8fC;ln(00 z-ACRvubSS1kV`bHt5-ZY52)VXR9>U3qL^9oWOap3Akd@`Y|*;0&>y^)pmJcrk5JXc zf+Y0xN215C3a^19m8Ob?gAKHg!JhrrdfaIp94CW03Kx#Uf}Nq|;p_-aECz4$6=MZ@ zmokZSPbsLPI9~Q8T)D3CxD1qUyqGLriUfW|mv@d~;81Ao3I&!_&yjQ4Nf(?$0(E2T z5Y$M|iqE1@>=q`?jmwousVrpNH2;F*rH|-D;G3_MDml!;y((!wcpi= zXq71BBIpiY&Zg6qWV+&6VI->+b2Oef6X@ckOnl0$tQqP4>QaS|s8i%K^pS0hLhm__ zoSj{;D8HlD-(r?SUP!}Z5JcRPDM(vHq>~TgGwFp(eSp%C+qjW=i5e2tD^mm$oSfh+ zX6rU7f&MPC30{SnqK_FRLQN)se;NY=hDm7prKPT>PS)b~-*3l`mICaVm9snS4J?`1 z8lE)q-L1O z+gz3>0p8$F7i)`+o5#Nh@Eo&kP`~zev5y?^gi51?%Da=nsiI`cza0FrvzBFTf;dWt z#E$h^c$z2I8m)9}Rt$@};+>!prk0&cN|jyAPH6vnlGOm4nek22=Vo<2lHj z6I&_V;#GgMOsd>%sjD>^>9V!IlwBNPV?lnnRj;x%L^crCg;g)jrehI~R5AUD00~sbA+V`+lkTEGWl+g#pEESab9w{uY zfCVN7KHZWnLPV;YDiG~Yj#M7fHyB9xnAyEld}D%&_fXh%vJfQQy4&om))DTcrg=X$ zXvphBrh2}M_$v*rCLm??;0GFtWx_W|uF97&+75)KFw9O?ld5?R~?_ef#MH5w3`(o=Sr;=Zp2lEYyf^Q4aRGHhFE|IfN3cUitA)zrl5A`bnH@bYosHg#;{L zFqsJQ??m32zST;RH6!$4Ue(1h^TnzygoEUl}M9{ z!N9vW(hR{C!5m%Aa_yrHAdc%5NMk}lsuaL9I4?}2FF_W-Fw0I+Bup3(&H6VSy+D@L zg>gk=h9ZBDH?P5kz?K+_6h|#XYARA(J2QxoNG&5)hLB`fB!QRv5!(ERcBBEl1j4D* zA}7aj4DC@JxowsB>)BuDMK3@Xo-742L8=0<{aaT^k$5xCk@s zkAiN@@q!Z8wl|3)Az35l^Eq1~-4gG;+iB<_@YPj@IPuUUluMu~-h@HpQ&P56k->KH zf18jn$wrDie}$RiMWY&%W35tFX%&!RUHB#;ROT-Qp21S$u&O}ww$-W2&=uj!*!R4) z_>#lxlAK)qWHY}B>thji)~K84b>7dP@kIZA6VK5%S|UvcoihCbR%-Ocy-op2jr0$=WL8|D@Ilq==4OcENbSB zd(;#I)!9aP%0|K!G_BPr;Shd?O@Le;noCrmQ}-HEx>2s{#Nr(jYD5K%KzEH4!akrOGUbk`#-%8qvPF?uO6aAW3w zs{K+TQi*p-y*3>Zjs94LreO|Jg<44~4Lc&U?B|U$HP^NYk-?81J~^gWH2&&p#OQr; zeljpQ2R%|4r9vO+8MfC;bfkau*awgAbS~&}Jfc+*TSW?9*Mv>CZlKb?_!A&;`s;G^ zex-Gl|I%ld#P0~{JJc14Hi=t^WKEy?JNb_`f2Y!W`U433Z}vB9gdcyg%b)xm%g+-g z5$Xu4KsQ=mi^uC&`_*%nhmrI;!ldVTyCyt;E#u+c)oHr;`yV%`-)wr1x8r*(Ud^Yw zt!>ZczhXARM*5MtzWQ)U@&MagUh{ajD(riETK;%T6XSb>7l*RD1>qTwFIQNqY}5Vu z&5RV&wVmxgKHM;dC#iGO-(iRmT|F1L3nj-)s38{$52Gl-Z?06A2eQ5!J1k2OiXs;; zYA${ZlJRVzVM;z`3?+whrHU^ z(do;E-HM}auhQA((URFelDDly>?zNANE}5x5TWG~bSbVtcQ>JZT;%n&++N#VOM=Zd z_CY>w;e1i#3IWUQHwBLsR?@Yl~+4eMVGE=#?bor5-}@e;iL(Jsq_9%UWr z!{k>dXyW7xgXzV27kvja5hVRrWna{~@@qZqO}L6~Ij~F}a~6wkDs&XRQ%XGN%#8Z+j);E~^N6*ELqBO0AS+umHlhZ0S zysX($2dRIB{PM6MM8xJNxQX6*{HU{er{3Pu_@l4@>#gs`{wBlg z54my0VYxF6Z256+rqToqp83m^X{9)EE`xX)k`_N|8qVLEd;6vnddgh172(?!l+RJU zgwUEdObKtQPu# zJtBGzmEE!>*nOL{_;48Byq-N-nbGJ!zm4k;^HtQOO^0Rggd{CcmEP<|F0ygebx`8$ zGOi(9+ox^Y(0zgn{XWtyM1oMD2O%qyj?hgTCqH5!^mVk-<9pc49tF{A>CPl%?AD#W&^C74Tv+D2jH*08f9vuGL zct75~Z-tIo)x#G~UDfn+{2o6|TfvsM`*F6mJ@3~LdtCRxMhN?oyh`jU9WQn~UG{9U zOm?rezA7fK*3~FKLS@OM5X=WiuMKO_lWF8OuP=4fG5`en@)zRqPZ;_PQ*3W?3*UT9jne#1K} zJLm;J-<IhR>hy*4*=Ld!Z%2%KPl5 z3#;*d>7e^tlX<8d!K+-F+shBN=LBCqTRvJ6dc19FYTF*+`L+6h<^=byd<&P{v0U4f z?QxK-6?sb>?waJ~DdDtx!V%EuJ!p#NNd}>u9-(hFADPa-IF*y%5%u4+iYW1FdG&Gy zZwRbADMz%cDi4|}dD&l!?~02B>=xY#xp3YGFipPI^oyAlcyQ9EBAw}3f28ogs20BK z{1jzEAaD`2?xTt1*`&Sp)(8@e1{TlZ{z~X@AtExuu-C^3UmhU&NNpS#?5F*07MiiU z;YX84%?q`EwddId{qnQ(wSe}q8ZIMx9bA_m?~`$R&JLzSR;CS%-P2g#{Yn;6Z1#m* zZ4r2hQ?uF^etZUF=rU&48DC<0Uc%k64*RCMg67&=ObN$JPWh}cC2DSJ)(~c6y~7^d z$%HVk1S6+Om0weK;hz8H8x$+ma0XX4F)#Je0YQE^ox6gTR{2eYzVpxFMD?$ETq_i` zn~vr_i5&x~`)`!wD!l|9LQJ=-E6b}&MjuqJnpcCrZpL>T)w<{fd~kP5cK@OD*(2kE zC?K)pGGIXf&hD?P!RGAGul9em3I0utN9tLAKcmSE=~x2q)yy2dDz{LJvQIz)ZD+#& zq|VDr@VD~Z$FX0Yg=m91d-uMh;mg?ycU&eg-Z9r9eE1k7BxZL(*S>HSq*nE$yu;bq zfvo8t>Yq$gT~_(U<61Pc7PjJ|3vS8s%(3+87tD2GpDZ|*_}k)MwlXd-I-TzRYh-iQ zDrPL%>}Cg}kwCXKRZ6XdJjI^HWTh zkj)fcd+^d{{sBo=&m%oMGEdLfo~b)7JJVmTiAFZ{Z!-L45ELA3iLEv)2@PzBaRz)D z5K^|b;@jUXQA#=ydq$J^uYL?~=8SLHT3)dI%kwD+%n?WYjm18!jh8qIa@Ok__i0(C zfIK61yhPRGPLAR)`--Ke*rfW?P{}o(b@7VV@(#{JO|QmbK(4pjT%7H~IRNREeT?k&Q0K*P#)g zENuP5MW&V*Omt%%Zl)G>H&c$*NM(n1j87!Ru;+=`f)l1r1r;{T`PSzZF{%oeOa)P_ z_;T4!mDZ3=RP`m%${Y#X_8bbomsn}#(pM@hw#FP8nmMh%23CfaX< z;ovQUmH+HCDPKg?jF@=w^Fy#*e;J?jpFa{Al?ny)bDwoE(W>22ZKsuQl+gTAg)}5; zN((j20w-^x=07wJkaID$H(G8^!RmciunuM-EuvGis(6Q8V8YI70xvA`a*a@kgQzJc zq#jZ%#bX{a3*WWBS_eyntr%OCo2Ut?t+jR{5k*f#&Y+jF4ssmQEBss`oT;E2S&q$z z)vo9|f`2~ho+itY&mN2XPpzaB&KN}lCQk0PN@EYUg|El8)HgzcPt-f4CpzJ`LI{oOXRPrhFYJdo%V z&B^|E0%KOz{}!t~1NHv5hgP0}de1<;XQ19QQ12P2_YBl~2I@Tn^`3!x&p^Frpx!f3 z?-{7~4AgrD>OBMXo`HJLK)q+6-ZN0|8L0OR)O!Z%Jp=WgfqKtCy=S1_Gf?jtsP_!i zdj{(Ls|4T~sP_!idj{$~1NEMPdP2`Yy=S1_Gf?jtsP_!idj{$~1NEMPde1<;XQ19Q zQ12P2_YBl~2I@Tn^`3!x&p^Frpx!f3@Bc%f9xL1b=KYbtPJqZPV_{=u?MBMV!hy&v z?qu)uR?XSO%z~6z!otJG%tBRK43SyP#?4jP;;p!ogR_&Pg`*oO434 z4KZ$d0U zFfi}~lx!a*reze!|0?y_e}Z}cgFB2bY-=lPL*eA)9MdBd#{?R)4-X`^Jbo+acmSl> zb-b|-WY-TilHc!RjA_`lMHs8wq~WQqtbA9aW(705 zl5a`9^Z4UOfY1IIv)xcdTbrf-iWplw5qF(V=tHi_c!J4t+vWI4pMst>cyfD)Tc2vVqNZ!bH^(l-{ z*8Ov&6vBIv1X;r&0Np}pnl}nHEiqgB}4d^)D%xl-E0e-G3 zTEy!)pCdJ+-m(y&B;j=N=@psJ74&>CdVIj|WHqBf!aXZ}*$bfM1i~h48--OLA%at7 zn1uH4@#waUI!?9vWd6GUNfR}wAnn%D(gJ?AX1z;db(57lm?3sg!#wIBYth5fAP!W% ze%Jx2DwoI`*<|Ys0fB+R%qV;t-7%{VUp$V;EatTq_}X2HCH^SZ++GmD_eCTY zzoWg;%G0QtO9&56Trf%oHm(1IxW533tBaxr(clu?-JRf#ySux)2X}XOcZXmB5?n)& z;O@cQ-JQAl{+XJYSFfg~-kX}AK$Gs=&h6fP&faV7bkuno7q*gNa zx!G*`8hPxFQs~X%1eu7>lGRHC`wY5%VZcBR*%F5giscgV47;%shTJcaWwRXLzdd9c ztkr89>6tPnht&$j??w4~(18f0gF?!O8f%0H$#qWVh)x?BUh2r?)%@`QGqZ(HkfNZp zGn3!-f}z`Fn}PDJa7_IWK7)oBxLkEllt7e|WQT^c$I7f6{+)YhW2Uc z#VxXnu^6N%GPQ>A=NJc{KAq%0_Vak#&^AKHvBI~ZfAF~Gv%P|o@ih(^r0zuuo! zo0s!JNt7PUFg9|P6Y%*yP7TxAP?(&GX78r2a^D!4=LtMUsv?OwC}r3PD=(q3iUy_E z4MiL=AbbvePl}6<(G$*+5?lE}lH^{2ab$%oJF5F5lFFysMe|xHMB4S(88P&Z6*806 z?Rmahb7$FKhXlgc?X`cn*uB|j1W0lQt|(%wIJrSyXW@yTvAG4DcWDN*6-l7w^I7Ax z>dwX?<6NC7zk#8Ldac zGKa2OuTs={Pup(R^aao=lfeC6kSpGJJGK5-}I9xBc)G~+6hjOWcHxC@7?)fvwB zC;Hz-fYTzz9jV>W@hs#{zP&UtZ5P??exau)4_P6U*A+S@VVP0!lgUOEINg>JJiCD8$w5A$7CPD<0@^&#A5swWJMaaTWga%*rgza zt_md=lU}C1(tILB);Lv@)oba=BPU?_Jht4Q_5|Ybg~JNPbs-;%0579#EueyqiK%nR zAQH}^FqxdHy1sTcRFeP$xmFdBYYM1=-z)x%@?H8@T9{BK@_99R2|>Q43UC=>l!WxT z;s*WUD=6}0VU965im=D)a|viKiIvy1#EgM_?o<`4N@Jd_%&XbG}8 zQgM-f0#8wacu#f0HQF5t=v2algk&Qy2{(vDV8*lf>VB**dvZC#V)sW_$uT=mI3Tm7 zG3|ymT1w9OoTDzmaWOOo88k2M@E&NlTqRpMBoutK#Q@Ds>gX%`Z*{K zSi5y1(O}70ra0Jx=+uhyWul~8p^0K*nCvsX707dxtmt)wkSm7TpL?4Vy(tfR|H$U$ zlAS3r;bL+>33mggj6ROI~~)T2#uvN@DQEs-yls(TX3c*AbY(qYAO+&Xy_pP&=X;ywmC7b&CHcbcV&Shotg*e^u)EY1z=dTXZI! zL>Kv58m-+gP%!#0ZIXJNr$QNbPP}#opiEF(T?g#|_KeYKPI!Mg$k8Z={X~M7-ky;u zT!2!xJcLpDO@gKqC`7Zbpg&t7VwzCW>T0gIbgcm&L9k##y_MXvwnuU0Ahbcn-0zq) zWmYN*Ijz2|v9Ej;rKM&&-xz{8M-OaGkLoc&SFu@5^feV%_h-D@*1dIehqt(f{#ijQ ztQhJ;6Kgtr^R(fEw635E@-D}660>}J{jb-l)?_WEk1rV@sKK7(vQhHy`MQm1_8R8| zIC<6BX4dDOonk%5$e}&8AP=#dzk}KT(r*~F-0yK}A=yR?us1IE*Zw6ZbYoPV$BL>d zyt}*BY14rwyTz@y34ONC(v+Hyy2SPOISB(eQ*m5O%RAv~IZy*JnS z+oW&%y(a-{K7ip&NLe|)l(RtL{fl*5$dGI!WuQd@QOxE5(6 zYEh&r0XhvX6?%>P>5{kt7sB87yY&~0nYxeP+uJ9#j+tm-E}xIY5Ji%yaA6g5+uayV zaR0(S^oyOX*5uY>Rm`daWN$Y>MS~mEw;)zv~dtbtcF` zDt^a4CJv) ztp9~pTufU#tGPa~xOnuv5uxaEyG%Aou}SZj@1Fy~^|*Oqgiz$9XaJkE4D>jeWw1lF zAE+Reu~*3?>74dQBlBt#%wpL(=AN$AgqGA$@TY0J!s*#+6BYtkv!d z{1i!gH=}hfn<>nd1>J@E;S{Kri1Q>!3;5u8hcSb*@=^k3yH=_#@o!&&d9hYTlG_#a z)yN*90*{r#Ah=X|Yc7@VD&SoN8umxQcI;m;BsdRBevb*hMwqy1ADBcM&X}c7g*=uB zfG5x0LpuI{&D91E=pXL-503x8_e(UTa9I>^ zJi5UeM1hLi=A15(OvrEfclmgLX?RwA|L!^nz%zUvUo#+&0=P^~ZS|p{X3X$yP_S$6 z82Mu`0fO-3?xL>d-NLRR=Jh4mGoII=)9f#Gm*B|?(@A*&vV`*tfelG_+>mcgYwO=K ze~m{VS2aK*0EzqK{^R|!)12iWadJQbiad3ojW=MRJsaTlD+s{qG4XTrkbPNNCL0B0L*j%ap32!`-FgrN!^OsN!IQ_oZs+;byflnJ;hS* z{%hf%%+eJb%otwf}8gm(zZ^H)z`V-}?k{x4Vy80m+T$9C*ob z91egjoKr5F9~CJ(Fa`u~T243zmqe(IA)SV=d5XkSjt*S*J?GP3w- zFO-QpYn#LOHyObX`nqO+fa$%^GSfQ)s1j7ZGzpI4dH5VEt?J6-zA9#8^SJ{E@a2j` zaY0?ejbcM}e>Z<}oNWIBkeH5d0e%rxr1Ne1YoAXZ&&v}GBCf>t(@hv-7?-+iy2dVy zB65pVDvgcaa(W;%rxM=de5@w7Lk}6q_hw%{UNN8V&(%v_$MH;re!Ek9zz0~ylaA9* z!5Wy6(NXCqfNP=e`04a)UzcXwXKi0@kc z5xs$B0-oq&CWtbEYvxy92SR&Pd8jOW`Ix4G-MIR>9S~^>vdLw)lj`c~Hm8eUZ+duH zZ%s@AW)i_dW3=pO!CU(wdyTu?hjT(d5 z{|NY)SF~T_Ak@Ap|!5BdauFh0cBI1$FelO<2 z3^bt?Vgwt_Z$6KG0E`<3EV20EDRNQmLYPl2ZV%IVEr~L`@1HSjiZF3&CVrd2zULZr zxii$$Ktw(!k+xY-c+x;kQ7@R~DtdwdrXWWawBmIzJ6ks>*l*eb2Ulkhy-Y%ea8j<0#d)X8Iy<=-t=enJIqnu zw`XegTLc7%G|750ls^9?WnOD{8`TGB?_jw~w27D&coxk2o)}rYZ^mfQ-hVan zI!}<1mPRT8ATGW6`}388OiyOLFjw6Y#RBe8+UzV|riA4-Tx8k^jU7+3`$Z)I1?(?6 z#T}a#_KUYkCEbqFs)_>4(0EAi|K^P#VcN_y2u1LG+r7mGu5BC5cffofgO`BMY$=b| zytq)9YxH12u1K!Rs2o{V7zs~cPf#9_TB2KP_ZG^`7Dy^dHG%+cAo3fhg z-z&uJP1}!0YEtzEMObLIC-=8-fc5{{2iWHsSGN0Lh;1K8#((R_Gr6^{_k8JRm7GCQ!`fQ_m3NBENtW zK`K(r=DW2_0U9GR76q@5N^z?r*#;aKVd=S-a@6J5U$ol^N|N1rx~LVi(pwy_CSj1d z!6EM4YEG5(d+0|#)`?@tp#Low9vt)}I=LGZ`>=+589VTlrUmYaNPS~E%AQEDrlMlJ12YkDlYRn+|kEpUss!nw}7%I zZ*YWBS;0gBlU2SyXTLa&L_=+CV$#vV`yASA+k$ZGc%Y!YnPjXJ)GsU=PRQq~#dnjk zUcmjW5h$z{wIu!(sPLt3jLyX@{7~0Bt?14+HY^ov5KTS=JJbsMRXLMc{2sqndgIF4 zsD%7s%i}))2tGwbt3B%I4~n>HK^N&cPfmHN9CK5~ewloU|!SKJ9#c_l-U_x4OZ~M*X?Vh!N~8 z6*^Vi0wB>8V#Gd6E0dO zqFNYZGJ}9|UXjyJY3cLFAIx4OASse`HhK?*`@^5vY2fy5tw}=23UtTS;%5Kh#z+7+ zQY)??Rfz7gf=S>``$WDu6sYlY_3yA9v=yX9}?+2J>;bcGJamgjX zSex2)ze50BoDNwfLLx|yVnzix9gP$H5^$iZSF5h9oU;5o(`}+zx>3DX3+sA3m z0DejnE%cq6xR{SFESZ#43;;B19UgbRcp@rzgeOqh6;}8k_fRuZ3sgWbVJZb4doS zSnDT%Oe}D_XcFvr5ur$}LMej3YO(0q?rycw;W2rpu(k9dNCI$q{~*}eMnmu~bnOIQ zNisC_^e!LO;9z7qmcf@bUNsy%Vieeg@D0uft+U_CB!rh3HEUm&8A0hEZJ*-3^u$X0 z#pt#zoPCvSX}18_&-dqSDGDTPePs6$Cj6B|xeQU%%gak_5WYrUNK@l_cJAhrg@-K0?|R5ACfncwyaglX*ZixI>KbGs9zLIQU#LB* za*1$>ZJ<+3?0)197`Osm(q{XWT65vYnSWwSsK$SCjFA-|pf5N8JR_S+r^CLhnaFRo z!Q+*<1~30}Ji%L;T?bO*XIDTy$qvc2W@~r5K=n$S{D+aC0TT1=8bA z04A2fBjW1|$G_JC{QLi$UkqT2(J==H44s}w$qE3}b>V>z55)fJd%?QVZ)%{?JjA3P4tUn$bIK^>^SJ?8-UWWah{W;k zLkYqhP}=xKl{$dJ3y?^9T3YF}1HcwfvreDa{T1@qO5raag3ejMnWpK^1{UOLjd5P} zpLSI6etf%io6EC*;*n(B`(t^ALV-YE5746CWRNE})cInVbx|=r$nV1hZ<*-7`m^Eg z#DSx4R+r0H)Q^r6JmYr>U|o6lcX)xhbEEBKz8W$0xM7TmfZfJlmG(>LX2moBl6~%< zL9iV}Q7!gsHqc=CSYg&2c&a#2O$B4S-C5(;;5_ z4scy*BY!S>e}Y7SSUIUmt=0dqIOCaj1+)Uyy`!0?j%zgy4zsBwXk;QjtED)uf~b^o z0EV-lR)4k12{;=2>te&&{q!i?YVG`lh~%%zT2X3w_YQKor9PKoPMU7%l;Jo7v_!fN^mP5n+e>`&{OIf`r!Jk^nG6q}&^j zQK`-)U=h7OV*?JlWwZf6dTY0xNzJuXkAMc7Esk2FwGcYR2bpP6;OAvnjgchs4*dPVb+n!b*r02r#b`)gdOe$pM zD84;RO!-GWo%^PMR~P^FUg%X>`q^g?e;#wM!3BN=0675b~ zqvXlkx12VO*2Xxdn}NP>5zq0R>rW|*TpOuBi${&$zauEhj`Xw-r>J>i*_U1g>JT8? z;h6dWa$%UbR$>86fC%AF6;!^Zn2z0w>AMroRtg!2hU42FNk#f$K&NH)f!p-O=JLA6 zAdF*f1e8KRul#JhN?Cx{b!}s7MFa!Lt!1Ggbnhu6Xc9$w1KCf_5p-%zs^tnLwRans zUksBJ$r=X}{s_0CBk=nsu}hV=qMqhr45Fw6IVv^-;-X-Q4hR8&Ansevfb?TOc6fkF z+FxfPOYmq6ME8y}P3*VJ+};zapB|5X2QUGE&d3JPY%hYj!rmRvDx) znOD5KwGahVRFsREvSN_eC5}9i)>bs1$4z>Tq7QkoGr2r*C^>AFq#0_uM34CsEqsr) zMxKElv(Rtx(qR&Y>74BQHkX6Fd4-7dbb+(D<8D{1_#ZK zo#{Sm2vGA(9M(IvCs)>Xa73MU{&iz8CY46{9*7;eF}3_2)C~+2;OFH8)3PHaNoQc6 z2p(9ErswF_|N2#$_3?U$!wAQ%4x~(%Xp(7q6&ruiYJ~K{34}4$TR7}9)G-$mg%1(V z2)<0>s1e+bX-=ZDweFZk+pA1Sw7!0fLmPH~eS~w>{o&+-ax7D#T(itPwwo!nbx=O- zm(cS&(dw?>YgEmCw^B@gEJc8YANCv26_G$ApNmA0Ixj|pFks~>A$vwCca31*Y=`3WUMHZlbHK6N zIMRd0l-fe~2^}JETH!2fKL74|d)>YST6j*h%N!qT;la1tEM$&)AVj>c6^M>pj{8H8 zsJEwgm5N`1c7Vtk2gcXA%Fe^9c5T0k>Da7pm6GR7c+-L*jR;tv6rdMLNIe4ZVvUyX zYy-F8aH(7ZMXf0{oYXV?*3Cgd$y}cOH__2@ZO#;8C2ehjK>U_92f|7WyrpkbWRy0XI2&s zTJxFSVE4L@Agc!|+;^OV)XJukK>3`2ydoa=*H8)Kp}?j~SRB0ZXu4H*zjjP}7#Jx+ zeKEkzeWIdnkd)|}nK^X~EffErBnhWTz{?5e2nE3ZP>rU9A$|~}u~N%;ANQ_UTKEzB zsw^25zkjb;8vyk#whyF>^T-G}G<00)B zjt2@lLEtG4mlw?N?3JQ~VUAI}tt%iZ$_e6I7Es`~@Gn=rys}~21G>*l!X~HFw3hrpG>naNeZpVlY%Gax5!ms`8t@onRU$#0Ftdw|8!fK7V+@g6Z6>F6VbTs zTHPZq_$SB4am5Dus3UK3i)l2P83Y=Prjtn;U97p%q>z9{Xln197?cM5 zAOm?3*5c|2A^)`e?G`5Y{&ZpiyKQ$F0l%gw+x3*T-ir$RDT-}5HhrOLOe|3s=x`w& zf_2L8cmsJ~q_1@a(Ib8p!uLGfuJ!MaSw;%==a@hsV2`f`J1L!opUcRh+V<4lPu|Dlp5rZhQlzBdTO@VVFB=xTv*aq zfRC8DkN(mMw)mJj+nfrF`u7DLo9+uSU{9<4`T3F2w@?rLXJB}sluPUGCC?WA>lnXjhRtU_|OuNBsNue_ZAy1DkZu#zf>lf>+_>SFCjPf;{ zPSpeFz4u4FGt{F20*R-}iD@V+=~sbwq>kmo)|LxY4XPG7^qbj!s!2EUBsG3zf_3FL zG5#syBP*Z#^Vv)h{IQDfx@qY?YSiG{o3{!^{e+!!S~_>t{c~& zWJ^pV8H1ol;dF1pi<5{#i%x89VNj{W z*pO`(Fc>u4OJY*NP}&B1opcc-qm?TUnIp_-;?7eN_5)bF~y(MqDoE z9P9LszYB4yZZHOMx`*&We^zSgT> z-``v5uSL>vvU9ARu0&GYtPuu0$SZG!TpXiJHEs9ibeWe~tE^dcV6ghhRd5Y9-GsK; z75qo$j*{TY5*2G>H2Fm0)Ox&CO4JIsHr?i`)wvdB3{hGV$VK7`S?z>YO z50nPd<20iu&UWj9c4G?)Qz_q4IKn3gQByom@Zj(G5jN_m4bwTQ%S?oPzbGYqaP*Ak z?`s6I-k3*$-l{&09ES8Jj&n6>RE{@%FXoJfblp5G9#cmedpJUFm1#7RwM{T-nhO+& zpU-dy+Z$9hq}%!kU^zWdgyu_+2o2*e#_Grb`|6}bVn-cug}ZDuG@GiwaVM&ksf?Se zNc=77xL5{-jx9M-RWAL=-3J|&VtrC2Afepgew$xI)GdzF8qyAg&&bXNy-L-UH_=GL zXugIKiUWRS^(^u~`UMSy8|RZvo-ZCz&21j4Dc*d`NjFV(=g;cUU8bx=x>#A`5;HRg z0@a1NZO-Wj!iO+{uE9b?K`o8Dtb1 z4tM*1z4g^#D#gaoEm*nig+V0SJCs_Or$9I>+&mvY&SH(58u65^=r!vviPL1=)W@W- z2OWC1bN7!xRfcwBz$o z6dhWRN-mn2f-B;-Lv{OE%(iT1#47evS>Mi}qEf5LPeBybpHXB-bi0<@yOlDMbPslr z+`B{IaS%wk)%IMKmB?}DaPhS^pr1bc(e@&dNw4;!(J9QZ~F|rjJMkFOtc@;Cq$9M(IJDi8tko_9#?}|UIxZv~+L@C-% zfOb47cT{+ig!Pc(pi>tP6pudbQkl0M*yO&gF24RPrZ7QWx&v%XO|Nzx359;KW98S_ ztu}H^B-lRH#;#_#?j6Yxo-Im6HkK{+XL+(Az$PseVxK=OO;Rg z7(4tlN_qJoF$}3Ox9K304%f5VAU0EimVl`6_y%@{qnT7|1gdQbm7^Z^2%9)<74Ur- z=0B6&iv4b_^!fGltezwR_ykYa?$uZ`)4~ap7Yv~RQOvUa} zwB7^Ntz`qS{$&zH!%%Ls-U@0*)J}O_@1EPiI&_QrfXFbvL)_EnGlVaOCXTrpZpnad zby7E()l>ojPM(nW?d~X+@q98bquj(;te>zZ@MiI&(K)xKl5np{MZ0k4G7|>s8=P?x zX%Hcn9fmazydI$XEv%>q%x_#B4^nM7sTaFG zkg`h2TK#xn!w~%Ww5A#w|Ex_Hsm)b@McQHpkkwGhM0QNwL&t&8)$u~<4NU+iQVqA4 zuekQMtJLdZImP5gXm|m};vlP5H2u7PRZGNkEUG{!5ovD#4?~>3RWYNV#m|CKll^ZQ zEjedOiK30;4?s~qFxq0f#;p7*_xU#dE96$8G_MN@{$Mg&NZDMjBRP3#{%;YQ;yZ3(Vc)2wwWy&;&-;a0x0ArLbU_&IZ}S& zOF!;`<+yzVXo!;+fm*-F)eQ^w{)Dow|SAKAw zlx#=+Ev0{0&_(&BD29o_&wd%(z4W5yNyaX-({63@pg`c4Wy(5;&S_u!RvW*L72$be zjwP<8jT)Arsg|fPzZvJ;V69`Aabo121YGsOZvRXK@T3U)E;^DyB$~ zqfCQ3E^76IELod96t-Qkmzt-`oi4WFlWLsutF}99ziZ7F`+#sug+8oNQOLzpzEa!J z7v3C-u#GHdG(v@MvUzGuELyPdLwa$e6HR+xg8>U}kK4c#i`;7$@52Vyf=UpyXZp+F?3V`~>h)>fC+UXV#AH~H~ec@u&jrfrHtpCeJcZqaOuWzym| zgdlXxvFZX}9httv0m}*C59s0*eA?RbbU|ja<~UJg!}3ppafL^t{-3G*MNsVP9t3FO zET1-_=QJlAM~A|Lj4|g>b)7}?#qdKG1_YZ!24}15<38dGkX^$#8m|OA?)AECf%Ru8 zyJ+4*ILBpO#8FX|2wELe>;wyA;XcZd7%kdRRFxMy(r(Y;L|A3qS~?svpqgG>qt!y& zY>oAUu%wZVm+TQEGJA?RqSyW36^SXi-s5rYQ?1Y6QP^iZid1p)PXZ68l*yMz^{ zOkcE=yFy^$Btwz8-SbGLUsenO$PEa(uMZfFj-m+j{&&wI(agIMYJ7D8#}T+n8B|Zm zu6Mp(f@vvmA{T_#8U13}Bv+=rGMd2pqY1p(7J**S7O#Be7Zi7lByMG7&*Y)O7S#EK zQXB+%tSbhm4~QV{-8eAvIat+zeLZL^OkdGKu@*qLu{eb2oI5X45K z+L=B=HM;So9gJmDd5^zzD_G<`FNA^Rs0D8`? zB^-7ug0qvoohZNGMD9A=M~!arK2|k8hYG-P8P8x?2?65P)nxeV`elKD-*gC7{P7K; zplD3+23QF5FEjsjL7!VKyB6}F?**wWIQfJlBO{GsCkA#mf^j&ZFVt(Y8R6Kd;^G;& z+MJsk)^8d?<1LK=f=SS^m6Q3r4>vuc649aA*?0v|WzX>iMSsX zHwOm^2*kn2PQuQ?%*e_N+y{;oCgI~_`X7FT!ltg)4)(w)NSM@Bq~ZVFkkDAUx;nb> zFfo}L+c_{=TDw}gnYg)_JDWP#y8?UZ7)>4QnAlm^joHl2Ex63sxGgMzGjcGSv2(L< zaB;C(m~mT}TClKMn3%J28MB!&vvPBou$Wsgo3om7TClUTSeTn|FmZ5kf`C8FoNR2y ztQM>m9NZjc94s819PGd&tgPn$-RI}Kk!WbFKJ10W0V8rj%^ht2KH987HB zSy({-e@B~>1Bf;W=zj#7or8oO#Ky=9gr5UA8wVo@h(7!OL*ZsIX92OXS+H`mvvQi7 zvzW01F$Dr^!Op^A#>vjcWns#}W@Ze020-vw%z>Dhn6R0efH*kKjX8m*vi|=XZdNu< zAkZKVcHou`#Ldpa31VkuV`1myWcj}lZjS$BxY_^raQ|QJ!~`*OvHqVnV(M!HtD#Z- z7wa}n(Tv*shy#EA&XpiSpqThHQT@}20MeA&oq$A8P<_pe7(>>^8)Pay+Yrcr`FBm- zFt0TGyRsBuJ`epN5c@~7eCe?1!Q%0?@4TGwzyfq>`A5kYL!?0%zFGo@BBG#s#qTC! zOOi`Jo&br3s<;11m*qYLO{PP6{-+?zBz5eI8HZTB|J_MkClV`u@^qtwe8Qf0@pR7kP|9WI!`IKsS3nbEBqq#*-G z^B`dodde&fN5!p@EDBgwxAQo)NndiBwkj6=$g+XP9f#E$WU!{ciuo9$= zkg>;ub@qK(AYqt5Z2Xb$CMC*f#7#iR1yLM(dMykE_KCR^s*PewN#g=z1_dnM_E(+x;eU9l4o9j%*#G_v`hvq^QUyPk zNVPSoak*@CK6WC4Ds=-*!iZtfMi_=>B^YLtR0SpZB?XaW9FHJZ4oZ5x%}QXnBJd4$ zRji};HIb@IDJ+dlnts?eM4}3;!przs%4IXO>M}{l1s zzDUqq2hFDh5G}Pi)E_Y&0^Sp*8KF>-S*gCslq|87NU`xX=B)S}>xE1+vm2v}F%VBS zxk{2gw^>6R$Z@^*H@Up5V4I|Cm=rrBkBV!SluOTu#X+`}z4OE{B01(a;`#ywP{Pd6 zCBt_vKks#Ok4ek@ABmP@V5NGz5!qwrNYdKb1f4Q%E}5gSIp#=I zBl1&(rW^%c3$(5g1BZA@!pUwbBAOn;r2$6@YHfrOtS${3Nua$*AF@i=T3IB)V-_cu z3u^|$yezDdVbMYeY*~QH0%TS3B+gRWVtZ5h%2z(eT zRw5iWEA2pNC-IEgg@U2O3JT$l8^2#{hg5;`WLWJJIj#djwwxe$S?yvRI}Lu#J(m_*!5EIPQcbbaKBm|CtuZ3ELMz{?Bs}I;YT9#TS@po*kN&IFs@J| z@vw{*EZ89s`S7{+)qNpZDI}_0QHg`FZY}kUO$yDB_Y>NxViST8DoE;vzXsu);?1Xj z=_E5dL~)qSeua9a z6Ouz(?}3ySjwCKg3TYZ}V*k*pAnYqtlpQVGj4MSr ztv*#Lb;2it+kh$_dY9QIg!-z+Qo&fKI&~qX3QY)ODLqx#rV0mk+Yu7c$JMS_P)9Gd zVilmZzbAif*|)5}PQLn!PX|`AF()-fq+!C*VG?zA+SG()0mF2gMkZXe_$F0P$(Qc$ z{)Nvwv$xa9^K{RYiP-09X>{<0hFCIQFZdp;G-<~U6q)wYPr&I=;7qSa<&J zi7e~;?CKNH^706)FnD!O`ChpN=TH=Z|E^Vxm|2|fzvKrtxLdw;RKP1mG{FO-(HCCc zjdfDODfysjv47=a6&gTfNG@(s`IK|<}GU055$tqRm^@FcWEG0h(}FhiAe=ru^0H87yCv~MKSsnG1^2)QO1b0p)K z)|3<-snAevfw?lu@4^ioUU4nV#QulvACQ_&67@;i5u6|Cf3qh(;;@^0A6_?ZKC1UW zY&(0u!Vh+tw5~@71?MTar?O0uOZh25OKKq8;^oA(FXG_Chn%^WA*Z6XGnC1$t9mOQ z@t1W|l%+_Qm*3{kFtL}Z{&+}Tx2HVaDy8kZicqI@`|k(~neHDQw{zS4!iEmPzyAB{ zg}_>Ec`y2D+tfsY9t-&cI+b5Zr_%=1`5(RnKj+s_e5U(SI!BwoJMY)nz_LNbN+S?X z_f9v{kKfdibO;nmr8fj%kIc??{jq|R?(G~kGPBRq+0LtH#=7WyE6YT@g=h?5jM22% zZz$m-))jb|$By+`5P~96ow#C;d+p(RPS?JIJU!r+n7I4d=34orJvx%AnzeUDo81~T9_tesF z8FOdv47eNSI?E*_Sa6HS-Hh)fLveyLQ}1p4nAYl=ECRptqAF+mMq+T~=|pAujr@Tg zzLLt}cI@tx+o9(5V1%SlV)Y=*mvdF2#Yg8rx@6jOiFA>Q$f$7wxZx`9tP%5FwP?(f z8ptU!sy@#YMDCL0*Ocnhf>kU_3YfjG;wQ{Xmiq85+oe%Xg7qW6Gxciqe}3EF7Spu0 zVzfoX(2=$IBkvZB(ttrZzpgl!{WI#0k{j1jCZFdCy+d2Z7L0l0%jsMGr+O?BZWoOH~W-kBG6*w9G5u1gmHTYl|sEl)jrq+`f^V2h)d!{%kNp3;=>T@SUWp7GheM#un zHEeg$W?p@8EjLP$LQ_(?^BAwKUv3;ltp}+Y`>CfP<_biw?CiS&;rG{;z&dS2g{FOD!$I)+5KU%cEtXgl0o)pooQAn+R zQE0t2lGZNQYngCC#OQkTz1ePPMSsJ%*x8}aH!>%>7z~=5z^|Hz}NPX&`iJKTxjQl+0;-ST)HgnYWz|r z4OW?_zq@@4&1kcO-lcByV!-U&fKR*WE;aOr_Is6ISw^RIf^YiG?*yIe-$bSNxs?)x z`Bw&1+^n`T>*?(Bli3GZ>;WrrCpjKD^mHpzr}hS>KBQMVia5EG@%pSp_W3q1F3ceN-}#88j-`K5KjrUEvJb1qJWvFjN$0DbdE?n{P_19$wV2m`YY5n(H{|MQzYU11 z!#VPRny5Ci8jbHV@*EG|>h%}rT8o+Uj&eGq9PpOmv`d53M+e!{Zf@7F9Ci+72ad#c zQ5~`i*TH$qPuT{ae22s1T$^SW-ht++W$d8Ra@cYYKEwXmvW`aBZmYMm@Y#N%tSz6X zXHv7$VfDYPZdfOj%w}CzuHab0wAGjzJy3ZiP+-*6v+lKUdvT&aERW3)!J2Q=32%S& zqvaNEc@(cMH;gL-%jY<>KV>fkwTi1qC~6IJB{` z-XXaC_N(h5l>h$u?m}Gu!(Qo(*Wb<2;n!^EKpjWHUF!NW$0Bg6`dt0(*0$6Bl(WYN z^>PIh?Bd?C>_LU&d-?5v&s6fiBY@=0Bz;8vHD~dE&C@UWekZhDKij%6y6g-TkLt2# zm`(>?;8ynUAlJ}uuOR)5IoEwA1x(`Z_`WMXsqGpxe+%$*u*<2QD6=dO5clYNYKoSv zWUK-1{HQJ~vMlKJz;QVHTRxoc_aOPTW(mQi&~k%EmFe)WotJ)Gf09^^hbZ(3d3@KN2s70juGwFH-gWZ1koXFt zHLYJ!q2il6wc2~!+#(L;ohNEqiB{%-8F<9499bD9`JN<+cAka*{`_+kupvy**oBi#3nv zBBx(HH>a&2S2!qzTtP;~;?)2O_*;W@*ITxnXQ%X5sTlL`9o#@dPl}O>67>Y*41>GQ zOYa-E(bNBhy|)ait83SEg9djG?hxGFU4sM-?k>UI-Q9z`yOZGV?yd>$+B5IhXYYM> zb#?WrK2^KA8h!vjsI{1Lj`7I--0K?Od(S5eYPI46ts-~I98Da9AB*trU@kMVtU0`Y z%4skJ3<(y}$uM~(go`=oTZ`OzZ+~Jr`yc6I?Efwu{7bb`lh-!~^sfJ)j7eJQnHa&) ztC|@)m=eBIqW;%S|L>HjcS_VdCF-3L^-hU;r$oI|qTVS{@06%_O4K_g>YWnxPKo-r zPfov6qTVS{@06%_O4K_g>YWnxPKkP_M7>j@-YHSj@ z-YHSeSnNH3C=}+^%bZ`vVQM27& z0^4w#?KqzX1Q||xqri3#ioz0HBucH##AaqbI)k(|QHB}fZHOx-luYdq2+dgAzfnc9sz@X_3UOKs7-#$Wy$+sB3mR=n2eMGYJK`Xp>az@Y zcL?sUFk=`k!kQvjJZ1TQ2{^44dj#?6{<1$nCJw z7w5B#VK`r=BEi1YUvSK{$+#~R%(PPICdDI%V3f=_pT$)LW9!(O^l>JN9_a7EJHz~PBXkpu%45got${VQsrhQIPyWXIb;sX?%o^eppQyRZo|b>%DTJ+| z%i6CH`frZm@(d=}db$LBfl{;*CJc;$W;*GxgsRjwe%k!q!yvJBD;qYWy>H9X{Wj9H z_~y1=U@Namt|s9u^C7M&Gn@Q|Nl+0-7PQJ^6?#7|2$BUYmE3)`zF-`+oBV{7R8A!# zCR3eT(A_><1Z2n)Mg7{kA8Y}xJ`R^@=f`eKXsJEsgc_}=aKAT|B~#@n3C+$NT12Ri z(j)@Si_sn8=5PL<4dFT{2K*>U6J&l0zX6kLhogv%*Ue4C^f$uD~lS?+! z6lM*Yr-t{h1A$tY4j}}mEuLN#G__wiv_a|}ip_FfqIJU?dhN`q(H565P#^G%`cU!W zIfK<66>t~Dv7DV3g_#E3l>(awU2-L?mNMQWT27V!;t|VY|Vk@xKIFf!Cj@@(c*c`#45X)6~pp z5D<~sgEHQ$4f_JWJ3v^2&gglRqOkeqGRC^nv9VEq*k%$o>-MJZ3odTnkKIWqp~(bB8ey>*>~kj-EUs^eGqm?NWFSCt&z$!yKvZx?WdQCKqV zzQ|ysO>lP$A0&&q14^6Z+oHYA`8*%*|Ev+5<%CZBusJ((A|Svn#=c{{h_=kKwOQkb z_L({na~PXhSfijs`H^1aeQFu51PvPn4&iOlNc#n`b8~CuFRrM#%kuC{=k=v$g0TJJ za>d)*#q06HyGw8^ed5b9E)K#XX0fbn|L#hlr)9LcC=;vlX+P{CsCOr}kgk<#nNQ68Qag9@TI z&q2&qkj}Xv6S#sDL8`g9E1WmC_QQV_po&%I<`o$BGu-i;w%+0!IQ@!D5bp4AFVsoM zH~V^@QovdRl8A#4E?LkQ@GQZUI5LQ~&xt8kXd)JA_(@rC%RgMXyR8AD-f1W{o>V0A znI+X@Y4q43#ov6V6j`+c-;N9)=MWq}zz!B!m0tLpyIqJo)Av2)#fd$D{EY~vDKYM!$?P#jc7(% zrRvV4>y&pAjX#g6r15@DE5NB@aR*N$Yv3LUu!engq>abxG-BSDj`LaS7Eg`P!3ER# zGq1n*JJ0Tx$*(lyB`L$xSRQeRtr*lzsiF3HrS6%h%-VKSY$sjMYr?I&gC(7w$HAYz zTpKI>$C8^HFK-+esR`LGuD?f=*l`%nLqYWeQyRTM=q`(Wq{K9gB+Bj-oPCRAH&$Rs z9M8}1RN>EKW2#zwCw3jB1|&Wy>XKuDEZm^#NJm32)vbOv!^8YfQUm=C^KQ#oQ1IC8w&oKuBOsv`j7q~L|q!s2i>hk@`Hh1_Hk3$ z3;2zxl!&nTnQjP@Qcrt|128!8vEdni_r9aX9PXK}*}tp6-oqm{IL1f=^G6*(B2I=Cr3LC=QOC z!aKmmHBQdW?gJ9c(sY_Ve_9nhNL?lFE8WLeF@8eub4B0TNJO7)l^)#AXKR9> z&-xbkYsy7dzpcBle5EbhLq~@RZtgK^4_C&3b88D5`c0n;-8cNOntrybwB$2#f2iLX zjVe&@$%ExMYan(7DrEkUqCA?wpc~o`yMY+jx;-l;#*sj(F)zh1V`e|}C20$^3TCq7 zpMtVL$5eM@PvOC?YZ57WO+B$bgS0uioRVUkqu5_TR`Tq%hJS4SVdrNp)|HRBcKWoK zxek^WjToIRNKQatD}*g5qKA+{?8fm0A*%n8Fb+iK1_Vp3)0c)k8-iT=<E>5LoRP z4@D4IJR@$uZ?-S@11VBUSkPy>5Z|BnW)c3Cw&hsR1suzK+c{yU%WDxJ6ZPfaQ3prd z)S_HEC39CSSJmcYLyyCpej9iTv!W7h{$7>yldLx;KNef)_ZhBCh$mX9U1CAt#2$F* z&CRVhg)35XEpP9XPfc5egI2%@4IutHPT@<-pyiw8yVNan<($&RCZvZOX;ruFzwDAo zebAPGMgs-WCE=ej@h~yQvaQakV`qIV+ef?G!$K{i;+5Uh0r~ZFSljJd=0N{%qGViu zos?1zX6CP)Xqa|aGiXRCMaZeqhRon>ohOuoPqAnqN?5CW_|3w{0%-$i->EE;Wu7Q4 z<9a~zOs|ze-kfmou4)*$=lvN|KM0P32n9aCT%qr?2R{hG2BwYkZVqpavnM&&>^pAw zYlk{Cig)*&fSG^J{$lzqX+W^MmggW%^~vfwpu}WIeL`b+WycE_>QQsy&R`tL1{M5c z5olBV%cr?vVmZWmTlY4TO82;jI|9%f>`VBb^ zyc0+GD+g5ibME50vT@iHyAJ!%7qLO_Zm!iPZr%O<>59P1B~F_f+P9IY2Oh7|4Wl=o zmhx;E@_aeYoCyE(UX~l3;LNLNZ==b9C-1q4999F$LolO|L6>Cy2MP!E=Kkm8#H790g@Y!KdS!B1&T|P3`?=`>Q0;S+PIZ z$zIG~sMw}UhW?+wDtkrC_;&vb*-fhYe1&L#yAa&ONxQW$PGc4FB{VN>N1xLtbN{Y( z0&a#~hD;PA#wO6}9ukxjl|&~+?JW#qiGx#`YfqJCf5~Y^J~)CimHZ)%{*p(clf1T+ zR7di4Rl=)BL?v-|B5YT3W_;!xawBsp_oR!xjU%$4eVr%Cq%l;d&NOo?Ko6UH9=b(4ghsZyngx-|V!rN?gih4}BFjvcA26PD81``cV4oPuM04Sq=zmUwR{q$-~> zc82ZkXypov<(N{p!|m-#;Ny(pTADEqn3O0|M=W*d=o)_2`KN?i*o`we#&M2B*}K*4 zZ>OQne*5-mf_cK{IVwI}+Nzp84S1Ca)?$nU93Jw{KO=nwY=asY-(`Scd@T9HaJ<%& z;`dG4F3k)@YTa*2*po-W#RYnas(b(HPy5@Yni=seeN;Afl?;E-y(1gRK{Zz)&8~zA zynhrq)Wl3X7F`V+r`*T7I#~c=mAdwy$89*m5Yj$w}BLY9mH0UWEZudsvn=sq)Fp| zCUX?B)#%lE;hB=`Vw(0e{n(D@Z2pfYrl=$M3Ao=QqfQvu1EX0Yj?zp~W{rG1DLG70 z7uH=>sWfeCoe>6gnQo%}jPc`)NZ13$nhrfn1WFzQo@sWYji4FqSa?+~8HTvutq#y= zYJxO-VfEumlD-eXxmbD!Vr}SW6q~c9t4cK+88qb3cm?6=c2iRXE3}%Km~;x$8UChH zg1gd&7wf`iA5xB8`Ym^dN6Y8Z(Y2Hy^?46}!~&MBGV*9vhCTU*UIKRH=$T_tkrjrP zF={$Pk~faWcqv@&!thb{*l4zeSUs`nU1q*^1~UAYM)>^!Le+4l+|<3clbHz9VdFn@ z5vKCj&T|$)8G&vtCvcpx2dzEW&~k2s$qB{A_^GuAHdhaW^(jN*PM0~=vZrZ2;;pQl zFtY(h=uw|>RXM!Ka2MM3o@$!*;mcfmFdDw>!}stuzJaawo3Q*3;7&}8|1PlnFTtHS zm>6N`C5+5WOaZ7j3p)(Gu#KgSy^^h-0RTuBF>*39Fj5p30s!e|4&T2R*$dlP+1gnD zC4h4PU}Fd{qhyS%O#o;V69YTj|69Vze2P|?yXk+!hj!q=%+8fC?>gt?E$JbSz>nVecwo}_WZ~MM% zU61@;xQF+5-X4Y*MberIi%-bg8e2z=f4CnJaTCTR9gF?Cx!g()%8EWyYbdO9htsor z%r$NJXjk2-ziq%fYhwI!;iphZbeeKV3k=d`M>PY>yg8eEHm?ImqZIO4+tXb-uChg{ z=H?vaWMg}{v=D=fB;Jo#<#9_~c${wK5}4l1S9hmZlKtPZU4Q$?VP; z_|)42{HnC;oaT<{InFD6|K4M-*u&WtixqK#1-q|vfn%DD)-TqY_`FY+4={AJYR!X+ zA#wTbyoYNlD0i~9O6Ot*7t+)z|b}K z_6*4hIaTF=p7hP(#LxN9r@#1T=jSnq)HJfXr`HvJFE^yumaDZ$6&=(3UTNxLHTt`z z2IvuWtnZKMqhAu$9?YxQ2}O;JC=wAA)USt%qMwf@q8r@(HWJg)+I#CvK5t2o9ur~G zeIvCSoZLcuzWs&Sb~ii2=GpdeCeKeY&am5|7f{W((~mlmb$A~A_nN)=Hd158dR+H~ z5u5g??;1U??NA)uLEMtCQhniUr9wsOE4A9#%r05*MH_GB`5f;hUY?iZ3Pz9;MUW$S zH_k*GKe1bve`|pBtMPsV;WgjsmtF17iz#hy&q%mi2Tovmy8XmJNJlSUT+-t9@pzY_ z+2IJ~SX3HK4>%WCsc5WPnnEt2WA^GR=~ihZfiZdIw}!M@LIwz(Hfvt0FCksLbFtf& zFV^Xo*B&hk(?%nl0@AC}zu0Y-q=+3w*cg%KIt!UNQ`iiWpyS0Syl~eobw?I|HY1Y9 z)w+#E``ftR{aJ9KYEn{nbab2@xyo!kpH8jgQ_gDo*deWMNYsB_&0-GTKsW>({4C*H zP*&MeqxFQqvlZVbt&xjj@2gJ*8!1tA$YyBq&)!A@fu-veDMlL@h$4vI#**?N6+orr z*Qtqfus0?b3J$rix;cgI*dh`UYwm5m(dpJRp2!r>8cC+V5)h(B<_{D0Ua;mZl>0G6 z2Fi5}$pxIhP)!8|RkT+muha3^RRtz8!I#!VNYo`}>_UZ@!DED@Z#%qk_4TVly9ms- zxInOg{c_ct=B~(x+?K+F`j~VFHumUfHmG#1Kok^C!06sA0B)1;^638U3EATN>B0aN_A8iQ0LENfV(TC6#lD zG9aGP=S%uCQE46#vieE|!(}^UMi}#~S zxLGW7_otZQZQ=;@L#IWGtn|^=twEKJ;y^6aYpW!Kbc;}3u%5mN}{L$*aj+YyMv z)b-hFS!BY^Q93qSzGGq^(zA5>F?=%55r7zbG4kM4@BSOgqWXP8 zMi?`k4=1^Ckr=} z_dUP-gDuGvaoCk8d9W#JOGR%6A8f~? z(|9}rlvtPzjG+taVhN~%HFjLiWA1V?GrTwsNY)_u>l2^b?mQ;Q;C}@U=9v^HH~{bN zY}YnMD16=wUQ|1pp&tweiO}j&qrt)vCeCg3inCk@>f~_?!K4(h_oJ%USS$ipcsu(I zjwpSeSO~9#d$23ZV}Dc~PZtp_=5l$_@;kMJCsL8g-{oyeg(Lz+R;J>eqXh4Fq^4vR z`g|lJfg?{yt^tEMMVZ>)i?)G?%Q09(KAxM>^K)$da7CvGT&2=HtG=-@X@}?xUb5UFM!a`tf!S{3kAvjX!#IUS_+%9+s<-9 z9FoCjAW~3ueLU8YddY(BmMQi#_a0U-i>8xE8-^MB%7na>re8zUkwt30Z6cjVheX#@ z7@dOV-UWF{E{g}fPdR31%7A~8ELMAJ3Muva_iTwhQ!qG9fRtff#v_y}i?)5YqpaMX zZ?1uIE#3Xwq8+M~@x)rEK~XH_Vbfe~PVRPuA-D4>E?fDQ&Q8skuNcWf#hYRgj*nK0 zx#QK3-+x!%6Dp42bgz!t5n~jlcWZ*JlDcappA;l1_`3a0f26~Z0uVNk4zMJ)C+%ownteGsl)hfJW5mJ9GW#2YJ>QzBVcXR z=sfKlXR;bK8PYuK^#0rIPizd&OR9mG)JM+YX#-rF_1XrVw-h{5fyj2k&~ zN@gjC-tJ+|w+6ckGiGDER!55$2kIo_uRhN)+IVj-M<&_YinWCz75Q8Omktwt)K3Rj znI%-Dg5#8p!7&Xm2W6!&sxk0FIJal~NM0Op?z`8MTO_G_j(=;WoyJL#(Lh*CXe)45 zcom+@BL0Q>`9_NkU&?aJ0EVcc47qO&gFEAKkL##1WQn{3#1hICAaWq{G1~!$InGW20U$4kh)S)V>8Cd&qAS1oW^o$3h?i-^ zVp{D>*@?8b>+dR4vE8J$FHploQ-ug1clN5y`V3*oHFGnG!okeaMXpw17=3k>tJYTw z?Lyqjrdz3x5NMP0ae4~X+<0d|X)xSJPWIsQ<1~ufX%#$#lEo1?eN{LJVc`(BI`z>Q zls82=K^67!UwJCnVxbb*c7^Ye0|a8f2ZUdC8abT6t(n#i`xK;QdOB^iH?eF!M#7Z* zITDw39+J_e8DGM1cH6f0C{H4{h}N*Qq(v%&ESgV($E`l$eEDJtQ~US25`pNYVt4%? zwuuXq5mf=go{mGw(bZg##-Z@~#Wv?3?4PN}7s(DW5CggtFqHfXImpoacG17hy_Qj{ zQnrA&W}VIU$!R8KdDb{(QW-9uD^S8PW!KQBKvD-{5EB`a(_c&%NI%A)5>Z9PHWlkJ zCHUomyeewfqxFr{chC7C2^k8A;#f?Z+#6}s z2;DjNW}BN^gChuGpl#DMQ(qc72NM={bZmIhAp)16FZ5e9-Cu}_`Q}q8lghRB_rgW( zIx@I!3_8B3Z>iwpUbO~Dq=ur2{Fo#cC@BcgY_h(<(EOEH><2`}t!gZ`n3Cyj+r8n$ zfGh|&|5hczdri0BF8YalMG}5q-M#e}rq8mV(O8^fS1qd+7At9pB5ObzHI&aw_-6@l zoYMWRbfYrc3dd5&^e4BkN2g>wI`#MXf1G)ouC|oSm{e*tX)D#+HvVk|z#G2*)4yDd z`~(X23X3Lu90AQ%%a)|vj>QfWj>yC2@WqaUC#OH$U+BNowzhI1^vkErwn#ViQdIuy z{k#~PU9XJy+jDl-1%YCVgHc1n?KkH68Q(uQ^uMS(G9?A-?NRDf&*xs~%=x)FJ1(FC z1`_H9j7?QV#oFUqPp)h2Z*g*m-e|Qk1ypBNUN_!ADp4Gs)FS~Z z6QN$Sw_T-*pOfpZcO6j^o#zt=+$Yx~@H&pis||(w)OybQ<%nn3D|q~v-G`V*znKUcO8)_FIlZG**Y z;i*JL%WHgXdAsTgbSSuef1-MUGN{S#1HaO@R)?d-i=F37rqy=WtKUn*U>^~*+tt?6 z{LtaSE1w9g*MYlYE$#48@Tjzu4)7bI9T%S+K?N+Ug0_Z2lK`zxO z+kYVAn8&XU0_owRxJWK>gL5gYhk3%0nx0;ZiH*kv$Q0_dsn$W@T zIdiWU?XG*OY`Q>PUCHILN_jS{*QaZ3RC}l%*MZ1=O~66vc?2}QBA{1!{3Fr+$!?u) zt+~0bm+RqbwGOFLe{;3#-*6@BTB-H9^C~g)WT6VvlU~oRZ}_WH(S%Gs0pF#6DNQ-w zYZMk|Vc(uJ7Rp!GYty5ZGdMYR>}T^uyQZjt4gn2cu>I80+TIeXj&Zto7wJ_9)jxz;^8`Cv2?)2SA?Ba=N= zRInf0oX-T~bj+Jrw!jaQy16NBJ_Y;Hd zm#$Zvqm3iRFKBRT=tCeEPS87yUB| z*YVUo^fjzd=>{2!xG&rt(O~^Kno0|Zh@h^N?%|jOH@R;ePq*whVs5qa&K`1nF)_)h z2hNDEzCVlj=zk$|6J4Dn=cRwHCxPW~)da6Kzrm!`X)vIr1b;bg=B}10nwy&wfmd{5 z8NJxsl539L2Xm{VZ`=Yk4&~2@5D|`*!Aj8TmilTtHk-kr(0>8+av@)26G`Q-J|f`e z=ODvC8~#ndPzK3DZen1;@gf(w%{&8?0)0o|tY6#RK^E~vQ8Nj*N6zoJz-Cf&n!mZ_ z*?4Dei&cMDj40Rz85V4e{KVeS{5Y*z{Y zcn5UQdmR>?YMQ+rE6%@rJ-0qw$-wsfv=;{+t?AT=~>yY!t>v$HZ+DPXpiNPeQwKqrMPAcRQTUMY&CGa&+v&Uz3 z@E&T>1uF3iuYVE?!}S7Lg=U|Y(KXvzPM4|cWmn{)J4zr%^7&2e1?ik`&>SBGnhi2z zJZfIb;F=a3%{#S4z=iR>x@@w}B5XX zqt7rCc_yD#E97K8muod;q1pL^-s?X1x>pnU-f_F%4%{bVgXnR0z3q_P`jTdlW(y=YQ+;;0$xO!ky5m{_^*M(Qd-zHs27v0nZ?f`sd4s8^P*12U|pw z<0aTIs`}O<1k*>uQ=S>B$p{1G zib;WdZ9bgn#F3+HF8--Y(g`xXrMwKtcBvjE7eTS&6Sg=|?VCHINq*;{Tvj~OsE5#! zrwwM=GOjozhQ2&{1*ElqR+ll*UN;0L+O2LSd(@f3;lt}tNDS;6bi7n_to_P|-eyEb_|s|P-NE?Q7DeqBI$Anm+l=xOgsEHc zD~)GA^Em?voKB~$9uAsH!k>_ewS?)e!Ohm@m8U)}7or?N=1NA&g#MY$}{cW%%jnX;db75+zci=qxjYZep~!vXp(gL(D5;mT1Vs z@Yq^lW4EKdSln-K_nFITWCGDfvm7j+ zVXx-E9cJl7moz3vd+R}zWba!ieTblOklNgCmN$NQ1xerkiOm~8apsseF9Q;+RIo^P5`in_XFt1FxbcN}35Je?7aOoBa$tJ|c&YKq;CSzpNT*JH+2TxbIlEw6s^ z`754jE5VByMoPV;uuF1(QkKS~pNVNW^%|R>aC`gRU_%ln+tK(O7NIir7l|~beeN!A zR}h|Xd0dJauWA0Yepu5$n4Uc!F}l!$OJT+PUbS zfVzVNwq7=;PlST}B`;8-ObH$xM^k#Uu~4W_?*T$Y%0j!rrpP?swJA9X|4CHNm=#r7 z>P2~>xMs0EsG3DsHYJH~F2s~cLo)^H)LtdVHEJL#UBE%fZRmH)A`#a>yP05`CX4?s zEC%VQXgvOLcW5r$D@y#&s^oGvTN%+D{hMD$T#2DwuVsE^2BYG{d_m1*3lC$j7F6Y7 z9%#n_Z3ObYMDbl|xD-B4`4X1sj$=IsPiP@5l_((BvD{~)cN=CypNIl^dQQ+yS^&&b z8wO%(_}R|vRQzhj*^EzP3<<&FjXaw`J z51j-of8JM)_R4^XwNLugXl9k)Gyz9StDYsgh=>a8bBg&;7IumNIm|Xmkd$bxP9?n1 zkBjdKQ~~;8l5===j>kV#(VA2Vu^quZi_4Ro(`?OfzBiY3eMsrpB8+0DLG@GgC2t(7 zfDrRk5}$@&%db-=%yWv-ID;%3cbuOIm;LClK7NysTg8tNe%mHq^JoPu^1aDz^Cje< zmnxQQD3LiPsg>Me?rkp69wE0CVFOyKCgj_qMjeajh`_P+Oh!e?jNEj2-#|Js!9uK6 zpIi`Ak=O`M>51abr`jQ)AM}FkN0b@);musjY`aD}N=k}!HTgJn)W+fvc;0H1B_IP6B5H{-C+wdV|yk6&i zHzd{|CV-gGWFOPRY-~hfg^C@cruq-xN0@puJ_t7m=hoI6e>qspDZh0*o`jzjJREw( zey53ZQ_ekZX5v^`Jox0uQCr1%z7)_;IrT~wpdwtC7ONy-AJvrWcnCmDrMB_^S%ML_ zLR^t)1XY__##(9|$+n7^wt9|HCRchE~!BKb|XmNtpbC`88cO!Pz9whbAj z{gWWz$Q^)Kmd(}NTa&24dE=PU$ux6LxQk8R|M?Pb0*BCap%ZLraCFX=kZ!+zBo__E zxCE3fK1;#QR(iOA(PY{05z~y6MZVpdkGBUpAZh5cX^NiIeU1Nm+(GM2Y!sWYPw^~! zWLny&h$+AGQgBi+aAcAY1HJ}(A`0uMs(E@~cLPBm?8LNY_U;sOjR-45>zFOB3g24~O=3|Vc>lH}H2qYTNC*P@TH6e&IHLHh5>34R zDkL2CYIV6*xr|}XkT|oDpvr<4lF{hbs7E^&bY%r5_fuff6)7^+1qGJmV}CA}MDbkQ zk~-=9z3}U<^2k^`(u6>3q|I(gg0X2O;+evPoOJYst)>?F+!6(80-Zx*YLJ3}nGiXz zKO&%>f^-ZzF)3FJ`2z36z;@l#4vLv(Vt`{RWl1AU%%>#X346vWm0zaQm2e{+4EOAj z9(0qWPk%gB{FOXNi3t@Js~VpaO5MwmzU7=yD!R!{HUz$hXsNxxtxga3oe4FLIX=6zBLtA zpianB2u-_ycBo2k5#f- zaUU-SAS$zX?6=6NATybzQQ&6F_7KCX@63rPfi`LG_xD{q2 zy%Mw{fg@Kgmj?&=unY_OH0J6V-!2i#CNfYwSV6~2V)ae&Z>2#AYb=-~&_ritR+aj7 zpxqa!4+EuxY#D~rROz;*hR_@+9#>7EOyWGCV@Yy%!af}RY=l;62^s1Mx8**jrZxm7 zdxh=oJTnY1l(LiCks=`IwpX2vVr4mv4d;^FncL5-i7EX!$vnW^vP7SzdQ6_5QMhrm7ZeiLYe)Dl08{V-ntJgg z8~lcbMgdYRRSCJ10-+uMH?{{7$q?5P-!M65G1)3qG#D(I?#SIEBJ*N|M4Y-^=#CJ<& z@=V&pWS_`^1rx+Bt9(T9eN1@AZe?hUXF&@P^vC4chkD%yKarw{s#+p<7D`_?=nk$l zv_ggvM{8x@9(~5XtCotuk?77y=#gg@o6Bog$%4?}2fd=?RiO+ZYjtEh9MHVpGU14q zBQt&^xE970QO?SnOe!>Ckzq3Q(vJ!8dAcKD=)z>Je{?`BWBAh6+2sR`h{xu)FGFkC z$y?$2O>hzGYQB==GP|Op=C=6PHz113(4MU|5x~~ONG;323~sawcp zAv8aK&5AVp6@-irf>?f*uVG%Lpa)da=Hd=33J_#{UrGo)11imuDYa4i=5{Xbg^4e^ zbgT!NbuDEOlY@(LL-o@%;oOnTr;y}EKfDI@J7E5%+bte8))a$+VQgH)V)iiz=;fQx zMX@H8M^wR6Y*ujulu-O7LYE%#h{EsaM zX^g;Xa{+CAYt=HxE1obfk{@0z&$}}wmA2vm=!=V>aC8+8*k69R$J=VUsr+T68= zXq9TfRD`NpQL^DI{mDx3^~N_bx4k?hSf``}nbWszcnqW1Q&!P-0ni2DxuM*5EF1HZ zv&nUNAr=$d8W!7S$Rv^DMwz( zCIDIMlVU}8xi+Orln$J(+o?h;!$Tg*lLYbD>1yp1@jsqzDPv3gx`wR7LSN;H*JovG zlwnUVpNGP^S#-nm*ck4h=_SS=xZ=v;YO0cUH;P7Ojs-~b)g<*n;=1WuY*TuOpTX(3 z=K%9XiFJ7ass$;^o{*^hltYshf?syD33(vTH${GlO75EPwNToob6ZJ1VY)+bIx*HaL$+ru=?8E>@jmdmKYeaK&sw{E6v?}SyDFN3dClgxGM>TL7;&1Il&P-S3yBov(JU6iniWmW@}v9}Ww zEbY}G(`-F`sW=5K9uG`S`QksB#!v_wlO&D|4)&kF*WbLY#c<}O6+IMht!SE~f;7)Q zYt7q8Prz_sr!i;<9Z=%N)=yJ0BV$PAwgz7$n>yaw^2WGLp^+~A9ju|3dq!-?NTX#Y zlg~n^=hm#ucb1BLr9>Ae(Jw zGc-iuS98Z4Bk4wCfAJ;A{8jYR@!0hPYAY;P;I-PxzUxq#M~0B2c9Rv}$w;hVO-Q~~ zQSyA+wOx)-SGH!p!;$q-P2ZcT?Uwaq<1l4sfsG?7)J(L5a?;xeh%V+4agxg z9G?ZlKxfOPbgI)M?b9u;4X}~CC%8E^D>9CRb_*(DcLayS-gIv+-+vzH*!OV~uj)E{ zgFiM^H*MGIR9)F)OqRW) ze0FnFCif&rMj?&ZvexfI=C19WiERiO!riy0ouEXY&;))91%N|5P z@^sMOzwg_r+xr~02sgZCYLt+ijCp7C`@hUf_m80sc=tTfFPNMAW9iPCxZ)_vQfi`2 z)@!XRD4hTNW0a5!m0EQ>B>d0kx7_}1PA80B-T7&0WqOT);TUg^?1=Rbb8ioXPsdHqgN>~wdl}3 zt9$MGblOPs&uDEGnMSKIgRoLB$^Rqb3t$?fdGpxwi^hzVDDSe}(ocEV9a0K-^6t7! zldGDYwUrqLoCxi(xf=vN(Y(MkeS$i*Vn)@y1BBBzRUl`|V<`AL%7gozYrKPLg$7r?v~a06(Z zL8<(joqoL#u$byF3ULX5t!gEWX+w4K>P4HSeLuVb_XnVnF>*x%G9rAeR)-ty=o>K1 zAh`eIuLtuxDkCv5v4ZM0^Ft<7D;zLen>hlO994;YE%#qvfE)?)JPY{;bTuQi^{l~u zvh628>YP6U1{Np-tVV?j$wl0JnEPjdh!-jzoumO%Q=3)o*-mNBW!X)qG+SSP2O3tH z4GI;WN@ovT^W-@&LgV1G-6S0N>uyMXeef`mYqEYBX98RrP{HiK5YBO2NV|*I09xsA?O(05w65A_<8?ma*WX!R7mr8FR#_L%t*ZWBS;x8$T4G|RGM9)E zAZhXeH=G~XF1P2qd8jn`SyuBZ>?ScTu3`syFHhY%Z ztw^x<^J{c8(hT$xPlgHKwXJ5Qy$xVzy zkt={wL^h2lQarZeC0-tnPm#Y1(6X3B$P~a0Chpv0|MW+G0@Qe4?9&Zzf#_vGmTfwx z%m;>N6nlVbdVchfK{YFSqurAZXkQlbZH!)|Gl@y9Q& z>-g7nMzMidGd} z7_siJz?5RUQiFkLaXUCNK9p?e1aO_{q103;JmT%H`8N9NpkO5IEtHdaZ|LAuUT1t) zI-Sl@Fz6DIXG(+@zygp02wuspcP`j&c}t9A3BL*n2{V#@@MWeDf>lTLP6qr(W1h|w z*skwA@+47A#_h=)1}CzQY2^_o?Jl_%_*r30gLuEv7a@fWlR{0UaU3*P&G) zY`20}Y&*RA1uBdxP$_F~-$Tdi^0hhriNYD2F1fVF*VJ%}Z~zP>8#*!LbCmmLZkS!P zn#Tjb{-Z~4${1`kNxFXs;@@m~U)K~yS7ddmwU~5irIWn+zZY|t8C_;;k496(P;N0o zk1EEOLIs&A$mZSwBW+@^7l@9-(bPIFLNGXQK>JiM6Iw}uZ}^h!*ZkQIma6^qq^fI* zN;Y)H^@@L-<-^q!ZI)fPu4-qr+Yme4#4%rq; zBV-@(EFUmw`m^i|d}jLdQzs`wSTH6qQx}}vr9)q&ghnhFDlS#wRCm+CKQVF zp__L%Y}4iS*|m)eur|kxQ^ld|K^LZdKra%~g&!oOz>Nb9+lBKu!Gl8YMdbUUtsS*% zi*6rBkmR@62$hkcK_UOm3z(aoj?Fxbp~1-ahnAaL9^Z$e;?jSsPuT$kpWegOf^>AN ztE7t5Q0{GPJzCB9?XS6yr;=Cx<#H7I= zasl}l;QF}*=SY!pW->AtS|jl+b{KuaGPZT>kGBg{zKLaN6#vBWLH8b%D)MJDoGAC6 z!;Sy!+lwjspZfMZU_*=hAUma`qtUZXWc{>@kgF+Qg<>eeZ9B8!3sl72P2_0CtFs+}?xfI%tH{Jj5FTx3v(U{Znp8pk1$+FA2CFiN}0oo9h@D zQ5c02Za{;N6sy#HE0AOT&}Kh(Y|J-@L8O>X$huj;f}V>g#aHynvU)Z~jmZ5uk>m@lTBSWhm?ak*cG*oC^WcAR zGtyK~JWe_)V+r_3h)TpXu5B;$9np~N&3$`c!9M<3Y?1zUSCCbOqp4YK8!L9cc~y*z zQ^+Z9pUc<*0c!jV&&k@0!<3Dy9A=c*HBr^25U7SvNWZpUZ=^uKi!d z2}W8-i|rP1j_gP)0zMBoa%n|{)k3GgXZS~g&6Lrj-mezN>~3j*mzhe$gLVZ&T~X#> z3rLO<3;8)1pl0~8ZE@xWlgo}LThM?^S3r%)MS40o?)c=)PBmuJK?N(V_mP82ezaDbCBt6EmR`9E(lWE28M|(r#ixkCyh#ZX* zl&?EA{NZiN8Dsmehwl|IW`$NztpMHdb@IS#n6Zd;3v6IEE(+1yVFh5<8I1mK?7d}B z9pU!o8{FL`xVt;S-Q5Wi+}+(Bf@_fA9^BpCgS)$Xn9ez;?)*p2+?lykHD7K~wV`|O z-5XxI-etdMt?eSR){mgu^>k3+=fQ=*6>S;s_ZTBKy5QzGQ zZ<^?5@e9gA6sW+UxU>WbTa@I zWch;*?D+p!R78o2Fz??77*64;`g#ghhy5~kGSo$4c~Xg(1|fKYDyTnzduZ;!wS1>U zMm&qjvM{epKKWgOkc@y|TiX5n=?v~~93DqPjBSJakhOFPaPCC$+xmC?*W>iK2AS=W zQ5}G1SO?&+dV#jrBu{uI%;e^gD#}W-`aNprU1g3T-|?u{0Xpft-sE^0%W5fII23^} zB%cTj_Z64LnTsZ5@jsG$h*A!{dhhHw(mgYu{HPhVBX`v9wk-vriCJ<<w59#4uKqL27gqmUut++p z4{$J1Ju#W!PvjxC?SL9ygxX6M3Cs%&+T7snb7XQ&GawU@%l?8UUCJydjj%7Xro)Ep z0@ShpdRa=+{|rprus(eE8-PF3{F4ok`O%L2%Xb}rd^v8W{m*lU zbk+u(-Q)Pj(DRL3AKLbg#zxmuw^cw$VMYybGOs|q)uq{11^mBgp|_q|wg6-l@J#iA z(n`u~Mv>#`VkHnH#o)3|DnFdB3b+ryItgU>0TkuGkhwLD-;F*J0OQmEL}=%0Ei%*q z@0$iVBPhjy2i*WF;&r(G2vkFR0Zg)D?yuYlh<<>`E_fY~jkM3%xxMMyZ}$!W0w|B8 z?M9U%c@1gW%+dB|!minJPI|H8!^vzB>_5JpMkWC4e*s{9!17+RLO7HidHoG~#HW1U zGocVa^NjZZ^~NoEdQx2>IzYB`v3hOabl71`xU#-c{Vo z71rOS&C~(BqRZvABYG1r>eLT`O<2%3S%>4|#=-xuVrac~|BaIVU6# zFWM@H^*Y;N>}z1h)4n?wpR)hikBf^t)ZYW_J&kGM&(O!q#^*Vjt=8O7!^U}6zT)O3 za*m{c@~FQcheyr#@Ea{65M`CNw@a1^doI2dq0ZI;=H$`GDK}PX?e={`9eL|8eAakq z2B`}OLiYC4xjf2lyxTlv#ya&7iktBcec3($qt?0Db_uA_P&NEMh9tH4__H}l(tZtWYM>C#-f5DLNbc0Tu&<9R_xG9(S z9+aAvrqA1FW=Gkmu}h|4wdeiNYI7|hdTDr?D}(_J{VK;<1u*grw(qGSKl~2YBw4SQ zZ!r4dcta{HHCrrC?$Qkj*5=rqf7lml-Klskk`yUxX^nt&0ryRsrkcxNRTZ$sb6bvcWwjTE z-C1{c3%$Q|6&j=dO}>OTZBlmkDxvr`3lfl!dQ{EN*Kc{%R!RK1JJceti)zM_=>ugJ zz@-RG_U58Y$ODjWsbX^;HNpU*S>3!#sqxGY=0aLpUM(Ud6T9o30{yIw4WHLcPtw?= zvL77Aizn<>+=5SRLw|rMn&_%@O2)olewf3%|0o4z{aR&f&jtk=>Cs9=)*J^ly8`lR-y9E8Wv>(?EmOF}Smb{|MKpU3-xf0t<1h7fu zRRIDS-K%R7)I5D+!tfm=&gnfopVw1#&cN(MBRX;_24<#9oxr^#T zYD5$*@dg{xmQ@x7fr<(2=@#NM;I0P1&!W<|nrs4fp4Jtz6p#8M)J`sRa zB_Aj1dI6ld6yf4%beh^{BVN?p0#TRW+SJy^zzuoY3_xbNuC2cf|NK?H8FAgS0wDCy z;ccw$Y@0iV8UT(bfhx_Ra`DG`wAPo9Kw7@?Q} zWeXL;o{)q=OsAUTJypU}IUZGsu~0}Jet_yliH|_j-4eqV(-8o7sZtlPhzUZhsH}l| zvV@%Zpat;7`|EoFUr!2!lgb}yP@x^_6~(=)&de|RYZ^X>AQlmsM$KogG9Mx-vqjHl zCD|zoxjzXx>>H{q0h}A{Cgr+rl2Zo%-x0y4rJ2+{aVKeKf>S8TCSNmdj5QG~&;3yh zWh&Ec!*kiSNF~AHHm-c_?CN}t8>6|GUD%xE%yrgYHEg=0F_RoK~SXCI_eFAbX!4wV_KtY;2 z^sA5+#?M*7)sQaHG=N8x?4B2d?7WYa-(O>k3}gYSae~lAq#}%^`8)8%y27EoLB?T- zgcc~C>dy4&W+l49tBY>_S--p64kXa;5Kbhr_&i3H?+VWkY1J_?<4#z==zbEzvqV9J z+Q>QT3%uahM?78z1fkUHk;&!GG>kM}q=MaR<}G(Wo9D4r@;;I^UB7n&TOZmTJPe>W zsm#6^^mFl{jvV464)|6yTEZxsq4H!1n8p-8-^|u#q<|InJz<6TMH@McjyxNh1bvu{ z`C+mbS@1^y_jDljAh3EAZB}*|nKdC(7OE$Di-^7g(D{VnHhYtwyM0)IK&o01eVQ!Q zRshhcr0$$}1R~IxMT)+%Jl;PR|B#B_CJW?6__hXh-;uXq<%?h1S6zk`3hzwi4nc=9 zCyeos%q)kO8iiIAJqXL7+x)cgN{Wo+0Z{uS>EukDqiP$j@OfhXy!j@1YL&EgsYT?` zxGC?Nio>cFS-y#0Qa`l-^^7|4UmpYz1CxZ9&5Ehfk%|>0lnLz)riUhAMsViqOo*C) zW31)SaY9xfkgPJwXv_UN9yQH}o69IZdcnVWt9)0z9R&!o*ezUkL~$CdvzzNdL* z(biFa_nXm?r96H52<;1D<`Qy>X$efmY%wm1oVXPnT94kO^zCPNyZp>q8PllM}b zIk?%id>r_}+{ww>-60P~p)g~D0po?v{?ad5w@Tqj1HTh{Jc90s7s13XT9q)^3*p+jK4`^4{W|_nlUL9HZ(K@lW8K) zT5^+s=M2is@49B?Q4~l;<39AM3MAnsSxvA-^CiHe$M@S?O;8~k-m75DnQqBk|K zwvGT@XHY(($yiRx=A&LE)w@*VftU$A2e}4asgF@|Q{{mVbJnf@E%|?lFzlxTqt*t& zW*Gc`7{dtoBYcw+%bXrjYR>PaP{RZdd@e7tj;Nl)6%k@q?TN+nOXvzkR@EE?`(9*L z{=ugC^gi|APC@G?3|pxKE7U`Ln<)z2NvMf&F(43m2)#=8I1mF0j3=0)Q`WLU;f!vT zDstdtDTC}7|EriGcAhMBrots-9?uF^MNkOND80(S8(a67S0!iC66!)+&4qXofq5KT zy&b2WQkVFqgQBJmG0|g*cv+_!0J!G6S1Hc%9G~kzRNY)TH!%xM?a6fgdBMvjaSHsx z4k>E3!szYTKg=vCj1td!3-@KFF7AN72A$iIcYlxsvvK`UFo0vCSg?Al>e%Z9XsZM$ z5y5TudtgK`oU1EhJMk7}czdt*u9+h2;UzvJ@JanfD$LeKBbp8AaNNnYOgSX|Bqy~- zsz4T?=`TWrB94L?|Ij;UKL6$pamjMp+uMR(<$8eL6Yg`8f?7}zIz`;@DXpYfZLZ!S z1{EkLk@p~;#_Vv&bHGTwicxA0Xec>|r=EwX7OR)S z@a|~AzhaB_z^zZFY4z$vH~h`(3>FQcBAQOVFwG6RzW`|f6ryQj@j$c2f%+t|-LQ~H z-1Vmkik!q}ILb8$4_~J=OA4OF->Ex@l~U{H&PF*Pq&DW=%2A!!Vcx>Gz1Og6<)K0K zS9Hv0MBNw7wAXq0n)xwYTH~|1CLlpD>ykMHC5Q|T)`jZwKA(%K$JzLxJwhX{Bl-I3 zAfZzhGx8B}vA3vL51|aaMAgb;srQ7)PF{URGbI z4_>7=?(a2 zCpdJU^6%Ibs4}W($n_-(Ry45nqMm+>al><9{9G3UjUygg-906S<8ps0TU#@rR+>0! zD26rlqoa)e6gh15KU6}p8542^1HVbahUsh_RZ+r`R%m3_u@Z03u~w8w?NrfatrXk^ z{#6ju?*1YmT{~#M<3`O<^r%t5Ar_afECoY^zFQ<*A`z6;_=;|c;CDAiuB<(0)T1W@ zTPvF}2ox2yxF*r1wy?ifSEVELh+Ri}WL8W~fXCqdrlZuOl;6h}9bpp{S(;r#Igs#^ zc;{+|Eu#VoDX+2FgH%UfgF$2jx!ozp(0u3DWQVcR)e}_cITeHvURR&QbEGXo4_>Ya z?$7G)u#uV;Oq8>IgoTfVHPS7xA(9}W3y>{hGDE=ezN}H4TCH7)H74d+${~s-$)+H` z=Eb*O7X_puu78D=<=mL#o}QA5!fs;>?r&l<;iB59I>7Bmo2LR zrO`BZ`LX=d6Ioo$K5sCo-kh?79@3lO5|DaZQlAA(Q14_5 zu1)j*YxPwwmUyb3e5VOcwaO$jkUs8YXJZP29++xIGF&eQ2U%*EB zKeF;8^ybJ?D%|4vu=8JBHLJtik5!?6ho`XF+Z~CkTIDsED+|8m`9sVa@4M;ni(WVX z5L58^RSQ%Om4c$=E6PT11GHOekE%JD^~ch)kMpl21s zCxf*lv58LgK7Y0*%332UdK%J|3mi0Fn5k(L6hLI0aA>~MCgsLW=;kmTdOa_(Li?gP zgIx3tgE+CsqZed_jczu6!7$%3Z%>2&VzucsIEYinyJvBi)Zv4BvET+OJMEP;OTs*;8hu`!V9 zM6Q(WqsnF5E56R46bcwzL;v?IV!r|#%hVtu;d2jkjz90LD$)0|^|orcv2%9mkc_ME zKwX1OEgGWn`IyplI3-=P+rQ7w+x&AoG-&Ry@IsTt)8Z^mNVu#Ndud+Mam!L1+LV0RTM$<{;kTxoX?iGCH=6KmUgP)`IsWz9myj| zH2)})zIVwvq7xI|qfLEDP-(9gZvKl8`7VKaL=DSMtUBox=Npoc#bAkN($Z+1;HiWG z0bh+Ew2bqq^>kd*QQwq3MYi<#oxo8TS9l4ZL>|4|lzG~>;kSMwp{e@$p~aTc1xoQQ zJ2)SFQM&)YCFPvg)}w}t`tBz4ZJIl0UcI`gss1!epJuc#c1#T zzUQtJ=a}FBsWzK1pRoWW^oHg{TvTx|elq3ts8vKcS0Z%NPiGuKC4_l-Am$}5785h- z&`7-|cVmAta_|wFio&%F|JIe$}su@n6U$i(k|I#HOC_ zkn3By1S-fSAG8-?F!m zh^40XuL+X`XY&-#-YIpCwobRI!^j!70GH(;qHT3O$DN$OJmG|4S$UfCXMZ__OJDv; zyIMgS*(8+v-{Oi73w-|zxS|@Apy{JRE5qG1;KKGdAsEN{DY$|kT|TOnEFsmxmw+3<1g6q@FF3$H&UVADTSaD z&NGeAgvGIZF9vaFn`Biz-h5Dw!u=wyC?PJpN@4#w20SH#-3afFG(1|fvQYe*w^)^z%jVdxlQ=6k8 zfEmKjS*V9;G?XZ_=XcHNAzPCK;SXL2xJgLNiu{@sGP=-coFw^rX!e(=w#eMxk&T(1 z&fHD8gHHQG`ilkkT-2IAVRmS8;`z|retjHgfl42yqBFvItQKBjb3-{3y1Ve;@|W*l z{B>kHcm|b6Vmth2vV3|(jUws-e{`2@>_-&eJ<1r1yYk5rlkY=!;pev7~&y!zbJuNs!-o+ z`kydsUNDK&BD4CVJ7I~5drcghY#6y@XHXuy?U4w5v&2tkh*~8A2hVQIIYw)Y_+pF;}O~vB0Fu4R-W22$nqEt)kdQ4e!lR7UWRZmj1sBA z{dZiJ+CNP}AJd-o_&f#iw8uujX4rcM(UHk|Z>8vq2#>F{Qfir4vtk3$w@FbLgXIkFdAkaW@RLeUPc!R&`7Ku{;&`e~38^!Tc~L#N zz}Kq?egjXjEq-$sUxFAJcON=MNkkyvK(bAa8j8gsm=n!^`ifPiW?b3t;3G2Qt{4v_ zScI%E76k$Z;BzzRZwO`ZT582}5+jT*vrAIPMNc@L`kV^O^GhA-274RgnCP1^L~aMUu(AWqLnK+!T1(tz@TJWjjhq4Te8$_L*S36Fz3ZtbFeH zlmEe4QecvI?fR@g5nBImxJAVeTWVc*C%+v)KP`@=3T7U&Y&GwoKy2-=)P9!_)LBE} zKikrtl#aIFBRzuU;&_0O#x8=_EAG}l$O$$3SfOWLWM#R`BT5+OuIGowVavtc1Nu|* z#?voQ=NI~`QJtUaZH`Nrcdsy}zuz?qKjIy2gvy>Zdd2Wuu#@3#E!QP)H#u%UnjjA8 zfloGej|&4;n5MXd&#^m->aLDJ1uF9CF)G#z1PH$+e4h*fhgW_azsudb(OV29Q-~YC zGWzD`X6H{#_W#vRfd8!Z{f|m|0$6VXz22Px|Gh?zEdO;Q$N$Z_B*6R;1~ztX4vzmY zmxPm>je+~`IXzfLAtF9L#(%bo6f$lnV6b#o3gQe<7VY# zVPX;~VfQ77iBRksbI{Q+9R^V-9ZMTBfXQ zY}_0y?4}&1EGEFQ2?r-NFo1+n#o5ut*jdrg@o(=+W+o;kR<8drmxP7&|Ili)uyXx3 ztIf*7`v0#s_kUb%w*S1^|DztFY#i(?|K$`I9bgJf?LVi$_&2nF{bU@FosC0^1nH>N z?aR&)8)L8-`x$C@N&$m>gG{Z7t>FCF)+r`&P6d6W)el{))RWlfX^HviGOF`wj(XAS zyyN(bFVCCf*4iE9AFUY-fspBn#NILjo{@{COQg-W(pMOURQ@oEK~Ze354F2@VUIyw zMw7Gvx2Mg`&XZ8Wu0Qtfx5;O|Pg5YTIz2o+757|y0>BiQpb~9IHZ{rfsb+gEh2W36TUJ0%_skLA9ty>I1E|KRnKVN|3NF z;@}Hf80a5OrV+T?Plo&n)!L8VmQ2YNMa+L#gd2g^EejADz_?mxooqqFx5$OGaqtB3 zG?UedQawWG+LkuSc2Opdkvb27$Ou8ieNyUUM94NVPaY+4IRbk@EYJ+9H?+0j`h$9&*K@N?3_aqgGCzHp&(NOPe% zBHGzNklZF1@fl?zml`#*!26%Qw7gLtx7P+9Pp2?`ce65=!lZA8**hk7V`@|MB5LTj zL~4Mg??IVuM<=p5SaV#5y8?PlgsXxXcEj0d+8fQ6($t5t!F*U3>f^0td+1?U!0wCqtz9f68BMR1j`3M9K++xjw6>iqhy>{^dw&6RgF_QvNov zQ2(TM7?r@M?uoSS2Y1dEH(391!X$DC%35h=cIm+~%axetd7x zY3~|Om-95B(P6V&G@zI$w^lfu+bi2FXUNqUYeCvvWM~)grx;Lj-ZUlR{`u%(`U(-S z$I6oN=x?y%CLK(e4if%r;-6$;pU9hU{qfRN1q;pHiAABohNHXD`@?%_4e|4stBtm( zLJ`RXcR@P2pZw8M!X`17KkZ^8dZNR=tGZulWrcVk&jx)D0jI?Qvw+y?3WF!BsTNg8 zf?f#f%G)6eFJ$iqoeh=%3F#ll43=lh(xOgkZNGsjy^_#G+xsI7@x_`uZW@1WzB!UO z4`dFWA&%ux7!O}U1Jn{q+1Om8Y?^?zq1xFr@J6ms2MqSmJbe^4`~^8Bxx*jw`{`&E z*aRj4(*xrC8NEW-8evKPqwFxl6c`&kv2GuEtUshuEEVn`z6TVIBqxLjgyl%RTQua#N1qvimqa}u_!SP9cE{ehz3Y#$RNKXv@uCPHaXs%I@ zeCREMTL!J0=ajJ1i?sd@cG@2U2ZiPwxxC%Eugk5-QyDlhiVhu%MW<&XNR*$KOv6Gn zJw2QDkeCt>nzEQb>MyKQ&n9flf;T-qKW_303gZW(V)GQi+Tka~c#) z4HBHh5wD$vhbCAtzD&|TLiM0aly2_pMV3)%8mXAxh%D?>;3@iGp*{1K6?^}<;t8#3 zymB@3(mZCkR%yN3xPGCWagNL&W8zoGM3zSo6b-^atQI8m?zr!bTTsHEpM*Nc)}U0k zBzTnez8r_Heiz0-4N{Lt_))?ZvCUuM_T8}%jMhA2P*y!Y4QIXva{K|5hOVL)mnWTl z+Q3|ctSiB^Y%Mk19fa*Ebeib4C9^~G_--T7I%o0!8=uAhiT71OwK-a2}81(Mkp z&Z>}$v4Up`RY&Lu|#zMZHb#WeI0jx)&kL`3>b8&Vjj-*M;L z&BW5>hc}W_yN{RK$DgYMlz`vPg!aYMXHna(azn&LdXL+4+ffA3Fk_aBK2H*lh5L4G zNFRZjC^a#=5p<$NeVUf|aI)2!7mruS4BdFJHEKUS3ERXl4Q5ng$6T5}(b0XInX37% zP~>BKNenKtn?Mp&NA7)eE+%QpLI{2a!0ej2_BR<~)`oRLXi*;rsr&s`l!wiQ)bC8p?MGhSw>%<11V9*VnGD zSD5_k^`Xr_uj_MRmmiO-xf@M(ZOjaH45N7xYJA@BzE539B8}Iuw(|GLJ<90>NkNad z)Tzhue;^E?_F|by+n-ZJzPEq0tXDU_F1U8BTxi%``rd~`k>y}Xu-3Lq+1yT=9|` z3@2Ym(~?SpozM2-I@VGVy^T!cr@!~<-mHp9gvOqwZn({Ka=xq#$26!iX2l+pI9>Tq z>#az0gUeD#DfXucgn7ltjP}U5UqXtY>Kpq1x=e18WLY+5{4XNzmY?-HEmJKmWjn^n zRp=(#zXE{MTdX@GgmH0Vnku}4r6CyX6%qUJZ<0(4r`_`#_-XpGgL)9cSwcvd<8eKzcURd`{(@5?ax%nb7~Gr=cnzk8V8!6CsMoqN3y>5x;DItN0CUV&UM; zgOy9LDzU@_yrr2KL`T_CGLlvZr12*>`cKO5iHVN!{3YDrCP>ftN$kA26@3M5-^Xxh zzEfE(!SOasgJAn%#29lxi=?^@w}UWI4L7kG>MC*@de9ViU<3EI zGwmIAs|%5#P@5`#n@rhy<%Gr=dZ5t=CtoYmM@mi9LPNZSy|-+ltdX<_yo1a+0k4_N zGhS0LCz<>)aKu`S{EJvlNoVrs6SSe()Z2YT%D|)~e&0UH$vT?sVvQV_ovQugF+TM9 z8|Se(dW9dS_t2ev>EB5d6#n@<4a!-Thvys7zzuJ@7as=x*U>B~VCX@GHw9HNe?iXE zE(-to)6I!2jkW@_$x!bu6dWN2-@(A!twrb&uggZYFho zkHwPuuGeH2!+-67p4;aGz8#(SOh9Va6Z6V3CFba@(eXnn&FO%%C(hh5&P|vEQMNGu zzW(NqLbKD!@lg}O1J?rHJzFv`9cT1D_1nSV)lJAD`&j6b&zc12^g&+Z=2zRWicPG$ zMl<0&!$&h=yn3B;=L*YQfdB^+JP`D@>eqR%+X73+p-@w|Tl_-75VDq*X-jD+X3A-6z>w%Az9nH8&RH<`9kHe(=ls%7nn>QYpR3wtK#4^b4~(!lkb zXyc^U4H$~&BzWUxRSem{TSW08S_0#|CbrDW^d(JFZ+sHHtwA_h#^6KD-`LghbHuur zWs&ZsS(;kz49RUWU~Q_h)^w)DUZm-B`Z0T|TGa+A3a6_b9!WF~qwHo(7HltV$4R=6 zttGb)8`06~JiM?A#t^3ReNMBuL*8-rzUGXH1^oGmriweK2>mMplVgf1 z0_}m0B#l^3*$FDep|lAccU1bNa1>e<<5ja}#I-Mle+7iQ*17y6_S~effox=0EHjx` z8?tqxl?pzFMiCK>bskn^9%j2Kf6lx}WPbiPOPivE;xgnte7sWWOqj!@l)CY*vR}tH z&|^!c2sx9Nn>NRBOZ*&N-wR-}W2LY3(Ci5fOO_u7C>W_`)0zK!~qh({% zN_)wf83*l#5D=;3OV+6bK@E^8lw$`qV+Yu1c|Cy{-0#ioV+)yO%3k^KHhlr1U8xYD+S~-Un3al6@szrOipzuh ztmJF5^dG_mo}F7RuU?wI%C~B$W#y!8UtA2gmT{3LN9$CVN2gbp%ZoD*kM-r!u4-^T zQL1^*rcL$iw=Gwwbl3_JaNGvg+_wF(y=zt1J+-gBG$M@nz$~G?gy3765P_)EoOp9D!%A8nxP3!ZyD{$IgAUF?wQ=a_TBg}l z-99EwJAI8_3(1Vpwg_Cdi#hGD$7A-`5^2)akb?(7YOrwFFy|}kji9*Um9&|NY?(0v zu89466o3zl7rteteZ?>Z$`<#<2F~{*ttDam)cX$M@`e87ce1YFouz=bs`09@F7z=d4|T-Xx8h23oeT-e**vRk_A>nmeP zqPxBcmZ<)C&+A0FhKCBAw)m#AERJ-y-F{QD_H%0p6mT_gyLZA=X;)C0etqN za#hrN5wyH7;;@pwrtukBUtYw06SD8h&x?;Fizy)1s!$yrr8T-vr^w4lDO*QPz&pt7 zPgB4T4k80}m#sD`4VF&0kEVhej51Eqew(B`HUG-$+$~S}2Ie8_JLE<&oQ(Y(IrNY! zJUlX@HcQ@x{wuoCH>~Lt;xgMze3B_0++$)f;H@6~F+}L$JjlCzcR{c-;H@FxEtc^s zM|_uW{L<+rv?j=nifoaPeg)XG*83$~-`T2!hviM^4Z=R&=vSIil|;^@51;(@HMD)R zT-m<2(L}_$-YqQ1l_~#cS}(G$j%c^lUBCN~9v*aSxS**PrsiM8udhDkRrKV(KV%}3 z%|MRXdq$mnbk}tgmNUPi)k}cd$W1STH+4~%BcZHhAc;(fbXBETk{CaJ0Ti;AY{?!{>`FD?%3cgHsS`$A*t;dJkyNArYlwzG6 zTMy4oPW^GjVyj3#{)sZ@9DR>=pkXKG&6kH`tGYh4QPp8D8emji@)TtVSMk5hHqFC7 z%=V-8Kh3r_-@nZE3%?O8V76hTdv_kN3Z>%Pl4$@^KIyXAx*arrwY|3Z@<&iAE$8>x zLSlNv@+WrW(M-sZAI1NU69!aJ*B9G>8N1ZY?62lAz^Us+W1KWdzM$Z&D(a5 z-xS#g5t-=aV-&NtRUM^ba3FF%+BL(L6e<h_S6Gu@ZC|c3q3dbi)6nbQc^5x zyt23qGIWrg8mJgG#_v`T5HM(M)Fn_OsE9yF_(gQ07%l?uGEAa;BwKK5ZnPmNMLk{E zpG@x1)c({&{o3}7gs26H;6H`4Rgvojay7FeZDj*!u)$DvM9?3?zsZE?GqB|PTYzbk zLg9yW5yRs~QiAh@%!8=IRq4p$f>>I;2Qyg^jRp{&au6e=Ir}TE+r~fZn&4ku%v!R15nZ*f%yhi^9UbfBD8S{U9XUAsG4EuApnOT!J<%3hU5 z-Y}rWJ+DADF_13|akQ9}?F!hhyrE!WvYQV^QPV+95zHd2pQou~bD9FL5hT>0)`jGS zG=sbdfd*fD&Gm7Nmvf~s7$#u0S0yHeRA(Xyi~90SR+LjMxj3&os?6-0x0>WH`GxhI z{EGdgJ0k?=un}udd2}#RI@(Z4ZXN5!RH0g1lFAdva8;}~=$C2%UGEZN-_W$>WHa16 z|IHGM>x2XS3zWvBi{xy-JB-4M86EiN9^}I#p;Tgbu`<{K{-a-r?)X1DXYinuAbwnA z>s;UZe+J4F-BFP|8Mf4!?=<^tDLP#p{M?K>87m7lTV>xUt$JtEx^68I^|20MMtULX2StlK2_$252@?v^u^@F5gPW00c0`wle7UGt0} z7pSH`SY%2zWv~I*_^mRE1~fK7SAH8~Z#Z_=oXsIDNQg+BeE#?j1UzCQNtGTtv2o*V zFvic=_~O%X`&!8DHZl z0WuEPiO)RsNKc!3>v?x06K<1={#xSkFT$UMC=dyAa9=^GlB1^rV&)8v>~RTf4&gx- zp5UdYBK)Ot=i-e?1b30e4|&X>QxHW@%xlD~0t3xr{o`=uIzY|I_$;F)@jzh}6iTK= z4N*ksDI)cVPT*)I^$hXWWXmdNqT`h3#_MO+$J4E=aearvPe`DL${2~WDs3spx}B@y zrOR7cNES}+BcDoXD$- z`EOI3JK}?*E69)_Q$l0slvxuwexf7jdbuUawO5RS38VQP-ZOv4msYz0qaM#A#L{{~ zpjBvTp`f5{Chb1+VjDEV2i$YK`Fykw;vPD5b*aL&h9uRNr=nF(3bh08?B%UH?}89h zmTOyY`pnz0Rp3c8K??l>JdDh5hXO`LJy~jUEfnx|RAoiFD0onWgu|-xEu+zv$g!i; z8riidPLwoew8$cVVn||QBM?%RAo4Z1_bo|6xk>FW*!MG-tgg|2I*zKDu*FtUF9pY3 zf#*9*%@RTW8R>_Q8#&I3woKF8&nln7reZSUc=-uV-YO?mPHG-t;G#~wPs0lSDokZs zBs}OWebkoKH%euzRv2AQUm%7YK2VWHpB7%AGPn>P9lM34I@(;s4_1BanHKc;{6%&! zVAgnrq1s`vi?>=r2Jon*BFw+ZiK?{LD1~WC(>CgDDM>)`AxyuMM3zcRa0L}p#^di3 z3RCyUW5*bbR&xgQCu+zRarA7%(3|L$hNH+mFBidw53t$J;RG|S)AF46)(7BcFNh+c z^u5)RzeJ`5NPx~*aO9^Yy7ogNM};oQk_$7B$i=|UBq7=zVAZAtSeaTTZuieizd2>S zuSTpzNsccjaFd2o2&)erfq$QhqNE`;mDT38qcHMlZMPD5VWuR6@gs?}knWY}79BT3 zjg^osrK==M56>Gx0TG91Eft;jA4zf~=RpyYoj_fWNQ)u~mo9P{WU|N-r?dfA9H?MY zS_wCNNGoycSI$Yt78b2kFik=L#ty)iXQ=mjuQ;>!h8uBYVO#bxSzXLEA3X~H*=Sswn5@%pm!^77--t)k8dp&ks?Dys4?N*`d^-e*drk}CP z+xx}&<(QQKXS#e>;N=ZslRDB)(D2V0d@rBh^VqRjbCEB%-}6*rn8jy4blm3sv99dL z4kEGktG70V;zLHXKr!N{uOFvzeh^nn)qWqF0=};|#UHP{(Qo%y&7Pf&gwqeX-PKbZ z7+AX6s#SZ+&6KI*tW)F-q^U)jMlC;=9He?aE`|!dbf>!dD=Ja6Pn+ zcP!>NABI;zv9$I6!c&5dh!a*b!Y?fM#>*z8o&;WB3M?jUdP|ay+~07aZA6?31F<#Z z5YG{K+Z=HQSXko_9lMZz`@{D-%U>MvzKPKeRbu@v<#|0~ut)vIh#jjDY8bCUa=V-9_|{G@lTq zqbZIwf0H?XH5p|cALTv#s2X>s1?6n#<@NxDZnb3>ybZ}0VbXTFIAs^!J_|&DAHKUb z8|iBn#SQ6i^DzrvFUYN>-_}Y3qosz-`E9^OKL5RlglHFeex2>d-=Ba_gd=&0%Yo92 zKJqmYMLLK@S=X1O34Y(;NVAD-x3T|0Ka~NNM3RCul872X8B`3*I8A|j2+DZ~ z?pechwbe65q8nTglCg-$a7oGZA^Ns6r{{KO+>g=Vg2vA8RcaeJogTOmw_vVg=9y0U zYf8gS&!dUiu>)>B!+Tm3hSMy_Ty4VGhbI}*kxv}18}ql$2TUc%SCfX2UB8|N_Bh3{scmU53zvRotHJtj?fo9}WK z4-cn^kn;otcAtN}AOwAu;t5I+G7*z#8?m!mrcJ5Vc(rD~Rq2ht$2?0)`_gHnrFC6v zUc+P}&#joQjtHm6%_lA9d|sC=p9d*nH_Rb;1j(DhL;g!p!e9ZmZi|a=F=O0&{M+-s zSNRWOH+4!Cx1Cp$^0?Ca3?Y{}br*~YGiT1D`YIfJ7xov6(fknvjb`WMn^U7ZR%$u- zFO!Q$lGs0rY5SeT(qQsQf9+17dL8nrj`6B0)ZGs-xycLUkG3o7%Xpo>z1xK9>DDdu zulxPl7iCgrxX9gexiMGsAmR_kl2-zIR;PZu${J?_o7+djG9 zJ@UMGeSYtV{i7eZwt2d!d$ZDXKgg-~DKpOB;+%>_M6d_thLHy6 zczX(;ks0ZiV0??~y?oQG70BD!`^)FBBDZ<7eHp7`nJ{5L=q+%)HR_bMn__xbe%JnDj_NWyYrZQ=TB~Iqb5`L?Rp&58+2vm* z`|{NwaXC(|PVd@V`?;C^r(b;mG?v?QYRB@`XtvVRz;XdJ-fBy;RkOx1<4e`&@0g$h z8KI*bY6$wv>ZbHHoO`QB?c{FW6Hbk^DGHYRyCAMm-gr_>4aQNwmaE*qRKkb!3S`}S zCLL07APjPJ84#v&FiyU#aK1F(NiSE~_9vWsZc$=3^hxQ`vTwN|FRYc{_c&2v zk$XHHBlu?Ahll;ppD4$h(>@C)t#kp8_#D)a=mCB1>}@DBp~pi=rfy2|)4QJN`f6bx z>BIODZfBxH>{Xhe7R~1HMk{W35y4JBtJ2i8d<+3g{Wl!a*OJMUYynNHW|8`8i>xtX zACn&hrI$FfjPL4&Z@wPm%T*gXT3xO2Z+~LiwHGhZSxnUzH}%86X1MN+z6>Pq=*u}C zV2!?{MzrQtd~I?C9W!|!kBoAeTZ+1}s)eX@uuOh7IV@!9TD0a^={MLDCZ2M$B{v8q z0=*)+D^H+r0qMduR(0T~)dnkBKG+wuWX&1n7+R%SX=vYKThyqJdRk`PFBtu;hH>uc zBW$NWgD5SysH3P-Q)YW&#BvJuFdJb57nvTF;XL(OM&w=Fcqug^aFijC>{H{#Dr||?w$)y;; z4QB*gblK70`N$XHZGDvS%a4nAKQmNWtEl{FkiRj}Y72y{lWN3bhO&j`C z=WuOX&iTfy=U1L7IZXxF28H|9a42+LT`_AkZh&z?E?juzjBvAW5LQcd(3J z6&68Hx_^WMuO)?4nNwIat*DN!BC3q0C@8;3P%BG$xclZ(6pU4Q$&of*a41?!I$CFB zm((=C;zf;)M{!fuD8I+^h^}LqpQ*N<6*gxjAjQ#{&?MBb&KT9&N~T>c%ORjERp0XH z6;ST`-8$C^TfH{9#_ z=f`ENNzIh@{n*zr2b;JbWZ7%l`BiJG$`#M$a3y z-r29FU|5G#U6+((v6_EsYO^}*y;~30@yW(Rs-35Uz`e+rjMfvjCY%YFQuW8(D<{Fk zE9s z6;H>R%1iul`h)DKYH~prw>xv>&Zhk(lF(w(Cr943w0*jevxjZ;+QG>r=eD!MHCFVH zK9e=!PJaz24k`1DP_h8JdZRfe3>ZUsS3zBtqML0ftHAh@^C z=E^xXT=wa&UuR6G?0k?8^WbMrE8F_l2l;WDdfpm~CKBno?>F2*-Q8^hO*n;a6?V7Y zq3rC~-+k|apD!(2ao62^ksp-ZvnIM|SV|W0fghnNBm{fy1^Jk@Bv||LXEtDM#_qkkcZuXW(O}CiK0@lyD?s; zkGEa1vj@1S4hS=D&q~DB31;GV!Eznd-enC}ytE||4N2tBofHt(7%dXiz4TVGeet&} zLx#)e8_zWR$ZiSzTM4Av<1F;Vv)Glpk?pB*q&z7&41xL7<0&}Z>wvUbuQl_VB`_reZET4_{m zH&!~DTy3aOJhdjzj<;B|Xo}G_v5DK*B4TVVw8$p}7xk)F330eZ2)U-LUNdMzY_2fL z*#^$TRQC+gfRrq&H1gchkYx3kep-&i>N9%Cd{L`;=HZ z-9eX7_?bJoibnMq@-|PETM$H(vh=MN*Ea85e7*Cc_by6&TAP)upNk_Bmf`sjA8Xq6 z_h?lh>9zH{o&sRgmy14w^sWWhWcQtH`b8Uv zMNCHkfnIU{iJO%SYDdHJj$7f*dKkZEZG26MMXc*!seJ%9iDb-YNf)jo&!e6lbGt}% zzqN0>o$XzF1((LTel;S)Kkc`cKL{5_@i*1h5bfKT4*lqUD&YC8#@RJKp$k*7h61x0 z7W)x480C7$_(!q~Z67)a(Z|(!W~CEYuwOi|9yrUv9pn_|e6ZgA{htm57|*!)hKSEnMn8Z90~2>$7#zd2!?11>Gc z+UdiY!f@mkt;o+^%NP50D)*dFH%jRkm&|;IQsH~YG0a9R`A}prwBP)yB&JU1-aC8p zlRAzY+u5{wZwHTfUIMq`K9gB4SDpqn5Z$Lh^e+Pq%x%--Y(Lc1ah8k?9&_|^E9QDq zufIW&;GZx4CpduX{|*k|U}XB=-~g?7(oV;pJ*Z0YZGkf)if(>4=@eZeUq|Djy?nv(&7x#;}3gM1K)m{>jJ^ZGA&G9 zOKS%IzTV}f+c{t6s!`r6QqU-Bd-Ot(E*{nd_C9a(<@*Ouyb^EFj9(1jel)}(VZTGg zE|-IN55E1rr>Oj2E)A~zKE(3B?gZRD`ZW$qLY|RzUfes-zla{7|2f@0T-iK^n;mA%Gx6Xk%qS!%Sia3B%A6IBl|P4sC^-8|B$E=O!LTOw}MY zn^72og z7Xl+QhkG8PnPHB?JJFS@NjIBDfsA(#X6RIrqKJ-f07Bq92841ph6mvpagz;{V!yHJ z-UGy!11rg3eMdk@3yrvv8Vnnd)8r$>DdbEu(;JAydSf#BJw#}!KCj~FWud%2L=mSo z*|n@TPc8>;>)d$VnTuCXY3>b~(bWux#o_P_c=G-x3t8+$PDfzA=`jUc$X*`S9B0J* zEV90tAw&%G#Nhf5Bu^vEsC8wvdqJP}u zt!xqoo_Gi+PA`cH=Tu>vgedb*VV9-x3}ezOH6;ZuE2*Xae7qJAY$lQ*B{c_H7>nnp z1P4~+H~zko;vk;Gd=uv&mXH<1H6(B$a#B+S-7bSo%npKnh@fW|logKv&HN{SdI!G3 zh^iN{O%6w*lW^8u?BdMBT>pBANuq$Ac!GTNfEbdmiPK%=hq)$419OABg^BD}&i$!V}d zVX=+3pjToslt=~VXpe$~ijJQ|D5uz2Sc9}$Cx{}Mo=N@ zNw+gANJa8$LxCKo)i2n8A!d)pkqL?zPS~j-Qks&pR+XuZ36MKUjD;1Av`Q>t+PHVV zkJbDaAx7F#k)xumW`-+= zDMfM_nesR{CaZvbbQKqD{tKmY6!t%?hE}yp(0DG~M#1u+my$b|-<>-bNgnx8Do3_LPM)XLJtyyik~49^gX=`?CnwZA znQ+h0&P=p*g(N%O;!@ZqMBUKcv_fD6uc;hL zO-hQgwvrR5>t&PElfqqc!CvZG0|*vOwJrSsthwy!spkThEkRnjApAhV@>B zU^lj?3?1SySsxQFM|36Ov6&#jo=2wWK*PErS&81OhA^`k$yFGI>V{$qxC!gkAHyl- z|LzRgJgrgjp4h023%kdR%|_N|LpmF^lB8cYb);V&Wy?vZhU9WhuIHLqtJoLT3kttv z1;<)T$Bxqtw_0}^Edn~*RA&y9F_gokPfO9#Rkub^XOoxOEXky54IRWNZDUsdDK#nB zjY^fZ4>;1XlpcLQo{N{Zb%bGSEJNd^Zc2A6TaBzr$Ns5Uj);}EE!C>8K;f>bKw15% zS?el*my4B2SgF~JtxLsSRR7aeeqFi}883xfh*OHzmc4asu2*|ahD&3a!b!cbjq7_^ z-zl%E_#j67=yY3yZiKRFGm*xP{iR}kg(WBF=V4-h9_C=J!mvp(IQ$Ya6iXuuJ3%+m zYTxOq2$u3-Ms#DHEinE~9gp5OqQ1w?=wZ; zja5y230WJ5!=lf)AdWDasM9ogw9^!0&1G7bciqwB-gW$mjJm@#eg7ayPG}Wc4tiWZ zL3-V7aRsCYr85a87c(v8+_bq&y}X_bvYK}35ZgIJ$}NCOEYYUiC)K8WTl&XCvMs7^ zq9324tw+Qs+$L=#*^6FSc-u=dqUGUGhq21s6XLH=0|8YP{96k)eVY0eUoi{$G;%YaPZBPjuIjxDYjd89vO=^mZw z*X1VVs;*HuvKO**{afC3^s6e~mlZ!c$UiT?)2RHFu%- z@9udjHe;p`{mS5o>YsiaVW@i%gD;S!;>)EpR3=C`7;Dfvbalvh7;8dNMT;+$6|8=L zRk49*;eI9$kqWt4_Mgck^=I<%U+}8%xD{Yu-*U8)-GZO92Fj&h@%6Qu?IBx;qK0D}MCP3c1>bV$eUb+Guk zPfNlSc3Un&G&tK}9PBU}R$xCw>P`i(b%eY5%l{@uz0|c#n1Uy^5n6@_&@$dmM;H;> zI*_xW3n-C3y5t1BfEmA!{otPuI7NO+ROlNhDS^{csAHST=>8SABJC+Z8NQ4lXb6w0 zL7YPswtZuP9DNC$Bqtk@JZ15q8g+Y~5hrzVeURd1C~Nq`cz5``F)=tBrNTorWed6V z&#Rlnk4|Yagwr>cqZEA6G0XF)m3)9SR><3;#W!PU2; zquq{B(mI0D}v2TSAK1OyII z0XA{pnM)Wx0vx?^vhuJHni=*DGt$qBO;jCFcR&Tay zFWpwrzM$%3bbAWT^u2Ys!c(8Uc4%f8I)+8Fu$>+=TKNmhWNl)Vq*XuNqOmkqZ)b8L z#D+6Ib=&D|wRN_GVRRR&-pq4&40~&ICw6v!9Sx|zrZD)u0dy9h1@B--aL%-dq&?0J7LTFj!w)WSB$gut8U z-4fE-JVV}|X*9)WTmLTdkZNK)va|9dap`EKp)3uio31o(QTfiuuF|8Tbj(POj?BHj zv&hpr%DnuSBb$;uU``%jUxuebmhn_o-0KUPbDo@m{H3c}MxJ@Tq>EcllCSN~KH&U7 zhp5?1Ly$JITV0c3v3bqr?Q#muA~(TO#%)+MqY1!W+g9&B+jv3$g57}THq;)lVT-f2 ztL|M2N*Lf6-H*@6pMCL=CVRV}>gJ2vEbq;#mnuu~coY_%v3y33;zr9kO5?bDbIA); zcou7?olj=y3fO87WYO_juXfifo5}{bul!ZI2cCTbl#y_-kj&aX_JOy12h8Y^ckC?Of2_q|uM45}Y0PYTu-}g=zQHgLH8RaGv|n#M<(7KrxI#wr9Agl*@u1xeAE@iuu|*3pq*W|f7>D^` z4mM-kN#<%r^IJ62e6!}uK;DRbJivuhOp~QK(P{kg60;?v)x=KnqGACZ8||1M_;vkv z(5Q0H_jhqy^b+y9B@FJ-y7Gvk&f{`>o)y|35DE@E44=Oz$eQ#F8HUs~s#pC{Hekjp zgn4~yHv5~gbke-AqqZ!bZkLe@5SZ67EN^w_D30ABY7~dWsso#vx#R7@f414rhR+}| zyIB$QZ{6?HT+n6op9C;yG?PProxb2D#djyL8*!YRGj-km7O}Rc`K`5!NqQJ1pb84g zuvVbTyAj>J433Z{3%B>)_q#mvzD+iVV=mt7{RVXz?ifqP|xq$>}DV$&=Yp|*Sl zQe?A==C+3fMPb$>G?T#nEv}m0f;LYjCPn;nF={onyG;<#+p)qx1gGS$*L8 zhjwUHSTwj)B3=cABTVW`&rBu9)sWD14trdXz4~{)s_E9E7KBrDw#) zpehOe-b3FL)1u}~eOJk-ufnof3!Xd5Ts*IibZ7dr-}{}>sLndax__T{J=WE`BvD4* zK8%XYm&ZhHv(067v*$JO!4XgT{J_$~I?%GZnr}%@3B;!m=?oe^%dw?$hXrbcJxvc_ zTiBG^ZD9%;FV(d5EiLR#A8L*_P!7xC2YVkv=zOm*|GK%q*}CsG7%$kM`0Q2YpHp=lv z-o0CdHiX|>tah}hcA#cL-Vd~~PDDGm_(U|Vm29m-50gG0!u&g2w%pg5VMgHvdwc6?mxZ+&xP z*GPkO&Nh5lI)(0$W4EZh(lqwkX3*wX$yZLOhW$d_NX=JXNC#opd!<4fG}U%nDOZ(M z3sWg2r4~RNJ&bK~s}Kl9`%c{Jep4AV!pr7u)Aomv8P}>oTVBf_Qnn}lgziuefT@WF zIbmfGEDh4MX*p+zZ3I!Bl(*qT+k{~Qb4Grn_yG}sBGe>6@iV>bEEi@R#3(J8!8P|+3JV14PO!M z)g=tYM4(cQ!!MXaQwLh6T}PtqQBIfkc50Lq33t#6tO)A`M(ymFeq#d;w7ZKvfU6d5 zKVYrtfV&P(RL`dsouhQB$qCx6W|n3@*orgrwNF|6>oSjCCl$_j7{n_+vNutBF&(d$ z3;7>LBUU=f`sFWrDOf>8A91`59#VVrU-Sd5c6QPYcuq!f{j>XT+_$YGEUfwP9O^a; z+6H9x?Qa)yj@0ZyUa|XshT9$9#Ie zrdY*~9jC6ZZUhEFYG<&OFQ#!dc-{E_$vf#>+We)pd8Sm4;?n$lqd9)s>Q?k2knDax z$_?o%e=jEH3i6Z~oCm%F#`@(_My$o4oRcHUzauw>vk| zU}Z>0@AQT(-_}Y^HLs?6t>w_10x|e&#QWd=a(k>pj%wFeIqa=lw$7j8t*T4mD_RGu zrfK~(&{=W4Yr}e1yOQqzeQ4%;o7L*v_5HR;{`k-8U4QUA269gkoWgrxl^bIv6=+t> z_4%}Gf#HM!cteC^P2bHJKID7oC4)!1uf^HC1~k0)Z@Nf4=snC$934#QYQN| z8Ad*v&hJH0+cY(`#r*}=v(HciuT%<|RQ-yQwvlI<>T#XEk7S14G8NLn*~%9#^RcYk z(0!$NB4E=feWtm^hI1ys%Lg|PWRtUU;u;^8vvmk7m+8a&B&JVWoy)~6ZRQRxJ<6sL z;@wN^ zbMPXI8(JfWahsk;m<`J2a;BKNdKxh@Rd&4p#rNiQ@mlhU4W0Y?O2_bi-?!1bk3 zCgHtplk)0)duy%*o@Y;?Lzu*k9 zAoe;J|G+YFOw(VRB=noVhO+Ka3YAc~s`86O{Gk+6CF8^9E|f4M0HrFPf$|&0ET_T7 zd+&fum%Avuu_`2xw)khkp*LbC~eW!Cw`5z*-MSN_-|DUqTtz z75HC;=DZlIHHHHTM!c04v@V96l@|Ld(-ON?SFx=!nz_p?HyMmM%Pi>pj}(v0tU1fo zhVIJoc4dae;*^x#*UNv*cI<>^!!?=$vkT4Ov8u^1ngKE*b{}c=$7GY%cc)osdH)LX zlD*gZ+tkF`KEeb4xpz0v3Bfly%4B*VP2e^zBB-&h0W<8&vAGYPDLE)8RE;*p^0v^b$8<=u{Is zM+a_ah}StNZqQ4RQ<9TA6C8BiA+;q7)>>kR%F%j0!iT)n-~p>6m@zd8cr4u`@ld2e zjOg=nq-j{$s7s4M_%!CIgI_fe`i#u}>=bZ*8P47K?l!guF2l97{9^=ky4l{|@IABh zxC_FlWNXg?UFHp2-HHMBq@G%TbvYmjXE%FrOECDe zn<*@xJTxF8)e+5hgh0dd3CF{!4c(9i`-u?OTswkL`+!S}a@7!g6cgON~&dI@6wKoKenp3>A zs)0vfh#qrp+YCyGZysE?HN1cs`@+Kc-&!5AExecsQx3K2Kh2mi$-eMpMi1t#VJgKx zPY&3>pB>g`=(aUU=xE=sQ(jJX>Wp^b8O}s3!m>EFwb}YR)U3&$p6a=#cvqaVJzZwt z<~pLA-zhOdu3@>JP7(O_`monr7O&m&%{(N3jep{`I^#}w1z?1;Z^T1PX?(jk#V>F`8Q7MOTj;o)4gGj;O4pzj;Nl8Bghb6 zvs>ZG8_nQ1>NtT{lRsNsXRVt8WUr^Rj_ju+Ae=Gm*H)3 ztKQ<%o;bEbCDC8~PmgDL_$CGD=v*<_W2|{9!`EhU zhze5hXK7pq^JcSFy7gD3DGsOcnFhBi9Wk{d_5l)h->7jAul}rKIsyL!V8_Dre+St8 za02~s%@8`fIGGvQ!g%Cd>1o9u|D4~cHyD)I8gRPj4;Tb-ejGvsu>k<_!I}WTi!~b7 znD_JlMevd&%c5In>0MkO(9&x7*Yq5|F;7(N)0QB9lS6!eGA0Y;8aJ=)W}jcyr+@c3 z*qMWV*JK<@T*l^y%-bXlRbke(l$uB1vNrk zPmbJDf%@ar^wz&mpBB-d?=W1;>s+q`%3rgb)7G{#tJ91Uh8dwq8;9a|4AQ;wfYs$R z-C8qHFvkowGTT0Qj^uCk&v&V^Oh`XOvGVc7^3^g$;?zVl;p;ZwyOpshR~u*5eH|cfb(Q;QH_WS$A!eADbZWG;t5m^J(_!LqiQ!R(mHKgqHN3VuqP|@RFJAy)h0=Ea^f{p?H^u+mvU{pQfjNi zkd-P9q_1i_k&YxU%!8GSnACvkCG)f|uU8s}r~YAon(T-hCVOH*&kpQuu>o@9o`VR3 zVTjl6Aa5kt@XH+UdF1xpp{If&<)BuuhsJ_|NacVlXQ(#e1}#k3G)giNSfLphgtkAt z2q#HPK~Eu2egnXv168aSI+6}9@SCYO(BkgvBoVl&w2gsJ>@A^|Rh*QNxOVEaaeoKR z9g#B7RfO<=wV1+%pg}J{u8V{Ol4TWv)ukc*xQk9oI|$SQp`Rcn%7_(NNJrac$l0RY zVf8j6Av#Fus3N!tdvZ|R?t)v$wO;9X6%zuX9^4^zM$hWe0NGw7vpg>u=*-bHk1x_{ zZ)!ep5yKd31K)B`@xx{>`3~{SCTb8cIO$rv7NKPfTPUTRKQ`xNtZorH> z=K@^OQEr@O1zml?y}^+LbT98|{BOEHN}kDhOvpX7_D#qp0}HQgh(q^$!6Q&F>aFyHqd7Ojlc??rd}e~A!lu&|7ihBkfd2+ zF&Wz9aq3ZkC{zUPnr)hO|AdDmOeY9#)br9OK^VzxQ80knu1PG&P!B4!H$ovJN`f=n zjShOWbh2Zdy^}4xHb>UmCb@ARkNLWhO@VUQ1NV8|afa}c6+f>__YL2!9~Tj)m31t{ zf7m<@YZ1i;q&Yj02{{&LgqvsNP`s&bh!gr2t~a(0Luorln;7c|)y}x`11U;^n%ENT zD6)zgtg*wlE{a8-qN#vdob~#5H4`B^`}K-B(hxkqea#eSumo^%1Ril11%$|7G$u|R zs9jC=A`h6x5lAFZtKSGAb-xN6Y6|5;EqQ^|o?$iDoz*zlX34)t+1WcEk3#)44p@Ud ztn(uZqvschvI0(*_5o=T0KDP#tiXfQ_xc{xZX9Bw%FGS#7*6eJxAljVk6w27qV8DjL= zv5_uh&{NsI#7`ilWFL}uSYvukpU0YjP!ae?A<^3xkxy1b4_T*1y$B$FU!NYN@wvT{ zTcT%45-Ggwu^e6*?Cd)$OVsL1@&kI<2=P(c->7`G08;v?otP>d?D%E`z%l(~84gE%6bxk;@8x*aAs2nTIup%|IEnXV07&`ii(?Z z*U(Zns23)GQ`j`|8VkUcAq_Q8l6V6JV8kmatak)Piexjn#m!A=Z84P5Nv~{=bW)LF<0OKU zKg55?=gC7^fw`#aBDcjxU!Dn!1-+7!;eErZJ>3*r+aSyf`Cv=1#p9`%+` zjoV*6AV*2%G=_~6N79R{6r7)CwjL((F+@; zm6TpoJ4`8QwzCwJr!GnCzQ`JTu|fMWJSwKunTwy3;gFUV+H|BeR7I*-$@B?gF?Pe1 zPG0Dws5WDm)7SCF>g#%7|ApBWxNU)y@R%mJ3Mxmw^xPaq_Vdj8@yTk$tLRC$9*M1p zPszLIc@LS*cxs8Y=o5h5_{Dm4>p5`98f)pFC0=7<6>?)NvyvB`SA`7YM9Lnwe#iN9 zCr7-d{33uZZYjcT9N=S4zT^>Fb?Yp%5;0Gi=v{PM5+_BXkcStm=!wRfw(_`GkxnKO zWRgZudO>)eSJf5eZ2ndPnf0?uI_nT!U8twhf-E7qjA%8D%{Y%+LbmSYHip+AF~!iuTR7#6 zO}PBAT~=@h%{X!k)2Wc0V#wkt`2Z=}QW&rPDvMJbUj{nNT@R}yS!H$|@hXe6oLWY< z&g3@2Q;}#fy9gmG8|(Kly?9Avbp14sMf}OcQa_pa^d}Rq|77A~Og(pKcQrvaYIPy? zva(O^Y-2wnn6i2?<-ge2nxq+4aB1Mts%w43X1bYmZiA%*R`jUV zezjNSXn!)OIF_CN=}#=Ph*PKyJPLF$kE4R=|Mt`l>i=|ly*ar%>X-Ys`}xux|NRCq zUQ!V7eR;UOJNgg8;}3fCxz_*vFCV}E+#t=x=eqjwdhRZtj~~#4_?J0QJ6IG zW0AcNBW{+ffh3H3M{nhYPd{$#oNBh%Ua0}cB{}_hA7nyv0SQJZj#$kqgRuk$TU&-U zN%^CG^s}=#h35cu)Hg~^olt%KV8aRV#t=juZR2^LmaU_)ywW3AK?5?O)yH;x$rey% z+Tx7AEyQZFd0PEL_ZsP*0BvcrVSJ)PUDX4iseWhb@p{AfqwP~Z(4aNfeDP_Cjis0R zp^B+*v^?-0CZv1zXcbh_gJJvnpDJnbI+@eT zu^OK^en*kENb_&|zqWi|@1a7SZtu!7%XQh6&L)SeuJ)O0D|UB`6BdIpVq-deEnO6y zzjnM0D$5F1Wr?(!^Il;9@HuVrE9iV22ni#uW>dUP$qF>9I~|jD?D)x(9Ll5cw;pPn z*6)Gpk$XF1mRm=C3nAMg`~(XV*X7?!>iT)LCHvl|3i+=~7GDpU0-t;*3jg}WvVUc6 z;Z;@gs3tJ&29NnyZBz;irqS*)7}FAn1t{eng*wOg6bQt-FS|YCRUSCCL~=m9vBCcf z>hxt^d`?;5qo-u+x^cJ!rxVB6-Q(^!Ek-wY8HA^Y^0kINvDe?9F@bv|TE|%~U^3PV zo&o3R7~qf5ei;lECzt_e>KYIa7Kow$t)x*5exVe7FgY&V!r{*L3;LZCVdU#PP!rnc z&+i!BklEW|?luN=P?=o@^zz}&w~$xIkQD|iTCzq3>FYe@Lqr_qO|K!>;*>YB*wT_G zsmNyUsB`)ee&a^+oNJP5WP|NlDU&i>rWkytFZKPyxAV5KMq-H5*~c;__O@IcTl#B& zO_2TX^MTqYV>J`5t8E*Tpt&i|#2e6zhToM2D~j^Q6v)P2*Y3Z1Uj7&|!l4}5emQva z+Av{@Y%PRlE5JQ-V*V+Fg=2^*}G@0bdEJ4F#GuFxw_y5%}O zEe^r2<-NUxzeFlNk6PGoj~0zB;pt&aTY1lZbi}tZSSfsh+WIuF*DH7FOJ3ZOI;SYz zW2`>tyg1dVF=^bBJs;MJJfK_M;=mRe=Og*sImPA9bw+>2NuXQ)kjr5V-b_)NpVF6c zZK=N3FtA17ir@~}E@0tXHlDyBkk;#a+UI%0BnRJ(4Y(owlU$I$u(C>}cU|J*sQe~% zK6UPg3-9Nwz4V`N%}??Bk4>TUAvaP`xR*$c$$ncyydMdqBk#;^3&)$A7bWbJRMlUjw$%q^Y&IZo3*W5v%ZI!`sf zS$%G|(hjjoW+PJP&1$gp{Ep;_x|B@HOT_xGDgzHBU*|Z1x#ehO<}1;SB7+Y)fC>bF zh@?hra~X_U%}|=+z0=z+JC9#RoKxMzO`Cx3jYoH zBSxTchml^f(L|H4AlG|ItQ&3uqsgpU97Hp12vBR+3bxIXZiSO!-s;-j@QA10i4?e@ z8Jmc$p4@liS6F1X>*_+Rf88h_zfH&7*77~O%ucgm`-yAiPQ?>?Y~o1?O4qWkHYp#q zL)u+oR^BiIz_KT6N;2G8#-@@Vl=@64P4BN8;&*bbl6?|kPkL$i(_bIcs?}OqPu`j# zyN>?aO3YYoR-+8*TW#$$RZ7hjX;>mUVH^uz{IBkud43=cn@XqDHcs)1PbR1{md7oL zH$a=?q7L2Ty{yPp&C#udvk}1cF1^|tM4hNXqi2V;ZWTK^D4oZB zEPgfUH>waP7OsxQs~M2HT$g6xO)Nv2|F7pR?JnHCLb~nh)tz39Owg7^J+&~jqQl8L zwE`%PEE$SiD;z)2(C2sa7ft<&rAfKyf!;X_2tQw zc8%1wR&q2I+AP-)R{G*?0G$w5&Q~QFW?zwZl2bVaX24918YHbrHK?eRHZ@rev9cr4 zek{s0!G@z&lUb@&=BKYsw>s@eiFaj4l3ZLnNX%%iF3p7|l=QPzq*$>}^^mel6g5vk z(56_W0$CL_Us9GDP*IRCojjTi{3&S$g$f9q^(T*VCJflv?e-X3{slEK3a2g_B%xJW zOmdbgCw*Tzh{31o2~ERG?_rdNT!f$dW&Ylq^Q*DKck0rk1(M}slg-g6+<_G}5UFZ~ib-|%^RJ+4XlIb}r;Uv=*Tr|h;i>fI$ajfR$JP`WCQvGRAO;15+jG;d`@rK=N)qklD zf0>;;z*4Q0B2Ze2o37Mb(^m5dlt%vpSp3Hsw_^L_@|~kQ<6(5jGz^?8voV~?>}105 zsLiOAi-1G-p(c-Nr_qmb`quZG(h)vAt7_^@?uVOJ%{5K+ZbSp=N(?7OHAowfh?+Oo zShH4d*Z})aADzOtAL5JM51~i7Jj49+630BlIh4QAT!U34lU7zJ_=gj0RalGZyS~&a z*Le@Eu0bo|d6VduUS?7wULZa9_9dhv3$gxzP)sUMgtP55~9&vP|Sr5xj<$O8ltv=6?fI4Q|S7ChAe z!a4=n(n~==z^UMwq*+YbgE0+Jtn_80E83uh&Aul6S~8*IU~N>^&ImCqngE4BEx)wH zX7q`A%_c2n5g`TdFk}VpXsfL(ag9iFmOfKXR=KRP{zapuJLzg0qUdBC*&=;Sn`kA~ zWP5C|=onchy-B}dDgSRKi-M|uRMc{kA%GAZ$4TWLGbz->IQb4XW9Ufyrz#kH$7emk zU`$uBFUBMVIdj(gVPGY0#4*)kqxdkmTAMQ>u5Oh@8Dfs{~Z_-TOL|HETKu;+9 zNE&<8`|XZk@c zcNRJK!MlKa6C$gHmUs2LHGl`r>s^`Ic&3mF z5dEJq$3r&r(w=7isLpJhe`zN1-ef01{2ErXWd!n1U6P+r02#4>o?G7j*kpXe7o_nc zFRnTD*$RO#kam?VC3WY=bXR)ZyD5@vgno%VWyP_eL${|X=w}ulz~Qg$ojl%g#;uSu z-l5?f(+r6#D{L0?FgzwkiZAiJBY|T54{N7A&hcr!c*0n(o|bOej&$j-6s+svQ;Q;p zEbNKKbnG#UO=)PI`~@_iu+1(G5>Qx8;->&8yi?iBx!2-Aj2R`is8zt;PL0*jXzF^% za@8=;Dz|*QHHvCNHus#|BSq!odu=H3<}6cRv({LBH!|Xnepq&lI(U=Lt2)+Di+0;d zZJ&+lZbmO{a$W}Q=riqJM915j;fZb$^FcQl(}86p^~xPpH_&81(6V`SMc3&i%fC<& z&o-^Frc%~OzFuwa*LU|i^>R2~N|jYBUt?CAYniccDp*Spc(Gmd`J!EL74}F%&kw&{ zeeA?9svk>fjU9Shy_1#=j734-QlSE5%DsJSY5&pxpU{c_kAT4ciPD~ph3iLm;Qx$3oN5M9VFZ-XB@+bJePSXd` z1x@sPW6^=pM{7Q$PB^>-ATLbL^~w1>#JHkhV<6Ygl@(Q_!KejRq$)QFx#XW#qPU!X zH~@24oPQ{@(c`+QG{79oMDA^6grcsVBscx z^DkZT$eIbWx1LFb=^C#A=A{A};|-wsO^agAVq`KMIewiv%^qi%m$`5i#n&wqzYIBM zs?+EjGSO<&%Ywf(LqVz$en_sf5_F`(v{>+)O~*o=TEWBb@)DwaqJh6`z#NQ^mtj_s zSZ*j>9>V@!%Qy`oMUWw)!Ult*ncK3x{iwGT$r5iMAGr2D{5aQ5~!DP|DUxQbbvoxo!&` zypM(;pTSZY0vQTY$gfPO^cxAiIelfHT9bBAYbZg{<7328yCGyVlgGZjdZs zCp=orkwvbxEDg;~Qptbaxl>gN14AvgSZG-Q0Y{0(EF`jPa_~2JMsS<@IbwOg@XQzd z#}1b0al9)M@R*3L11fNE#BD&Z-7tiYJ&5-5#cuqsHTu&8$o6W=Q-pLuq7w0QvsHdg zR%}hjHlz+9YKQR*C>IvfVA-NoF7yPQbmDdBaoA14mTuflKVTxqp29%)2G%mveo6-= zu`8l#?UVV4{ck~cbi%-U@=&dTVk1Tq8c>LPd}Dl}2v7$xp8%nD)(sV+kkbl~UD(}y z!GHilPMlR(Gev9TfC1h^#?>MMrl$~0pXs<=ZP2uWVpLI%O-^ur*jYj{5LD_M4rPx4 z!mMN%VVQvdhP4G?&7e6Yr1DtE?|W{TAYjlS5K#SOFfw6roqEO4F|1fg^*Iavm^fQ}^xw0I(a zD3fkvcoickO{fPu(#>&C9=C|F{X(s5-5Dy({SX%+B3KQ`VkDzk$Y0T!4Q=%E?IcCR zzWe>fp6AC!g;?0F=;d+6#J>2pqOus-!3em+NnL`g(1R>2sG7Dv$&!uQ`F6j%yyYJb za>4<&LayNx{IfYP{6=NqN^r2hSt!Qr zA)9{)ulGqbbqb$T;(;~YlL5^jLk;uU>@=RRPD2^9H`5JdaP#Z`^;gif~{6R9p`ZhTr<%!f=*X$)dex(sF7%npho7A?+8ay zktKxacz03&LbMq_|7GN0sY5WFkC-1tREvk@!pgwA1T!VF&`MMpWs+wV$F#u3^x$V; zd~9oP0)IZArp(DY3(e5d+$GS|#8O>Q8dR@TActbii2wn!cv56)aXe(t=%v%+mK3w? z)Im)zKVmTC(nI(Z;JmnPWFMPyFB?J$=FejN(uJ`L7$GgK?2hkCSxTDZvVhAbm>e*K0QBM^l zp#aS3dMhxa8dFMz5mQwnX%+Uk{+JUb;_*s`L=juz$jfq+z3fEZqT`?AUQ9#1B&UOS zKc~vbjAoVgO5lu6981`QHS_cUG9O6VDdgD5LDXFyLYzipe~fFHp+)DHd|eSL?pRl$ zWJuC(oNtr4p+%PgPLy|eLrvMH1kG)NtUpKWp$JzC4kwPALryk)?(K#Oz|z+%R0KtR zBV)P2MdALSCaN{)I&(olk3!>&0|p(8F)H^P`HwR_i>#yq~v64 z;>;Q=|4X~hAe@boHI>RAbdSN&uc|IP@R>$Go~E;%;mYeBYC$VH(}}P>pIh%$mH_ka0wqS5F7bruSnfe3?|PFDPb?#b=0KE%W_i ztd-s4;9TsIMcGUT45hVhI&P!@gdIZ`ZZQYyq*MVxt7=*EebOka8i0|*GHH}e4Zu(f zf^I1^abqb@*gOUx=#5xz{b$4KCI(;i0?R%WWivuz&xB(rT?caZIxHZ;Vrh2XdVg2^*=+$z~0 zo@yn?BW(QVJmdmf?rNK+R5k{sa_AbaYS>IDB4@i13qhrDV4}XzaoCLHC>LGxlj~*D zBwcljSk`LTrIczvN5s^+cBxQAeT!ttw%cX7)uc0Xs@R!h8+VEK)PhX4YeXlhXJV$P z^=wH{oLUQIcV^a^$2IB`)VjRlX_x+*H}$B7rjJCaJ&*Rk_~TYH zruO$fxcAK&2ef|hi!FIQoglw zaLFe36mQh)z%o+`*KA$d_Slbl-F@{-E$83g?zQx@9gl5(K*Qw=sy^^d>wD|1?bxgM zi0fOt_>boI)|;@W#65F*Oj_~UtF_vmbJ2_*lPVAHT7LD?rfpjlzr4o0rgzku+~v_X z@A%@`m1P!mAG>5-ad+sbxveLbnA79MI+q_=V@{vbU#vg&sS+1l*l}IAhFw}@dQRF| z>-a_^tM|Jf;z*17p8v(UBUhC#*{SFUyTm=U`;~7r^z1JWymia?3ZIldbNaGMJysog z?hP9ry?e`%6JEaYnz`-vjy!kUqZ5z$^~la`s@#2gqu~WVHf*$c)ugv>NPlo+{gN|A zHXi@*!?%8r8hO@HQwOxY=&kilp8V&8j{oX^ed98t?{0bQ^p{$k|1mgJ&g?$!*@CAg zoL=tw{<9uC^?{@BXtQd{_Vx9*&itbCvbM7x`Q+5r7k&8pkV$nWl{{kXgm)ILJ@~Mr zyNH)vSEG=Cu#5u%PM07f&fS;^sTPA6tA$qi&zx@yr4J+rIj6tw$Q%blZ~a zPJbXP1_DRbLv@#OgrkmGwMIT!frhIBCGkrORP2r&75_9sjeNKJN~zBA3nII zcVUHbJFYvn{QC!dTJ5O4Zb^m{4xRcy&nI>at6q0d`mU2xJ?>w( zulFn44%t!lia|T}EZnm8x4YkZ`qtfZhV9<)?E&-le)rJ6y&Jadd1&vB3RnHQtIf-2 zw_9=g>uuM(cl_ek&#l>X`K(>(rh6wfT)S~Z|H&l=^xN?L>f;-YZN2mTQ3WqQeADOS zT7Tl)dC~1v+pb-FN4vS#ZTKa9>e1C#&;9t>UneXWHTB2suTLLUuI*P+G+4B<@Z(Z_w?Emd)cEf&eY)!A zp;tD(woJR1Ul~wk^0{xm(fh6KPrf^D%%Xqr<*k~1H#L1DbMh5it|(^xqxGDVyKky(zx&AEE0!<0wq2=*n@sNb?ekC7 z@RltYGobYqy=E=i{?bvEd;Yj$ea{w8)~|Jb)20usUiIivV?Ma=`K4EtKjrI-=MVj4 zrr|jy# zulVvO>i<|{=MQx%SGe|*Z{}Y8`QbgwFX_9n^c)DUoZ_iUj13F|ruQ;>#PbF*IdCp5E-d1#A6<1IGa_BDw*;YfY+fi)E zzkb_!#O9r6eR}pe2d(VV=A0H+Hh91H4Y!V~*|JZMc^jscE&oKrt>yOBss7508&-LH z?p*%ult&tM@0%_>=YSsHkKO#z*!xnWPOdO<>o32Jf6<;d>0d`yKkbR$%hUf{x8TNC zi&uE|$hB7vd!h8JtCud@+xeh#3M(wSs?8H`cl^BF5AR>UuJgsmPb&5Jtd39AeE-m@ z<*)1d=}WiWa{bUlH+l1~TKCdzb+i3@&L00n_gW?1{`AV3&F=7K4msuPFK2arw#S_f zuG~6caL4O|vErdaXs*2ix@gVPnOcS5>)i%*D{7&WJ7Jr{DPc zz{+#`cmqD&UHr3+{m-3$>APJDepq^M^9vTAyyv(!+kPt1vd7O$D}Q&(_!TW?t-4~< z4}D&ofBJjh{xa}^d&XyKRNhv4Z0Bj^X8rq|S>p@mUc9$*uCF1JpRR>E;(@T z*K_x`Qy%PnX7yLEJY>|K`=4#KWx~`}-G4f%Z~s9Tv>CFm;V*lCF6AA4NQ-Ocj&6JZ z!ryK_d)@eF?p?cYW0Nf%PAfaJ%)M`QX!l!_MrGPw{Py%Wo`3iDl{1U_b$EG`b>&MYjxDTjJMm?yu2*NRM&723^wbxAgeU zTNe!7^>m9N1=}9ze{aI?a{l>0_AgrX@y}DAnX~eah$?uNsLO>>IfUw(Jp$fj5?@H)zh51wTz%Qu4kU zhgN;^lX1h&8nXDE=8wMg!s*xja&=Glw{?ThZ}icM?Om^Z|JozIxS`$rc{|(xa?O|j zZ2Ms4=5;Cz`>ODT9mmYuxS;jjSGiZcQ2nr)w~zkz)(7`|G5Gx_y031%^z6o`uKVcf zbp6$v=eF;@Yx#Fu$2{G3{HTF7rW9Lz^tStJoxS+|*QejJvDVEqYrpjMO~uBw>+@N+ zN%N=vd&9P=XWsP8(8{~^9(&%|yBkmb_JW`5uB$oi#%)aoJzw>TvQL(MZs~*XbsL_x zhOZmEr+cj*x}Eab@=G4YM8~3?9&X=dWEyE?hWybcHDoeAX@f$>HVJ z^=LPH;46zCe67^SEk;!6x@g$@t)^X6{h`(^KKkH>jko-?tNyxjHQL%WU%l$Pp%?zJ zsfByR(93?5pAx@bwru&@8w*}I^@vt0t1SQGtKoAOjGOS|jGJaOzxve?gFC-is^tq? zHof=S(uzMkw0_^iU#wYn z;K&zma#v2f`=l59eeqSr>BoNi_KMjfH*P<4^AZ1QaLr?HRw-B5uUM5c$_&4*!*Sp5 z+Sp~s&TD6MXg8?F)tQUF+-%)DXTj`?NA0_2;O?^)&%EZg2e+*+bJ><5XMZ~T#n-lv z9{J(e=A9-TF|Nt?!`}PhoI`)ye(2rPzn}5Z)w`EId{~K!@75hw>FoyX_U&5It4@uw zy^d&e+paS6AL{$_`s?0VdHQ3Q*DToAv@Ue&L4Q-(7rP;}Y(5*Khi|>B5a0 zc29rf$=f%~u|KX*xqOosZu|0IgMaGt-gk{V^}ea;L(g2&`}S=aYug>aJko6Npj(H0 z*sBt5*;E@S&2NB$J8<&cc^lsA^4!X= z|G9hLf#)4_!a2HoReKBf(hgmUidSn=)VF8IV2(RAO9n|lFru{nfD`Nlj0z1Xrw@uhPu`Ms@cy&>yeyq8r zlVT5*^5%N*+%0RX=cZiNR=iq0FXhRfvMKhq1Zyiu7090oQ>?%gYs;kJy;=X~Oe(|T z*m=8^$}q(E&vq)qr@;Co*O=a}Lz zL2jg-x2kh1yUDTj}nayX-;9I@^WCzHD1j$}@};F<6Rci3G2qy^8d zW!!!GN3XfMI~^|tf5h54*%bWQuQhbUsyTes)Rl211%H-fiJ$l$-f!OqE~g^iF&YeqsQONC(*fi?U|tB}kF{trl<6F0ED#uFy=$-u1%`;TwVxK}v2 zJDQAt?GRvo)rsJ%O?f=Y@Y{*ME!pEsd4-6y-c^t1K_c?2oc|+Y{@9Q9USY zMEnzvWPh3fP~A{DVX!}3Def^fiK-rPHG-1XdS7;_+aIpEVJ#YT?aZ!znYc*_sgvTj zcui9L$_PGO!KS#!yh+Lvzj4PS^NQrIetm1g7b^VPQri#u$lR~hB*lFsO;Y?!4j(a! zIBYZlTZNy*;LGs?@#vra!-r=*!-of3Kp>tGm)ZbQ4k@l=;a0>yErk!kP!1o$f+MaR zLtN(qOs??Y%h6~D!riA9zbr(s#IC8^t*ARJc&b{xu3h@~fk24!yY)+*Uc0DIzn*h1BfEvkYT`7o7`Rd#kJx7`_e8cjtE2o)V{DzDt+#`uxRkZ={#MKJg!9j{Ia3n4!deFAU_MHA3i zHZXxp#pv?r3K&c5gNRv$E`rMzIEuuLdiD?_3HY3$%@oa02w{q9d=fyYK09y28mbs^ zZVY0SPFpHQ{1P|;MpCJnc*LSG!lY!7q6V(dPN*0oabvAxgw`I!V>DpAMnXcF*T{xE z%p7Y{+rlw*a4DK((PEg`HZb-4Vn@ylUZe}s3>*b$U)JLAy-D+cju|1Y*;xn zUvf|r@X!QI5+i`Q^wXNyFiQOK^U1t zOT!DfXc1P4P(C6oAc&Gxh`H1x{u}Bdi=!2Un@%IRJoF$wsYL98nK?3)Ucs80gNhni zUwjcKp<{$384m)8f{Pblgbu{Oh|((=JhDlK%EcG9s9bz<2U~?0aIzgN;vZt51d1g{ zOt4K=e&(Q}h)JPDZc(cciG4kSC9w5%ewINVnOt}Wwga(R;Xw*f6Ay3!H&|kj%&2NJ zplhS$B73G)Cwm5w-L%_@y%Dn8k;>r_8u6(jLOfzn1(cluXQQ^rQ0l@$1|y$N)a-St zh>(;ZwbNJ)@u-O?xR_VO;n7SLi&7UBQq4RrBf-e|BTJ;_wJ7OpAr;A^L!w5`nOzFH z84*VkBqz8}Rp#ZPHBq4Vlbn}- z1!4?_A>}dB1Uw)oYGeXK`Ye_~TT2hm2~aMuW-%=$%P1}_>S$siRgpE>R65SWD--0? zG`7`vEKn3JG0uJ%k;(A4DDiG1t;&OP5{w-4uw@y=umxf`NEL8&s=JrR4Moi@Cm0lh zWJH)zUI!@WF)6bm&SMtRQ9M2=Y6APYER=q+@g_)qAQSOqkk%r_hzPC6jlqx1XF|-tMm7Ww0gIYJ0C#?V zi?KN=J?9|F%VW@@My7G+Xpy)}vm+9BWL@Z?YtbARW!I?dPH9932__z9mmGI4Mo8SH zfQiH%NiZHe7d3;xx<|PIRop2*po1k?agF0y!hT@+ljh8B+ngnJ&N#H36IN0k}^ zRMg1L>NEeGSkYctBrSti%*Cmh&+@WJPxFl=Zsb{aE6x?xk>)*YHI9ccavPsW@ zO?!3dkpj)g7(f3P(B1zFolKbgiR=$c#3{xg6X_pyR0$(sC8ZN*>OY50cu`Hts2RJV zghiEClzdUeIZ_fWxDe^As%I@d*fC1pT7EdCkzYygtD}lL0V{GL>ImHOmYSF@#jy2@ zI&?1T1NZRxYgrpS@^C|pS#i9~J$@AujWcmuPI8HooW zoW$`%E~_S-3NWLZ1_-QCTw^dpdnwR1qD%-^HJU6Q{u_zDSVq4P1(VI9_(a@g`@Q5B z@-%{g5Ezny?Fiyu2iJ3hAkLkrl#+i<+GjSRxFYn`u@qo;lXC{cfC&v<#0- zi<)6)kPMP9u6HTVU%g;cS?xvv@aiZp==Ki?=`j|lhP+QF@-Mj{2g_`cag&6h(?V} z?IUrnwq#_fC8I3i;aX7_jLWi3OowNXd*7~>KCn%Qb(Io8)ba`v*Ulim;nlpNFvROBYJkOXPTAB|l84L&4l8PC z>BRGOGSl3m_!!_6w6uPICmsqmvv_f{sL2NC#E-6V=tP-US!CnrWzqsHu)jl7=2aHy zBx9n%{tit!Zn8*>^Z52Cf>Ycfy@4_%#S(c2e-5`GxxraBa`M7h@Lf5awYkEELW-Pq zg4;G-fHV!S@fI~eU}5rZ#hg?k4@fp;QfBecCP#%d>_iniArW&W>4MvqI@iL+`+Z!Q z86ns$-f$gi1av@laOmu@cXPy zIv`z5S0vK)kM@f+ctcV}3NT@qWQf0C{91p;#dpj;2Zz#xS)}Coa8a|9%O>&>#Fi2% z8F82G_u5h@T-oAv45Q|arI1LUh@li6QYj*%ki&`^nj#rDjN+2)cj9#0LI-kV&Jw}t zGX2B~Nk1vTykt7@2i7F6W^l~cF%GHKkz(YD7;%;@Z^T=ooMYT&``tJl8A8VF2~1Uy zV-kncDSeAC28^NmfuZxaqtU9~q0Z>oQ)|pt$sKiC1NY>lZ! z{x(OWIGHSvz%G;XrVIo> zyV?&D)y+E8Lq?Ve_nU7%q8{X3IqE^3H(6|Kv~i-M=(gc9QB|)Z}=$2QHt7u1Z91Hc7P+1s!WzVWJM(pA&jU~1U8n9 zi9dP@x)qZ7Q-D!Q=9*PfVRFiZ+#qBm6gG>DxhjO<5anHi8z7C3mC167EWb>wQE-q! zMFDa$Ar!@VSZ2i^nlir&&|%t`7_+}aQ@U`o665@1bPfv6OQfEC4Nx_}djYw)NASCjDN<{5mYX~PK#6=dkdAsgk+S;-QIEG19I zAT=m+17!kR$ufs5k(LreyJaY~f--gmtSGAIY;Of&F<~-lOgs+3Z?=UxAnsjI4!o5t zGbjPwDGh{Db_A>_8iaH#9g2dx5N;ue z)K+rCEMxi*K?T0if>@`q4WX$KYxzR0N`RRg`!dNqmBf#GN=p6M6%##?jDP zsndY}H@~6sJ`qdgz2pr|QxX29!Vf1JQ?2r{7`S^4vG#7-(t3xMGp0Z@=Y)h;G zJVfLlr;*WWTj)5NES1UfD_a|_A(pqAUj)(!WQm}rE@pe5rEHp@fc!Fap{|x80YP06 zj(QX`=$l*QUFn+}35{e4RZ#Vch1SH_VeCTJgmsLAD(OW;g3%_q895+~sapi?xFr&# zosCkTp=L&+4eA#wH*zm^t7fI~#xeOHi1Uf4dl$qhjjA|6l4CNQq*OEGL>P_`gru-I z%IrW_n>Mf49wq1Zm*A9`M6L;&hXno+LlehU zh_pupPj(9xpWu|#!2&N~mRJ0tDXD`>vDn6Bm;GI9;<#v`L$aMPQ7b;yW@E>tqz*o3 z@!_I4QB&ALZ#988FGX04EwcXLcYcQiUlHpgqzdaNTn%EsvEPNO!4_G3@WLmr0RUeK zPdqmel$JIsj$s?K$Iyb>QCVYtIYs|gAUHAvJF6SWn1vA-Il_!XBU55JfU`+V`{yJT zUAQ3v;Z8ap{9Avx^b<27bQC&7*v4d|0PgZuky73Wcfze{p-M3Vqhx$4-gW@K;wu1O z@&s-N#z`a|ZT}fxBZibZLiJ|sWC>C6N2at9Dux1a5J}X3{E;bTgld-9#+04_Kjn}A zngbE>6Qzq#bqhOLs#AQN$#X248>KO9^~3@9cz?`#Q06bHcLMI+KW1d=8puKgPXvV% zZg74gf(MtS0uhN7@VmG&7i$a(MBNAUGYEPo4neI|(+O?~=wxG-G6b zfM5~sFtn?v>Z2B_0b(0NwQ^jLH$ZF=sHJlOQsu@>ngCLH_|aTIrAu3=w1jO;&LY-N zDpZk6q@#vBH_7sDY~@u118S5OM5RLntSHv>*GnWNaqVpL`4VbSjh?W%zdH&tUM~f{ z0Vzsdxybcs%=gmQOVBhWW=)`m&aFJ4Fac_L3&MaJXf1=Zq3?+BE(KX_f5L?%lolK0#ppUkxIjacym?N7;{SkOdEG)$-6R?V{h{{`~wJ1B`I1c zja^VMYG`7=aAO_z3m3ESmGVv`s-y%q`aZ%36=O;WrnZt961|UX@{SH|5*wt5VUw^s zI#U<&#ug7`-i@}|4xuxr@Ia&!6Lp~zd2koXcZMoq*v9M*om)h5ho%KltxDUN?GoSy zZW+bc_U!7!u#R-z2PTY}8yw0$N2NS$p3szRr_l*9Ha^T6L+}H%iz~ON{0R$H->{AO zQN-Dhj=)c&sB4?2#6;05fKyz9k44~{_<>SV9y*O1lYEF>z-6Sq3(!&9oQ~458$Xst zJ)43#*DM&w+YMCq!A_Q;W5=GIO9>oQ_QB@474aD%{?KN@;F{HhOSu|QeFNK=mw-80 z1R{2t89_gloCt_Zb$h{ ziye~Xf^P|p=~UV?Fp^vKovDVG*~T1*z$(UdXS!w!!8MC4*X*fq&4R!qQ+<&*@yy;= z&%CibdSgi$?%y90Vp{D%&qVD(Pekouo(HY?RT#7tsP>?jSnWZtu-b!OV6_LmzG@Hh z_5hWGv3W*R)P6;5u0GKyc8F&5rwdWkgfZo(p;T~D$v*yDXPi+&?gJ3szzD(Z4cSEM z(a)GXgYdTF&ki_D9~${9l06obU}#D;q2ecYvdkVkD!m4;38k7)@e=_niqUeaNujlv zFs+)T8@+&vPIq&d}XMHpiFR~;b^V$m@$!1 zS4{$U9WHWPB+FE?MaD{qfq}M-S#Hs#S1~V`B^P_C%OLDY=QFC>BJhe@Pqc-R=n~00 zK{BUNR+eqdPs)id$%^FNn@V8jd^X`tiY~-fJUJ#d-nK|gin06_DY}q-nJjN9!O*1W zLS9(ot3qhC6AVp>E>y0-PL?v2U})HHK^}xWAytGJ^JOsBB10(7T7vqz#$2hq zz|Ql(E%J%-OrB3J@h$!+fsIQ42tr0Nx;#sW6g-Z#*tBql5E*3fl_3rCc_p!y?C;5t zhBS`aGUjCx0zBl@kGByM3Q>p!RyFEuf#i!>^fIq_es%n;6C}Wy_uACOEXYET^1;Bm`jLPRvn0yS295{A9X^!oM<3Q?4lwIcCvIr zq+~_1S5V5NQ5dal%%1a=Y*gMHVqfN$HBpZgyvW8%_H>pVe|DtcMf%;CMunwA{GrYK z>j3`-7Ys5=RgIJ^9WFj$NT(zYA2Q}h?I&SKrzAec8ef+R3?1chS9hRGnS0m_HDPqTikzOhEuuL+wGr;V|K5uhXpNyybKqZDqLd>e_&PR${Fqa5I-_nc(Y<-{$7J13vni*fy}O^ z5N9HSVpeP+&O{YTh%*s&F)Owh&P2~lUC&Hi&ulh$X6kxovFn+sn>8C{S+m)YHB&ch zc9CW&vjLSAv6H2pBC|o{@dS5n8o_srmF1<`;D}tC7$>5vnIE!**#P(BibQdEDo>QC zL=q*z>+;vi_ZgnSFex}1SNkftV&?1s@3-uty7V1Ij1k{5R0jLM55l|0O zA)p@QenLIW+lk%m=(Yg@Lf@0%E@raQPu5{5X2P;qcGGmtq@|tDcrIq*(#~fDg|2mj zyO3fhvtynreQ2aSlI7PY7~0IgESyf89SUA@lI6KO;w{e@Du{X3 z^r4Z1!PZ2dGqm z4G>%_JBt)|vN{h=RGkO^bp`H327^l*8HH*IP*Gf%Q%}lLIP%%hoK2%#mP#eY&bgch z#0=2$E`rdJGvHCCj#B?b@HVI?QA7j2{E8O7bbVSBB_#kw4ydW8kisS?ywWi~(}^~; zBT5&<*mkt!QyGjFd}7c=M5#h5V^Gw+6Yc~rX4 z#f{NGbci1AVx}&gIE|s0d6#~25<@Xlmrk6Eu+gs~Pyn4{tkb2% zuStH;c}&e7cal{Sa-u2;;Z-ynHFSs@hGG_7bch>8A zMh%@Wi=mhm10CXqp_tV!9pZ+enAI*F;)bD^#a4)yM^$xnh#MLx#LI)An5i4$V|lE5EL_YL%cjH6z1hIOt-)+84AqQEig-l0+PD^^Lc(qnLb<8 zgTx2*AkjfRNNi9K5*gIPj0?#q2nJ>Bpcr)_-lNnS(&@;^qf5M{QGr*-SO?8F@}f#~ zA)H8|jCufHYdWwH>p;fAv9Wey&yC8Rhv;==M44_P5Wy1+ZRXnyFs@D*Wc%_m4)F;i zN_o+iIa(5`fGngNsDv@rRm+)p%5pfO?nHh|D<+<_%fZAGQ=yQ@*0(8`R9+JK&-6e@ zzu<51!pS0Mjx2KK2+HWxMkg#|{X^Zv6S(Ve(a9=V(KIKjXc}Hcv(%!4_AnH)+@d{B z7>ZdA&>klQh4wh%F6NCzdz>&7vr3>nP8f<=DbOA#423iwa2GRm>9`^Uh4wh%E@tY| z9w!XNOkLXJgrS(JOM9F!6jJG;KmlWUJfs7V(hVt-hu+L|l9kDGc*VJpfNG2ya79q` zKu9@~;w=6!N`dI5-o$jZ<1mDqBfw0YM*&7Ob9l+QSU3^Z0))k*p-p;0tK;U-)$1Va z9>Gm!3it`DR3;f(CgzINmkBFdlL1uU)-Hf}u{~m$kO2|5Hl3r?63bno`G*~hjafoa zFJD3Is7h;okshCY;?E8wk49_s%9&%VuZB5S6f%opQ!y{EJ~ZMmV_7^|x>;QfF<*V1 zGiaYDt5>JOH>|c<22%wmA-Wf>HoFUrT5V~``Q;^;B_|8YIU?jD<;a3^4#!j{M;b}= z>;!Ssrp6F?5hnLTaf{gdA0zXg@gw2ui4`5^kbBE`bDWz@R*ibaM1afXqBIbxN3T(yBRC>knx892FII$UJ< z8S9e;7&DJYG-I+XeQjt|7|G!^#iD4Zo6&r>8kae!8jO>yzL+D5JYiPz4Noy%KKnTD zS0a0miVMLYUB##eskTrL%FM&D_{oa9A+04!UFj=_n9*F-OVs0Zc+sG{JBvo3q9fI7+;acn~0Y~ibEAjW1)AB9AjlAUt=jNA*QSEBJ?7aV=R{v zz$qfn5UnG5$%J}PPII{ZR>e6l%n~I!a=u{&@0`HOD-u!A4Kz-dl$Zh`w9xB-f@>m2 z7T5khWG3)MnYffA%i4)W10S&{b(3=FGBXgssiJHwU8wtp1@78j#v#$kYE(I*Bu@-! z$uh_Fp^+FdmhI9NfT#o%gqy}u`gk&aTl{V%#iXB+3?9q zfFUg)3M^@4#!}B%QwmE6QLHHrnJIyb-mY+rMZ5@i6VsO-5W-R99Ur+MdG;w4& z{Tv)#Zp!6|9_e#=&5kIs6QH#;kg4++8k(w*3wMkPNnB%<95l2WRVxP%O$i88T1&u+ z!rIqv%4&%D>e>xGujeK#v7^AxX%W%_q;GyO-ewDr-TVYMAKWA=tKt}IBgun@DB;F; zog<1Or35oIfv-4-2x1TvLnj?$@j6}q2&(CbsAo^tST`rYUUBUVIv5+T1bOj@yO4e{ z+=cXu;Vx#QhrSv^Q0QQ6+{LV+SjxS&B$D^ z#@nn@S&;x8Z`D2F8jC#ySTVx6if2dZYE*a2HI`w)Lo4+)qU8}QYM^7Sx<*_MD~6C& zS3}HKTW7dARAiK!tcHsU-++{P$I)dN(5jHPVlGmB#!4{$F@v1Da?FrIE9#hmUiK64 zq83GncURO8B*+bEbf6RfBL@!N14`V3a3K;0@j{}v z&0StaoV&{r`C3VjuWyO=#%^i>Ro zVzzkbN*Nf6`A|ex%D_;}3Z1TGilLYlIvwVXpwN{}aThc1(v?gx6f^JA$@~}!&AYPp zLU=T-aC=B6D$P(T3>ss#2Wi2mo&_O3qNEs7r^tfg^|dt3mPXDo*Idm(I6z%dIwj=L ztKvkjklKT?Ur^07m)9wbbVHmG>OdjR2-L+)UAq1so{O1x>H2>d zikZ6PQ5ikTDt2~kL4eE(QFtkMNJ`ttY~dv}qTu_E5vA@&w6dXd*)eG-?=yPz&NY_J z2?)R_$B4=y<@7}0=?C!)xqxt3i) zat#qwbImmtEXcWUMC~5THhB}se-b6i1VUwRTx0Db?PgO<9|WVb`Qz7CEnzmEJqojCi2OdOI zQjDV>q>e;ANCO1*AZ0k}K^F$ZT}b-_cQNmM+Wmu}m|2l_|6nL)($el948=@b+Wmu| z(C#1H#Y|l~SO-HPtvlSsOkFw_1w%1Ymv;YPC}!%??jHn&cK_fmX6mvuZB6tRAQOe} z>L?+Dga9&8xcnf53_8i0OGwCT8d0SohKWKutT1VSA9TFYfj3!I4LAPmNR5K*qhzHZ z+^A9z7;E5P4QuoUsel!=+P)2<;Oo-)?GQ4p+8{h(Jqj}7qXhIc>k8VzhDk#?*w9Oa zt}#JB=c!O=lT(C>o|i|OC_RrV-w0Ynkxn{$L_HI}LtIhIB<0FlE0RgzcOK>fU;0^e z9MQ@FMh>i*a{_mb%TQ55muK2X!Nt4~RTluQB3;iDcQFe^x}GP7VpchHJx>gUbSU61 zW|c#`YcLeE%Awsg2ny}4!ClPMrQJ0cikZ5!y9PrsQ z+FgUYn2k}|U4x;R%_Z7hgQ1vtmv+}+C}!TJAC^E+Xm^btG9DfmgL=@+EyMUZ44V3C z56TUIijBEk+Qk-*$l#(Yn51#8fVu2}G`K|VkPw4Q#fjz?wFhPMph{vcFWwOwYd7}X zNRtN{ddaGcxT54l461Q_x7W8D?s*ix`{MX8G*WXKypkeQbM1E@*wMRlcV zL8tbh;$k4p0Zrc&h?+CC6v_~$R>n_sVYU{Z1GOf|3C96OQBK-gN?{*YR3u3W9)jmS^{|;E^-oirkg@+Yl+G!G1!|9jKT8KEDl3FBZyzj z;w8j0f}qgK2z8-VJ?diK03n_c6-tO_1a*m_Ys}Q817pw{(Sb3z3vEcD>6)n<;u%3u z%+w9>jHpmTJR_)!nYwfVJ4BZdKLZ-b%)4}83?9f#UHV~648=@cIwJ!?p~68RISol~ zcEF92WJuy51BMGL!efLKwz-~%G+=l=j*uWfjFg!lsB&eHh@s+btP_OrcH_^Euo)qt z0}ajI%Z*KP*RMT8rJiEK0RIN{3*@e#p-pvZd?QA|*A^oGy&NWs^%9%0L-18oz3{UR`MvgLLQ2jc=Tu}@m%^1I;I&gw3%2%aa z&c|TJ$l5oqsNMv>lP)7jCsf(NSo}yagC^Q zKEySGx|n55h-(Bvp#5IDTm`@BLt`QYVh-(COG22Ta zt`P*qOx+OI2!dkP=pn8V1jWp|A+8Y>N(|Qs{Tf4t=ODu-3wNL3F60itUC1@UUC14P zyO3*yyO25ncOlmZcOi8EGKP{Bl|#mmsIR8UMO4$%<=J0Ra?y|Q;g!5XBqBzNakz==U; z+O-mZ6NApQa|Oepy(@t}G3ZSDR|0TisCxQ|fB>8r{7gR*5P%bdpXpeG036cf#eL|j z>_DFwbfzz}18`!{nJQck8J|e??Z4kgR9g(nyELx9VlF5s{XU{*UmS%_CrkvW13NG{ zGZ$4eb&W;x0+Lv!>zXAoyW7{tM#fIE3UaQ0D;i^LkQn;d$k^e)qExEy`%zZJ6{Y-i z$jI19Rvyq*;2Vq``Y?}IoIcG5`oP}Pa6nZX#iXLBBMlB-nN%7aB8@2}I0_68v0U)w z28T8vDxmBdOPuPy7eP23E;2NXMFewxEUJE>YPHF8XQb7JN@}{sDuQ~9GhIY5?INR$ zJ3FEl2X9%WM4N%ZXeUm9d144$I>IFYCx&999XSCwF%%o^$_c=Uq1b3=4u(T}a{_&0 z7;3aXCjcjgp+%D_LaS@kg+>Z>p?L>&p;1CzXwE@hXv-6Iq4@@Nq3ur8 zh2|R6g$~unD?r~!2l~VyaL8E~&nE_n>09Z*d}0upj@S>ti9u#|jM5}MD$ng23rHfV zh`d$8dmMzHM%Cs#9*+?vb)~n;tGec&alpqgZUz;QvUK#s6t zID5%zIeNS51^0G|8ipMFFy@qL ziNel3W1U`vxfg$a>_Dkg9hoA$USw>ldr^gL@s3~zRefyLM<#$pjqMv)3dSB?;L=5v z_dR1Nz5S+Wj6+h$(He1u2ZowP9aP5L=7bppUvUvA)`GS3dr1vxlvXDIMh>hQd4f4~xT>GdOIBOd;{{SfMSxm( z1$G#5*XP%U zm~R91i9z2?3<8JwHqclxNF3(dKyYFZIn1|#;KU$v$Q3bS(J)VeI+hiK&U8tF3?3^6 zC)1(#0XQ*unSMP!04D}FhaG7UePZx)*pWtsLzkw+bEb+DKpwN|V=&fuMk)}QQ=*D( zP@<+$RawtiU)VRNcyZ-evXssm2w+YsP9*D%rGpXXUV`}n#|5bZ+2ShIlhxYwqH661 z@TNR6=+OlMEJ}C!=9Gf5M;G>lOiP3np(F9#Eof# zk*bAw$+gposa9O@jD=zYJ4~8~WE3GO%7!>Jo$PXo-)Y(25mx zp&b^e3$0dBmk_@Y?h}K)A$}nYCkBNxF-RQZ7s6x3AaaOb2*Zg%<`BOSf)nBw!hK@U zIm9o7;l!YGh+hc9iNVVuejyAe1~-TJg)p2L{2byJLU2O-Lby*Hff0kwAy?gatQd4= zSKYdJMQ3AU0cIp#k)KM`VGhDiV*|-p*V;GTqAFTvFy=8hgLy52GlR5oRbd%x6eG;N z1oNW|X7osgXROQ|7+YQ;nUxw@1S5+KEMvuL-*i(j78BO5c+tBRUb2F$2`C!kazI=W z68?buz~0lj0vWc(%F;PMl|r7J<*C|kJh;kWyJeBx$RWnC-9+JY5!1!@7uas1usD3B z?IucI!|$~1hF+BMjAie2+f87v6H_fC=JAr7Q3wUtJhtr?;upe;9Ya8d_=PZ>7~&?x zFNER5P(&eqAp|GHFNFKVP(&eqAq*#mVMUj0z#==uFNDX6LFW*^5QYC zsa--ytab^J7;2Xg0;^p@_*m@{LSD5CX+)r-zOe=}lCH>fqiZ3j(My~juihOcy?xV7 zRB{fvU%-n^$7|p|z!92WL9d2*$;yy>3FZf^rjL!xMq{yUM0HP8wT?v_$~Y-N9zSEH za^G}QF!tzr+ECd+HHMRwc1=Rj7~75=&2_3Hqm?5R$&13RC5np+(@0dGPYG@T6G;{r z_e9ZdeL!Ton2Y~wS9gNw8r&>8ZyAee2H1z2Z?V`XhGYu!U8r&=6GJwI`7TsAVZIC0 zCywxsA^k(j9w4z2=DR@iiJ<_(d>05#3>6UOyFhS4Oc}LLnC}Ahi9zR(FnvVBkO&dn zC&q@SD@$V2AtX#6j}?Q?q$dD9B;qA2`i~T;D7G8i^J(<(t7ohr?JH7It~btZr|V&2 z3X;Bn0wj%$b)K;_ia$SgVh>WqMbB<{yasWUNDquH&(pLIMCEp}ASQY-!!uUa_7$mu zv6! }M}c)X%=0<74rN!XIPA@GmG(Sv^`% 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 + { + allowance(owner, spender) == value && getNonce(owner) == nonceBefore + 1 + } + + @Note: + Written by https://github.com/parth-15 + + @Link: +*/ +rule permitIntegrity() { + env e; + address owner; + address spender; + uint256 value; + uint256 deadline; + uint8 v; + bytes32 r; + bytes32 s; + + uint256 allowanceBefore = allowance(owner, spender); + mathint nonceBefore = getNonce(owner); + + //checking this because function is using unchecked math and such a high nonce is unrealistic + require nonceBefore < max_uint; + + permit(e, owner, spender, value, deadline, v, r, s); + + uint256 allowanceAfter = allowance(owner, spender); + mathint nonceAfter = getNonce(owner); + + assert allowanceAfter == value, "permit increases allowance of owner to spender on success"; + assert nonceAfter == nonceBefore + 1, "successful call to permit function increases nonce of owner by 1"; +} + + +/* + @Rule + + @Description: + Address 0 has no voting or proposition power + + @Formula: + { + getPowerCurrent(0, VOTING_POWER) == 0 && getPowerCurrent(0, PROPOSITION_POWER) == && balanceOf(0) == 0 + } + + @Note: + Written by https://github.com/JayP11 + + @Link: +*/ +invariant addressZeroNoPower() + getPowerCurrent(0, VOTING_POWER()) == 0 && getPowerCurrent(0, PROPOSITION_POWER()) == 0 && balanceOf(0) == 0 + + +/* + @Rule + + @Description: + Verify that `metaDelegateByType` can only be called with a signed request. + + @Formula: + { + ecrecover(v,r,s) != delegator + } + < + metaDelegateByType@withrevert(delegator, delegatee, delegationType, deadline, v, r, s) + > + { + lastReverted == true + } + + @Note: + Written by https://github.com/kustosz + + @Link: +*/ +rule metaDelegateByTypeOnlyCallableWithProperlySignedArguments(env e, address delegator, address delegatee, uint8 delegationType, uint256 deadline, uint8 v, bytes32 r, bytes32 s) { + require ecrecoverWrapper(computeMetaDelegateByTypeHash(delegator, delegatee, delegationType, deadline, _nonces(delegator)), v, r, s) != delegator; + metaDelegateByType@withrevert(e, delegator, delegatee, delegationType, deadline, v, r, s); + assert lastReverted; +} + + /* + @Rule + + @Description: + Verify that it's impossible to use the same arguments to call `metaDalegate` twice. + + @Formula: + { + hash1 = computeMetaDelegateHash(delegator, delegatee, deadline, nonce) + hash2 = computeMetaDelegateHash(delegator, delegatee, deadline, nonce + 1) + ecrecover(hash1, v, r, s) == delegator + } + < + metaDelegate(e1, delegator, delegatee, v, r, s) + metaDelegate@withrevert(e2, delegator, delegatee, delegationType, deadline, v, r, s) + > + { + lastReverted == true + } + + @Note: + Written by https://github.com/kustosz + + @Link: +*/ +rule metaDelegateNonRepeatable(env e1, env e2, address delegator, address delegatee, uint256 deadline, uint8 v, bytes32 r, bytes32 s) { + uint256 nonce = _nonces(delegator); + bytes32 hash1 = computeMetaDelegateHash(delegator, delegatee, deadline, nonce); + bytes32 hash2 = computeMetaDelegateHash(delegator, delegatee, deadline, nonce+1); + // assume no hash collisions + require hash1 != hash2; + // assume first call is properly signed + require ecrecoverWrapper(hash1, v, r, s) == delegator; + // assume ecrecover is sane: cannot sign two different messages with the same (v,r,s) + require ecrecoverWrapper(hash2, v, r, s) != ecrecoverWrapper(hash1, v, r, s); + metaDelegate(e1, delegator, delegatee, deadline, v, r, s); + metaDelegate@withrevert(e2, delegator, delegatee, deadline, v, r, s); + assert lastReverted; +} + + +/* + @Rule + + @Description: + Power of the previous delegate is removed when the delegatee delegates to another delegate + + @Formula: + { + _votingBalance = getDelegatedVotingBalance(alice) + } + < + delegateByType(alice, VOTING_POWER) + delegateByType(bob, VOTING_POWER) + > + { + alice != bob => getDelegatedVotingBalance(alice) == _votingBalance + } + + @Note: + Written by https://github.com/priyankabhanderi + + @Link: +*/ +rule delegatingToAnotherUserRemovesPowerFromOldDelegatee(env e, address alice, address bob) { + + require e.msg.sender != ZERO_ADDRESS(); + require e.msg.sender != alice && e.msg.sender != bob; + require alice != ZERO_ADDRESS() && bob != ZERO_ADDRESS(); + + require getVotingDelegate(e.msg.sender) != alice; + + uint72 _votingBalance = getDelegatedVotingBalance(alice); + + delegateByType(e, alice, VOTING_POWER()); + + assert getVotingDelegate(e.msg.sender) == alice; + + delegateByType(e, bob, VOTING_POWER()); + + assert getVotingDelegate(e.msg.sender) == bob; + uint72 votingBalance_ = getDelegatedVotingBalance(alice); + assert alice != bob => votingBalance_ == _votingBalance; +} + +/* + @Rule + + @Description: + Voting and proposition power change only as a result of specific functions + + @Formula: + { + powerBefore = getPowerCurrent(alice, type) + } + < + f(e, args) + > + { + powerAfter = getPowerCurrent(alice, type) + powerAfter != powerBefore => + f.selector == delegate(address).selector || + f.selector == delegateByType(address, uint8).selector || + f.selector == metaDelegate(address, address, uint256, uint8, bytes32, bytes32).selector || + f.selector == metaDelegateByType(address, address, uint8, uint256, uint8, bytes32, bytes32).selector || + f.selector == transfer(address, uint256).selector || + f.selector == transferFrom(address, address, uint256).selector + } + + @Note: + Written by https://github.com/top-sekret + + @Link: +*/ + +rule powerChanges(address alice, method f) { + env e; + calldataarg args; + + uint8 type; + require type <= 1; + uint256 powerBefore = getPowerCurrent(alice, type); + + f(e, args); + + uint256 powerAfter = getPowerCurrent(alice, type); + + assert powerBefore != powerAfter => + f.selector == delegate(address).selector || + f.selector == delegateByType(address, uint8).selector || + f.selector == metaDelegate(address, address, uint256, uint8, bytes32, bytes32).selector || + f.selector == metaDelegateByType(address, address, uint8, uint256, uint8, bytes32, bytes32).selector || + f.selector == transfer(address, uint256).selector || + f.selector == transferFrom(address, address, uint256).selector; +} + + + +/* + @Rule + + @Description: + Changing a delegate of one type doesn't influence the delegate of the other type + + @Formula: + { + delegateBefore = type == 1 ? getPropositionDelegate(e.msg.sender) : getVotingDelegate(e.msg.sender) + } + < + delegateByType(e, delegatee, 1 - type) + > + { + delegateBefore = type == 1 ? getPropositionDelegate(e.msg.sender) : getVotingDelegate(e.msg.sender) + delegateBefore == delegateAfter + } + + @Note: + Written by https://github.com/top-sekret + + @Link: +*/ +rule delegateIndependence(method f) { + env e; + + uint8 type; + require type <= 1; + + address delegateBefore = type == 1 ? getPropositionDelegate(e.msg.sender) : getVotingDelegate(e.msg.sender); + + delegateByType(e, _, 1 - type); + + address delegateAfter = type == 1 ? getPropositionDelegate(e.msg.sender) : getVotingDelegate(e.msg.sender); + + assert delegateBefore == delegateAfter; +} + +/* + @Rule + + @Description: + Verifying voting power increases/decreases while not being a voting delegatee yourself + + @Formula: + { + votingPowerBefore = getPowerCurrent(a, VOTING_POWER) + balanceBefore = balanceOf(a) + isVotingDelegatorBefore = getDelegatingVoting(a) + isVotingDelegateeBefore = getDelegatedVotingBalance(a) != 0 + } + < + f(e, args) + > + { + votingPowerAfter = getPowerCurrent(a, VOTING_POWER() + balanceAfter = getBalance(a) + isVotingDelegatorAfter = getDelegatingVoting(a); + isVotingDelegateeAfter = getDelegatedVotingBalance(a) != 0 + + votingPowerBefore < votingPowerAfter <=> + (!isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore < balanceAfter)) || + (isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore != 0)) + && + votingPowerBefore > votingPowerAfter <=> + (!isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore > balanceAfter)) || + (!isVotingDelegatorBefore && isVotingDelegatorAfter && (balanceBefore != 0)) + } + + @Note: + Written by https://github.com/Zarfsec + + @Link: +*/ +rule votingPowerChangesWhileNotBeingADelegatee(address a) { + require a != 0; + + uint256 votingPowerBefore = getPowerCurrent(a, VOTING_POWER()); + uint256 balanceBefore = getBalance(a); + bool isVotingDelegatorBefore = getDelegatingVoting(a); + bool isVotingDelegateeBefore = getDelegatedVotingBalance(a) != 0; + + method f; + env e; + calldataarg args; + f(e, args); + + uint256 votingPowerAfter = getPowerCurrent(a, VOTING_POWER()); + uint256 balanceAfter = getBalance(a); + bool isVotingDelegatorAfter = getDelegatingVoting(a); + bool isVotingDelegateeAfter = getDelegatedVotingBalance(a) != 0; + + require !isVotingDelegateeBefore && !isVotingDelegateeAfter; + + /* + If you're not a delegatee, your voting power only increases when + 1. You're not delegating and your balance increases + 2. You're delegating and stop delegating and your balanceBefore != 0 + */ + assert votingPowerBefore < votingPowerAfter <=> + (!isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore < balanceAfter)) || + (isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore != 0)); + + /* + If you're not a delegatee, your voting power only decreases when + 1. You're not delegating and your balance decreases + 2. You're not delegating and start delegating and your balanceBefore != 0 + */ + assert votingPowerBefore > votingPowerAfter <=> + (!isVotingDelegatorBefore && !isVotingDelegatorAfter && (balanceBefore > balanceAfter)) || + (!isVotingDelegatorBefore && isVotingDelegatorAfter && (balanceBefore != 0)); +} + +/* + @Rule + + @Description: + Verifying proposition power increases/decreases while not being a proposition delegatee yourself + + @Formula: + { + propositionPowerBefore = getPowerCurrent(a, PROPOSITION_POWER) + balanceBefore = balanceOf(a) + isPropositionDelegatorBefore = getDelegatingProposition(a) + isPropositionDelegateeBefore = getDelegatedPropositionBalance(a) != 0 + } + < + f(e, args) + > + { + propositionPowerAfter = getPowerCurrent(a, PROPOSITION_POWER() + balanceAfter = getBalance(a) + isPropositionDelegatorAfter = getDelegatingProposition(a); + isPropositionDelegateeAfter = getDelegatedPropositionBalance(a) != 0 + + propositionPowerBefore < propositionPowerAfter <=> + (!isPropositionDelegatorBefore && !isPropositionDelegatorAfter && (balanceBefore < balanceAfter)) || + (isPropositionDelegatorBefore && !isPropositionDelegatorAfter && (balanceBefore != 0)) + && + propositionPowerBefore > propositionPowerAfter <=> + (!isPropositionDelegatorBefore && !isPropositionDelegatorAfter && (balanceBefore > balanceAfter)) || + (!isPropositionDelegatorBefore && isPropositionDelegatorAfter && (balanceBefore != 0)) + } + + @Note: + Written by https://github.com/Zarfsec + + @Link: +*/ +rule propositionPowerChangesWhileNotBeingADelegatee(address a) { + require a != 0; + + uint256 propositionPowerBefore = getPowerCurrent(a, PROPOSITION_POWER()); + uint256 balanceBefore = getBalance(a); + bool isPropositionDelegatorBefore = getDelegatingProposition(a); + bool isPropositionDelegateeBefore = getDelegatedPropositionBalance(a) != 0; + + method f; + env e; + calldataarg args; + f(e, args); + + uint256 propositionPowerAfter = getPowerCurrent(a, PROPOSITION_POWER()); + uint256 balanceAfter = getBalance(a); + bool isPropositionDelegatorAfter = getDelegatingProposition(a); + bool isPropositionDelegateeAfter = getDelegatedPropositionBalance(a) != 0; + + require !isPropositionDelegateeBefore && !isPropositionDelegateeAfter; + + /* + If you're not a delegatee, your proposition power only increases when + 1. You're not delegating and your balance increases + 2. You're delegating and stop delegating and your balanceBefore != 0 + */ + assert propositionPowerBefore < propositionPowerAfter <=> + (!isPropositionDelegatorBefore && !isPropositionDelegatorAfter && (balanceBefore < balanceAfter)) || + (isPropositionDelegatorBefore && !isPropositionDelegatorAfter && (balanceBefore != 0)); + + /* + If you're not a delegatee, your proposition power only decreases when + 1. You're not delegating and your balance decreases + 2. You're not delegating and start delegating and your balanceBefore != 0 + */ + assert propositionPowerBefore > propositionPowerAfter <=> + (!isPropositionDelegatorBefore && !isPropositionDelegatorBefore && (balanceBefore > balanceAfter)) || + (!isPropositionDelegatorBefore && isPropositionDelegatorAfter && (balanceBefore != 0)); +} + +/* + @Rule + + @Description: + Allowance only changes as a result of specific subset of functions + + @Formula: + { + allowanceBefore = allowance(owner, spender) + } + < + f(e, args) + > + { + allowance(owner, spender) != allowanceBefore =>f.selector==approve(address,uint256).selector + || f.selector==increaseAllowance(address,uint256).selector + || f.selector==decreaseAllowance(address,uint256).selector + || f.selector==transferFrom(address,address,uint256).selector + || f.selector==permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector + + } + + @Note: + Written by https://github.com/oracleorb + + @Link: +*/ +rule allowanceStateChange(env e){ + address owner; + address user; + method f; + calldataarg args; + + uint256 allowanceBefore=allowance(owner,user); + f(e, args); + uint256 allowanceAfter=allowance(owner,user); + + assert allowanceBefore!=allowanceAfter => f.selector==approve(address,uint256).selector + || f.selector==increaseAllowance(address,uint256).selector + || f.selector==decreaseAllowance(address,uint256).selector + || f.selector==transferFrom(address,address,uint256).selector + || f.selector==permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector; +} diff --git a/certora/specs/delegate.spec b/certora/specs/delegate.spec new file mode 100644 index 0000000..e5283c6 --- /dev/null +++ b/certora/specs/delegate.spec @@ -0,0 +1,817 @@ + +/* + This is a specification file for the verification of delegation + features of AaveTokenV3.sol smart contract using the Certora prover. + For more information, visit: https://www.certora.com/ + + This file is run with scripts/verifyDelegate.sh + On AaveTokenV3Harness.sol + + Sanity check results: https://prover.certora.com/output/67509/021f59de6995d82ecf18/?anonymousKey=84f18dc61532a37fabfd59655fe7fe43989f1a8e + +*/ + +import "base.spec" + + +/* + @Rule + + @Description: + If an account is not receiving delegation of power (one type) from anybody, + and that account is not delegating that power to anybody, the power of that account + must be equal to its token balance. + + @Note: + + @Link: +*/ + +rule powerWhenNotDelegating(address account) { + uint256 balance = balanceOf(account); + bool isDelegatingVoting = getDelegatingVoting(account); + bool isDelegatingProposition = getDelegatingProposition(account); + uint72 dvb = getDelegatedVotingBalance(account); + uint72 dpb = getDelegatedPropositionBalance(account); + + uint256 votingPower = getPowerCurrent(account, VOTING_POWER()); + uint256 propositionPower = getPowerCurrent(account, PROPOSITION_POWER()); + + assert dvb == 0 && !isDelegatingVoting => votingPower == balance; + assert dpb == 0 && !isDelegatingProposition => propositionPower == balance; +} + + +/** + Account1 and account2 are not delegating power +*/ + +/* + @Rule + + @Description: + Verify correct voting power on token transfers, when both accounts are not delegating + + @Note: + + @Link: +*/ + +rule vpTransferWhenBothNotDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + bool isBobDelegatingVoting = getDelegatingVoting(bob); + + // both accounts are not delegating + require !isAliceDelegatingVoting && !isBobDelegatingVoting; + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + + assert alicePowerAfter == alicePowerBefore - amount; + assert bobPowerAfter == bobPowerBefore + amount; + assert charliePowerAfter == charliePowerBefore; +} + +/* + @Rule + + @Description: + Verify correct proposition power on token transfers, when both accounts are not delegating + + @Note: + + @Link: +*/ + +rule ppTransferWhenBothNotDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingProposition = getDelegatingProposition(alice); + bool isBobDelegatingProposition = getDelegatingProposition(bob); + + require !isAliceDelegatingProposition && !isBobDelegatingProposition; + + uint256 alicePowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, PROPOSITION_POWER()); + + assert alicePowerAfter == alicePowerBefore - amount; + assert bobPowerAfter == bobPowerBefore + amount; + assert charliePowerAfter == charliePowerBefore; +} + +/* + @Rule + + @Description: + Verify correct voting power after Alice delegates to Bob, when + both accounts were not delegating + + @Note: + + @Link: +*/ + +rule vpDelegateWhenBothNotDelegating(address alice, address bob, address charlie) { + env e; + require alice == e.msg.sender; + require alice != 0 && bob != 0 && charlie != 0; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + bool isBobDelegatingVoting = getDelegatingVoting(bob); + + require !isAliceDelegatingVoting && !isBobDelegatingVoting; + + uint256 aliceBalance = balanceOf(alice); + uint256 bobBalance = balanceOf(bob); + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + + delegate(e, bob); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + + assert alicePowerAfter == alicePowerBefore - aliceBalance; + assert bobPowerAfter == bobPowerBefore + (aliceBalance / DELEGATED_POWER_DIVIDER()) * DELEGATED_POWER_DIVIDER(); + assert getVotingDelegate(alice) == bob; + assert charliePowerAfter == charliePowerBefore; +} + +/* + @Rule + + @Description: + Verify correct proposition power after Alice delegates to Bob, when + both accounts were not delegating + + @Note: + + @Link: +*/ + +rule ppDelegateWhenBothNotDelegating(address alice, address bob, address charlie) { + env e; + require alice == e.msg.sender; + require alice != 0 && bob != 0 && charlie != 0; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingProposition = getDelegatingProposition(alice); + bool isBobDelegatingProposition = getDelegatingProposition(bob); + + require !isAliceDelegatingProposition && !isBobDelegatingProposition; + + uint256 aliceBalance = balanceOf(alice); + uint256 bobBalance = balanceOf(bob); + + uint256 alicePowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); + + delegate(e, bob); + + uint256 alicePowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, PROPOSITION_POWER()); + + assert alicePowerAfter == alicePowerBefore - aliceBalance; + assert bobPowerAfter == bobPowerBefore + (aliceBalance / DELEGATED_POWER_DIVIDER()) * DELEGATED_POWER_DIVIDER(); + assert getPropositionDelegate(alice) == bob; + assert charliePowerAfter == charliePowerBefore; +} + +/** + Account1 is delegating power to delegatee1, account2 is not delegating power to anybody +*/ + +/* + @Rule + + @Description: + Verify correct voting power after a token transfer from Alice to Bob, when + Alice was delegating and Bob wasn't + + @Note: + + @Link: +*/ + +rule vpTransferWhenOnlyOneIsDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + bool isBobDelegatingVoting = getDelegatingVoting(bob); + address aliceDelegate = getVotingDelegate(alice); + require aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != bob && aliceDelegate != charlie; + + require isAliceDelegatingVoting && !isBobDelegatingVoting; + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + // no delegation of anyone to Alice + require alicePowerBefore == 0; + + uint256 bobPowerBefore = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, VOTING_POWER()); + uint256 aliceBalanceBefore = balanceOf(alice); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, VOTING_POWER()); + uint256 aliceBalanceAfter = balanceOf(alice); + + assert alicePowerBefore == alicePowerAfter; + assert aliceDelegatePowerAfter == + aliceDelegatePowerBefore - normalize(aliceBalanceBefore) + normalize(aliceBalanceAfter); + assert bobPowerAfter == bobPowerBefore + amount; + assert charliePowerBefore == charliePowerAfter; +} + +/* + @Rule + + @Description: + Verify correct proposition power after a token transfer from Alice to Bob, when + Alice was delegating and Bob wasn't + + @Note: + + @Link: +*/ + +rule ppTransferWhenOnlyOneIsDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingProposition = getDelegatingProposition(alice); + bool isBobDelegatingProposition = getDelegatingProposition(bob); + address aliceDelegate = getPropositionDelegate(alice); + require aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != bob && aliceDelegate != charlie; + + require isAliceDelegatingProposition && !isBobDelegatingProposition; + + uint256 alicePowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + // no delegation of anyone to Alice + require alicePowerBefore == 0; + + uint256 bobPowerBefore = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + uint256 aliceBalanceBefore = balanceOf(alice); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + uint256 aliceBalanceAfter = balanceOf(alice); + + // still zero + assert alicePowerBefore == alicePowerAfter; + assert aliceDelegatePowerAfter == + aliceDelegatePowerBefore - normalize(aliceBalanceBefore) + normalize(aliceBalanceAfter); + assert bobPowerAfter == bobPowerBefore + amount; + assert charliePowerBefore == charliePowerAfter; +} + + +/* + @Rule + + @Description: + Verify correct voting power after Alice stops delegating, when + Alice was delegating and Bob wasn't + + @Note: + + @Link: +*/ +rule vpStopDelegatingWhenOnlyOneIsDelegating(address alice, address charlie) { + env e; + require alice != charlie; + require alice == e.msg.sender; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + address aliceDelegate = getVotingDelegate(alice); + + require isAliceDelegatingVoting && aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != charlie; + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, VOTING_POWER()); + + delegate(e, 0); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, VOTING_POWER()); + + assert alicePowerAfter == alicePowerBefore + balanceOf(alice); + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - normalize(balanceOf(alice)); + assert charliePowerAfter == charliePowerBefore; +} + +/* + @Rule + + @Description: + Verify correct proposition power after Alice stops delegating, when + Alice was delegating and Bob wasn't + + @Note: + + @Link: +*/ +rule ppStopDelegatingWhenOnlyOneIsDelegating(address alice, address charlie) { + env e; + require alice != charlie; + require alice == e.msg.sender; + + bool isAliceDelegatingProposition = getDelegatingProposition(alice); + address aliceDelegate = getPropositionDelegate(alice); + + require isAliceDelegatingProposition && aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != charlie; + + uint256 alicePowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + + delegate(e, 0); + + uint256 alicePowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + + assert alicePowerAfter == alicePowerBefore + balanceOf(alice); + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - normalize(balanceOf(alice)); + assert charliePowerAfter == charliePowerBefore; +} + +/* + @Rule + + @Description: + Verify correct voting power after Alice delegates + + @Note: + + @Link: +*/ +rule vpChangeDelegateWhenOnlyOneIsDelegating(address alice, address delegate2, address charlie) { + env e; + require alice != charlie && alice != delegate2 && charlie != delegate2; + require alice == e.msg.sender; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + address aliceDelegate = getVotingDelegate(alice); + require aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != delegate2 && + delegate2 != 0 && delegate2 != charlie && aliceDelegate != charlie; + + require isAliceDelegatingVoting; + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, VOTING_POWER()); + uint256 delegate2PowerBefore = getPowerCurrent(delegate2, VOTING_POWER()); + + delegate(e, delegate2); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, VOTING_POWER()); + uint256 delegate2PowerAfter = getPowerCurrent(delegate2, VOTING_POWER()); + address aliceDelegateAfter = getVotingDelegate(alice); + + assert alicePowerBefore == alicePowerAfter; + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - normalize(balanceOf(alice)); + assert delegate2PowerAfter == delegate2PowerBefore + normalize(balanceOf(alice)); + assert aliceDelegateAfter == delegate2; + assert charliePowerAfter == charliePowerBefore; +} + +/* + @Rule + + @Description: + Verify correct proposition power after Alice delegates + + @Note: + + @Link: +*/ +rule ppChangeDelegateWhenOnlyOneIsDelegating(address alice, address delegate2, address charlie) { + env e; + require alice != charlie && alice != delegate2 && charlie != delegate2; + require alice == e.msg.sender; + + bool isAliceDelegatingVoting = getDelegatingProposition(alice); + address aliceDelegate = getPropositionDelegate(alice); + require aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != delegate2 && + delegate2 != 0 && delegate2 != charlie && aliceDelegate != charlie; + + require isAliceDelegatingVoting; + + uint256 alicePowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + uint256 delegate2PowerBefore = getPowerCurrent(delegate2, PROPOSITION_POWER()); + + delegate(e, delegate2); + + uint256 alicePowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + uint256 delegate2PowerAfter = getPowerCurrent(delegate2, PROPOSITION_POWER()); + address aliceDelegateAfter = getPropositionDelegate(alice); + + assert alicePowerBefore == alicePowerAfter; + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - normalize(balanceOf(alice)); + assert delegate2PowerAfter == delegate2PowerBefore + normalize(balanceOf(alice)); + assert aliceDelegateAfter == delegate2; + assert charliePowerAfter == charliePowerBefore; +} + +/* + @Rule + + @Description: + Verify correct voting power after Alice transfers to Bob, when only Bob was delegating + + @Note: + + @Link: +*/ + +rule vpOnlyAccount2IsDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + bool isBobDelegatingVoting = getDelegatingVoting(bob); + address bobDelegate = getVotingDelegate(bob); + require bobDelegate != bob && bobDelegate != 0 && bobDelegate != alice && bobDelegate != charlie; + + require !isAliceDelegatingVoting && isBobDelegatingVoting; + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, VOTING_POWER()); + require bobPowerBefore == 0; + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + uint256 bobDelegatePowerBefore = getPowerCurrent(bobDelegate, VOTING_POWER()); + uint256 bobBalanceBefore = balanceOf(bob); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + uint256 bobDelegatePowerAfter = getPowerCurrent(bobDelegate, VOTING_POWER()); + uint256 bobBalanceAfter = balanceOf(bob); + + assert alicePowerAfter == alicePowerBefore - amount; + assert bobPowerAfter == 0; + assert bobDelegatePowerAfter == bobDelegatePowerBefore - normalize(bobBalanceBefore) + normalize(bobBalanceAfter); + + assert charliePowerAfter == charliePowerBefore; +} + +/* + @Rule + + @Description: + Verify correct proposition power after Alice transfers to Bob, when only Bob was delegating + + @Note: + + @Link: +*/ + +rule ppOnlyAccount2IsDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegating = getDelegatingProposition(alice); + bool isBobDelegating = getDelegatingProposition(bob); + address bobDelegate = getPropositionDelegate(bob); + require bobDelegate != bob && bobDelegate != 0 && bobDelegate != alice && bobDelegate != charlie; + + require !isAliceDelegating && isBobDelegating; + + uint256 alicePowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, PROPOSITION_POWER()); + require bobPowerBefore == 0; + uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 bobDelegatePowerBefore = getPowerCurrent(bobDelegate, PROPOSITION_POWER()); + uint256 bobBalanceBefore = balanceOf(bob); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 bobDelegatePowerAfter = getPowerCurrent(bobDelegate, PROPOSITION_POWER()); + uint256 bobBalanceAfter = balanceOf(bob); + + assert alicePowerAfter == alicePowerBefore - amount; + assert bobPowerAfter == 0; + assert bobDelegatePowerAfter == bobDelegatePowerBefore - normalize(bobBalanceBefore) + normalize(bobBalanceAfter); + + assert charliePowerAfter == charliePowerBefore; +} + + +/* + @Rule + + @Description: + Verify correct voting power after Alice transfers to Bob, when both Alice + and Bob were delegating + + @Note: + + @Link: +*/ +rule vpTransferWhenBothAreDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegatingVoting = getDelegatingVoting(alice); + bool isBobDelegatingVoting = getDelegatingVoting(bob); + require isAliceDelegatingVoting && isBobDelegatingVoting; + address aliceDelegate = getVotingDelegate(alice); + address bobDelegate = getVotingDelegate(bob); + require aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != bob && aliceDelegate != charlie; + require bobDelegate != bob && bobDelegate != 0 && bobDelegate != alice && bobDelegate != charlie; + require aliceDelegate != bobDelegate; + + uint256 alicePowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, VOTING_POWER()); + uint256 bobDelegatePowerBefore = getPowerCurrent(bobDelegate, VOTING_POWER()); + uint256 aliceBalanceBefore = balanceOf(alice); + uint256 bobBalanceBefore = balanceOf(bob); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, VOTING_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, VOTING_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, VOTING_POWER()); + uint256 bobDelegatePowerAfter = getPowerCurrent(bobDelegate, VOTING_POWER()); + uint256 aliceBalanceAfter = balanceOf(alice); + uint256 bobBalanceAfter = balanceOf(bob); + + assert alicePowerAfter == alicePowerBefore; + assert bobPowerAfter == bobPowerBefore; + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - normalize(aliceBalanceBefore) + + normalize(aliceBalanceAfter); + + uint256 normalizedBalanceBefore = normalize(bobBalanceBefore); + uint256 normalizedBalanceAfter = normalize(bobBalanceAfter); + assert bobDelegatePowerAfter == bobDelegatePowerBefore - normalizedBalanceBefore + normalizedBalanceAfter; +} + +/* + @Rule + + @Description: + Verify correct proposition power after Alice transfers to Bob, when both Alice + and Bob were delegating + + @Note: + + @Link: +*/ +rule ppTransferWhenBothAreDelegating(address alice, address bob, address charlie, uint256 amount) { + env e; + require alice != bob && bob != charlie && alice != charlie; + + bool isAliceDelegating = getDelegatingProposition(alice); + bool isBobDelegating = getDelegatingProposition(bob); + require isAliceDelegating && isBobDelegating; + address aliceDelegate = getPropositionDelegate(alice); + address bobDelegate = getPropositionDelegate(bob); + require aliceDelegate != alice && aliceDelegate != 0 && aliceDelegate != bob && aliceDelegate != charlie; + require bobDelegate != bob && bobDelegate != 0 && bobDelegate != alice && bobDelegate != charlie; + require aliceDelegate != bobDelegate; + + uint256 alicePowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerBefore = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerBefore = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerBefore = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + uint256 bobDelegatePowerBefore = getPowerCurrent(bobDelegate, PROPOSITION_POWER()); + uint256 aliceBalanceBefore = balanceOf(alice); + uint256 bobBalanceBefore = balanceOf(bob); + + transferFrom(e, alice, bob, amount); + + uint256 alicePowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + uint256 bobPowerAfter = getPowerCurrent(bob, PROPOSITION_POWER()); + uint256 charliePowerAfter = getPowerCurrent(charlie, PROPOSITION_POWER()); + uint256 aliceDelegatePowerAfter = getPowerCurrent(aliceDelegate, PROPOSITION_POWER()); + uint256 bobDelegatePowerAfter = getPowerCurrent(bobDelegate, PROPOSITION_POWER()); + uint256 aliceBalanceAfter = balanceOf(alice); + uint256 bobBalanceAfter = balanceOf(bob); + + assert alicePowerAfter == alicePowerBefore; + assert bobPowerAfter == bobPowerBefore; + assert aliceDelegatePowerAfter == aliceDelegatePowerBefore - normalize(aliceBalanceBefore) + + normalize(aliceBalanceAfter); + + uint256 normalizedBalanceBefore = normalize(bobBalanceBefore); + uint256 normalizedBalanceAfter = normalize(bobBalanceAfter); + assert bobDelegatePowerAfter == bobDelegatePowerBefore - normalizedBalanceBefore + normalizedBalanceAfter; +} + +/* + @Rule + + @Description: + Verify that an account's delegate changes only as a result of a call to + the delegation functions + + @Note: + + @Link: +*/ +rule votingDelegateChanges(address alice, method f) { + env e; + calldataarg args; + + address aliceVotingDelegateBefore = getVotingDelegate(alice); + address alicePropDelegateBefore = getPropositionDelegate(alice); + + f(e, args); + + address aliceVotingDelegateAfter = getVotingDelegate(alice); + address alicePropDelegateAfter = getPropositionDelegate(alice); + + // only these four function may change the delegate of an address + assert aliceVotingDelegateAfter != aliceVotingDelegateBefore || alicePropDelegateBefore != alicePropDelegateAfter => + f.selector == delegate(address).selector || + f.selector == delegateByType(address,uint8).selector || + f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector || + f.selector == metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32).selector; +} + +/* + @Rule + + @Description: + Verify that an account's voting and proposition power changes only as a result of a call to + the delegation and transfer functions + + @Note: + + @Link: +*/ +rule votingPowerChanges(address alice, method f) { + env e; + calldataarg args; + + uint aliceVotingPowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint alicePropPowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + + f(e, args); + + uint aliceVotingPowerAfter = getPowerCurrent(alice, VOTING_POWER()); + uint alicePropPowerAfter = getPowerCurrent(alice, PROPOSITION_POWER()); + + // only these four function may change the power of an address + assert aliceVotingPowerAfter != aliceVotingPowerBefore || alicePropPowerAfter != alicePropPowerBefore => + f.selector == delegate(address).selector || + f.selector == delegateByType(address,uint8).selector || + f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector || + f.selector == metaDelegateByType(address,address,uint8,uint256,uint8,bytes32,bytes32).selector || + f.selector == transfer(address,uint256).selector || + f.selector == transferFrom(address,address,uint256).selector; +} + +/* + @Rule + + @Description: + Verify that only delegate() and metaDelegate() may change both voting and + proposition delegates of an account at once. + + @Note: + + @Link: +*/ +rule delegationTypeIndependence(address who, method f) filtered { f -> !f.isView } { + address _delegateeV = getVotingDelegate(who); + address _delegateeP = getPropositionDelegate(who); + + env e; + calldataarg arg; + f(e, arg); + + address delegateeV_ = getVotingDelegate(who); + address delegateeP_ = getPropositionDelegate(who); + assert _delegateeV != delegateeV_ && _delegateeP != delegateeP_ => + (f.selector == delegate(address).selector || + f.selector == metaDelegate(address,address,uint256,uint8,bytes32,bytes32).selector), + "one delegatee type stays the same, unless delegate or delegateBySig was called"; +} + +/* + @Rule + + @Description: + Verifies that delegating twice to the same delegate changes the delegate's + voting power only once. + + @Note: + + @Link: +*/ +rule cantDelegateTwice(address _delegate) { + env e; + + address delegateBeforeV = getVotingDelegate(e.msg.sender); + address delegateBeforeP = getPropositionDelegate(e.msg.sender); + require delegateBeforeV != _delegate && delegateBeforeV != e.msg.sender && delegateBeforeV != 0; + require delegateBeforeP != _delegate && delegateBeforeP != e.msg.sender && delegateBeforeP != 0; + require _delegate != e.msg.sender && _delegate != 0 && e.msg.sender != 0; + require getDelegationState(e.msg.sender) == FULL_POWER_DELEGATED(); + + uint256 votingPowerBefore = getPowerCurrent(_delegate, VOTING_POWER()); + uint256 propPowerBefore = getPowerCurrent(_delegate, PROPOSITION_POWER()); + + delegate(e, _delegate); + + uint256 votingPowerAfter = getPowerCurrent(_delegate, VOTING_POWER()); + uint256 propPowerAfter = getPowerCurrent(_delegate, PROPOSITION_POWER()); + + delegate(e, _delegate); + + uint256 votingPowerAfter2 = getPowerCurrent(_delegate, VOTING_POWER()); + uint256 propPowerAfter2 = getPowerCurrent(_delegate, PROPOSITION_POWER()); + + assert votingPowerAfter == votingPowerBefore + normalize(balanceOf(e.msg.sender)); + assert propPowerAfter == propPowerBefore + normalize(balanceOf(e.msg.sender)); + assert votingPowerAfter2 == votingPowerAfter && propPowerAfter2 == propPowerAfter; +} + +/* + @Rule + + @Description: + transfer and transferFrom change voting/proposition power identically + + @Note: + + @Link: +*/ +rule transferAndTransferFromPowerEquivalence(address bob, uint amount) { + env e1; + env e2; + storage init = lastStorage; + + address alice; + require alice == e1.msg.sender; + + uint aliceVotingPowerBefore = getPowerCurrent(alice, VOTING_POWER()); + uint alicePropPowerBefore = getPowerCurrent(alice, PROPOSITION_POWER()); + + transfer(e1, bob, amount); + + uint aliceVotingPowerAfterTransfer = getPowerCurrent(alice, VOTING_POWER()); + uint alicePropPowerAfterTransfer = getPowerCurrent(alice, PROPOSITION_POWER()); + + transferFrom(e2, alice, bob, amount) at init; + + uint aliceVotingPowerAfterTransferFrom = getPowerCurrent(alice, VOTING_POWER()); + uint alicePropPowerAfterTransferFrom = getPowerCurrent(alice, PROPOSITION_POWER()); + + assert aliceVotingPowerAfterTransfer == aliceVotingPowerAfterTransferFrom && + alicePropPowerAfterTransfer == alicePropPowerAfterTransferFrom; + +} \ No newline at end of file diff --git a/certora/specs/erc20.spec b/certora/specs/erc20.spec new file mode 100644 index 0000000..f77eba9 --- /dev/null +++ b/certora/specs/erc20.spec @@ -0,0 +1,559 @@ +/* + This is a specification file for the verification of general ERC20 + features of AaveTokenV3.sol smart contract using the Certora prover. + For more information, visit: https://www.certora.com/ + + This file is run with scripts/erc20.sh + On the token harness AaveTokenV3Harness.sol + + Sanity run: + https://prover.certora.com/output/67509/a5d16a31a49b9c9a7b71/?anonymousKey=bd108549122fd97450428a26c4ed52458793b898 +*/ +import "base.spec" + +function doesntChangeBalance(method f) returns bool { + return f.selector != transfer(address,uint256).selector && + f.selector != transferFrom(address,address,uint256).selector; +} + + + +/* + @Rule + + + @Description: + Verify that there is no fee on transferFrom() (like potentially on USDT) + + @Formula: + { + balances[bob] = y + allowance(alice, msg.sender) >= amount + } + < + transferFrom(alice, bob, amount) + > + { + balances[bob] = y + amount + } + + @Notes: + + + @Link: + +*/ +rule noFeeOnTransferFrom(address alice, address bob, uint256 amount) { + env e; + calldataarg args; + require alice != bob; + require allowance(alice, e.msg.sender) >= amount; + uint256 balanceBefore = balanceOf(bob); + + transferFrom(e, alice, bob, amount); + + uint256 balanceAfter = balanceOf(bob); + assert balanceAfter == balanceBefore + amount; +} + +/* + @Rule + + @Description: + Verify that there is no fee on transfer() (like potentially on USDT) + + @Formula: + { + balances[bob] = y + balances[msg.sender] >= amount + } + < + transfer(bob, amount) + > + { + balances[bob] = y + amount + } + + @Notes: + + @Link: + + +*/ +rule noFeeOnTransfer(address bob, uint256 amount) { + env e; + calldataarg args; + require bob != e.msg.sender; + uint256 balanceSenderBefore = balanceOf(e.msg.sender); + uint256 balanceBefore = balanceOf(bob); + + transfer(e, bob, amount); + + uint256 balanceAfter = balanceOf(bob); + uint256 balanceSenderAfter = balanceOf(e.msg.sender); + assert balanceAfter == balanceBefore + amount; +} + +/* + @Rule + + + @Description: + Token transfer works correctly. Balances are updated if not reverted. + If reverted then the transfer amount was too high, or the recipient is 0. + + @Formula: + { + balanceFromBefore = balanceOf(msg.sender) + balanceToBefore = balanceOf(to) + } + < + transfer(to, amount) + > + { + lastReverted => to = 0 || amount > balanceOf(msg.sender) + !lastReverted => balanceOf(to) = balanceToBefore + amount && + balanceOf(msg.sender) = balanceFromBefore - amount + } + + @Notes: + This rule fails on tokens with a blacklist function, like USDC and USDT. + The prover finds a counterexample of a reverted transfer to a blacklisted address or a transfer in a paused state. + + @Link: + +*/ + + +rule transferCorrect(address to, uint256 amount) { + env e; + require e.msg.value == 0 && e.msg.sender != 0; + uint256 fromBalanceBefore = balanceOf(e.msg.sender); + uint256 toBalanceBefore = balanceOf(to); + require fromBalanceBefore + toBalanceBefore < AAVE_MAX_SUPPLY() / 100; + + // proven elsewhere + address v_delegateTo = getVotingDelegate(to); + mathint dvbTo = getDelegatedVotingBalance(v_delegateTo); + require dvbTo >= balanceOf(to) / DELEGATED_POWER_DIVIDER() && + dvbTo < SCALED_MAX_SUPPLY() - amount / DELEGATED_POWER_DIVIDER(); + address p_delegateTo = getPropositionDelegate(to); + mathint pvbTo = getDelegatedPropositionBalance(p_delegateTo); + require pvbTo >= balanceOf(to) / DELEGATED_POWER_DIVIDER() && + pvbTo < SCALED_MAX_SUPPLY() - amount / DELEGATED_POWER_DIVIDER(); + + // proven elsewhere + address v_delegateFrom = getVotingDelegate(e.msg.sender); + address p_delegateFrom = getPropositionDelegate(e.msg.sender); + mathint dvbFrom = getDelegatedVotingBalance(v_delegateFrom); + mathint pvbFrom = getDelegatedPropositionBalance(p_delegateFrom); + require dvbFrom >= balanceOf(e.msg.sender) / DELEGATED_POWER_DIVIDER(); + require pvbFrom >= balanceOf(e.msg.sender) / DELEGATED_POWER_DIVIDER(); + + require validDelegationState(e.msg.sender) && validDelegationState(to); + require ! ( (getDelegatingVoting(to) && v_delegateTo == to) || + (getDelegatingProposition(to) && p_delegateTo == to)); + + // to not overcomplicate the constraints on dvbTo and dvbFrom + require v_delegateFrom != v_delegateTo && p_delegateFrom != p_delegateTo; + + transfer@withrevert(e, to, amount); + bool reverted = lastReverted; + if (!reverted) { + if (e.msg.sender == to) { + assert balanceOf(e.msg.sender) == fromBalanceBefore; + } else { + assert balanceOf(e.msg.sender) == fromBalanceBefore - amount; + assert balanceOf(to) == toBalanceBefore + amount; + } + } else { + assert amount > fromBalanceBefore || to == 0; + } +} + +/* + @Rule + + + @Description: + Test that transferFrom works correctly. Balances are updated if not reverted. + If reverted, it means the transfer amount was too high, or the recipient is 0 + + @Formula: + { + balanceFromBefore = balanceOf(from) + balanceToBefore = balanceOf(to) + } + < + transferFrom(from, to, amount) + > + { + lastreverted => to = 0 || amount > balanceOf(from) + !lastreverted => balanceOf(to) = balanceToBefore + amount && + balanceOf(from) = balanceFromBefore - amount + } + + @Notes: + This rule fails on tokens with a blacklist and or pause function, like USDC and USDT. + The prover finds a counterexample of a reverted transfer to a blacklisted address or a transfer in a paused state. + + @Link: + +*/ + +rule transferFromCorrect(address from, address to, uint256 amount) { + env e; + require e.msg.value == 0; + uint256 fromBalanceBefore = balanceOf(from); + uint256 toBalanceBefore = balanceOf(to); + uint256 allowanceBefore = allowance(from, e.msg.sender); + require fromBalanceBefore + toBalanceBefore < AAVE_MAX_SUPPLY(); + + transferFrom(e, from, to, amount); + + assert from != to => + balanceOf(from) == fromBalanceBefore - amount && + balanceOf(to) == toBalanceBefore + amount && + (allowance(from, e.msg.sender) == allowanceBefore - amount || + allowance(from, e.msg.sender) == max_uint256); +} + +/* + @Rule + + @Description: + Balance of address 0 is always 0 + + @Formula: + { balanceOf[0] = 0 } + + @Notes: + + + @Link: + +*/ +invariant ZeroAddressNoBalance() + balanceOf(0) == 0 + + +/* + @Rule + + @Description: + Contract calls don't change token total supply. + + @Formula: + { + supplyBefore = totalSupply() + } + < f(e, args)> + { + supplyAfter = totalSupply() + supplyBefore == supplyAfter + } + + @Notes: + This rule should fail for any token that has functions that change totalSupply(), like mint() and burn(). + It's still important to run the rule and see if it fails in functions that _aren't_ supposed to modify totalSupply() + + @Link: + +*/ +rule NoChangeTotalSupply(method f) { + // require f.selector != burn(uint256).selector && f.selector != mint(address, uint256).selector; + uint256 totalSupplyBefore = totalSupply(); + env e; + calldataarg args; + f(e, args); + assert totalSupply() == totalSupplyBefore; +} + +/* + The two rules cover the same ground as NoChangeTotalSupply. + + The split into two rules is in order to make the burn/mint features of a tested token even more obvious +*/ +rule noBurningTokens(method f) { + uint256 totalSupplyBefore = totalSupply(); + env e; + calldataarg args; + f(e, args); + assert totalSupply() >= totalSupplyBefore; +} + +rule noMintingTokens(method f) { + uint256 totalSupplyBefore = totalSupply(); + env e; + calldataarg args; + f(e, args); + assert totalSupply() <= totalSupplyBefore; +} + +/* + @Rule + + @Description: + Allowance changes correctly as a result of calls to approve, transfer, increaseAllowance, decreaseAllowance + + @Formula: + { + allowanceBefore = allowance(from, spender) + } + < + f(e, args) + > + { + f.selector = approve(spender, amount) => allowance(from, spender) = amount + f.selector = transferFrom(from, spender, amount) => allowance(from, spender) = allowanceBefore - amount + f.selector = decreaseAllowance(spender, delta) => allowance(from, spender) = allowanceBefore - delta + f.selector = increaseAllowance(spender, delta) => allowance(from, spender) = allowanceBefore + delta + generic f.selector => allowance(from, spender) == allowanceBefore + } + + @Notes: + Some ERC20 tokens have functions like permit() that change allowance via a signature. + The rule will fail on such functions. + + @Link: + +*/ +rule ChangingAllowance(method f, address from, address spender) { + uint256 allowanceBefore = allowance(from, spender); + env e; + if (f.selector == approve(address, uint256).selector) { + address spender_; + uint256 amount; + approve(e, spender_, amount); + if (from == e.msg.sender && spender == spender_) { + assert allowance(from, spender) == amount; + } else { + assert allowance(from, spender) == allowanceBefore; + } + } else if (f.selector == transferFrom(address,address,uint256).selector) { + address from_; + address to; + address amount; + transferFrom(e, from_, to, amount); + uint256 allowanceAfter = allowance(from, spender); + if (from == from_ && spender == e.msg.sender) { + assert from == to || allowanceBefore == max_uint256 || allowanceAfter == allowanceBefore - amount; + } else { + assert allowance(from, spender) == allowanceBefore; + } + } else if (f.selector == decreaseAllowance(address, uint256).selector) { + address spender_; + uint256 amount; + require amount <= allowanceBefore; + decreaseAllowance(e, spender_, amount); + if (from == e.msg.sender && spender == spender_) { + assert allowance(from, spender) == allowanceBefore - amount; + } else { + assert allowance(from, spender) == allowanceBefore; + } + } else if (f.selector == increaseAllowance(address, uint256).selector) { + address spender_; + uint256 amount; + require amount + allowanceBefore < max_uint256; + increaseAllowance(e, spender_, amount); + if (from == e.msg.sender && spender == spender_) { + assert allowance(from, spender) == allowanceBefore + amount; + } else { + assert allowance(from, spender) == allowanceBefore; + } + } + else + { + calldataarg args; + f(e, args); + assert allowance(from, spender) == allowanceBefore || + f.selector == permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector; + } +} + +/* + @Rule + + @Description: + Transfer from a to b doesn't change the sum of their balances + + @Formula: + { + balancesBefore = balanceOf(msg.sender) + balanceOf(b) + } + < + transfer(b, amount) + > + { + balancesBefore == balanceOf(msg.sender) + balanceOf(b) + } + + @Notes: + + @Link: + +*/ +rule TransferSumOfFromAndToBalancesStaySame(address to, uint256 amount) { + env e; + mathint sum = balanceOf(e.msg.sender) + balanceOf(to); + require sum < max_uint256; + transfer(e, to, amount); + assert balanceOf(e.msg.sender) + balanceOf(to) == sum; +} + +/* + @Rule + + @Description: + Transfer using transferFrom() from a to b doesn't change the sum of their balances + + @Formula: + { + balancesBefore = balanceOf(a) + balanceOf(b) + } + < + transferFrom(a, b) + > + { + balancesBefore == balanceOf(a) + balanceOf(b) + } + + @Notes: + + @Link: + +*/ +rule TransferFromSumOfFromAndToBalancesStaySame(address from, address to, uint256 amount) { + env e; + mathint sum = balanceOf(from) + balanceOf(to); + require sum < max_uint256; + transferFrom(e, from, to, amount); + assert balanceOf(from) + balanceOf(to) == sum; +} + +/* + @Rule + + @Description: + Transfer from msg.sender to alice doesn't change the balance of other addresses + + @Formula: + { + balanceBefore = balanceOf(bob) + } + < + transfer(alice, amount) + > + { + balanceOf(bob) == balanceBefore + } + + @Notes: + + @Link: + +*/ +rule TransferDoesntChangeOtherBalance(address to, uint256 amount, address other) { + env e; + require other != e.msg.sender; + require other != to && other != currentContract; + uint256 balanceBefore = balanceOf(other); + transfer(e, to, amount); + assert balanceBefore == balanceOf(other); +} + +/* + @Rule + + @Description: + Transfer from alice to bob using transferFrom doesn't change the balance of other addresses + + @Formula: + { + balanceBefore = balanceOf(charlie) + } + < + transferFrom(alice, bob, amount) + > + { + balanceOf(charlie) = balanceBefore + } + + @Notes: + + @Link: + +*/ +rule TransferFromDoesntChangeOtherBalance(address from, address to, uint256 amount, address other) { + env e; + require other != from; + require other != to; + uint256 balanceBefore = balanceOf(other); + transferFrom(e, from, to, amount); + assert balanceBefore == balanceOf(other); +} + +/* + @Rule + + @Description: + Balance of an address, who is not a sender or a recipient in transfer functions, doesn't decrease + as a result of contract calls + + @Formula: + { + balanceBefore = balanceOf(charlie) + } + < + f(e, args) + > + { + f.selector != transfer && f.selector != transferFrom => balanceOf(charlie) == balanceBefore + } + + @Notes: + USDC token has functions like transferWithAuthorization that use a signed message for allowance. + FTT token has a burnFrom that lets an approved spender to burn owner's token. + Certora prover finds these counterexamples to this rule. + In general, the rule will fail on all functions other than transfer/transferFrom that change a balance of an address. + + @Link: + +*/ +rule OtherBalanceOnlyGoesUp(address other, method f) { + env e; + require other != currentContract; + uint256 balanceBefore = balanceOf(other); + + if (f.selector == transferFrom(address, address, uint256).selector) { + address from; + address to; + uint256 amount; + require(other != from); + require balanceOf(from) + balanceBefore < max_uint256; + transferFrom(e, from, to, amount); + } else if (f.selector == transfer(address, uint256).selector) { + require other != e.msg.sender; + require balanceOf(e.msg.sender) + balanceBefore < max_uint256; + calldataarg args; + f(e, args); + } else { + require other != e.msg.sender; + calldataarg args; + f(e, args); + } + + assert balanceOf(other) >= balanceBefore; +} + +rule noRebasing(method f, address alice) { + env e; + calldataarg args; + + require doesntChangeBalance(f); + + uint256 balanceBefore = balanceOf(alice); + f(e, args); + uint256 balanceAfter = balanceOf(alice); + assert balanceBefore == balanceAfter; +} diff --git a/certora/specs/general.spec b/certora/specs/general.spec new file mode 100644 index 0000000..45ec69f --- /dev/null +++ b/certora/specs/general.spec @@ -0,0 +1,225 @@ +/* + This is a specification file for the verification of delegation invariants + of AaveTokenV3.sol smart contract using the Certora prover. + For more information, visit: https://www.certora.com/ + + This file is run with scripts/verifyGeneral.sh + On a version with some minimal code modifications + AaveTokenV3HarnessStorage.sol + + Sanity check results: https://prover.certora.com/output/67509/8cee7c95432ede6b3f9f/?anonymousKey=78d297585a2b2edc38f6c513e0ce12df10e47b82 +*/ + +import "base.spec" + + +/** + + Ghosts + +*/ + +// sum of all user balances +ghost mathint sumBalances { + init_state axiom sumBalances == 0; +} + +// tracking voting delegation status for each address +ghost mapping(address => bool) isDelegatingVoting { + init_state axiom forall address a. isDelegatingVoting[a] == false; +} + +// tracking voting delegation status for each address +ghost mapping(address => bool) isDelegatingProposition { + init_state axiom forall address a. isDelegatingProposition[a] == false; +} + +// sum of all voting delegated balances +ghost mathint sumDelegatedBalancesV { + init_state axiom sumDelegatedBalancesV == 0; +} + +// sum of all proposition undelegated balances +ghost mathint sumUndelegatedBalancesV { + init_state axiom sumUndelegatedBalancesV == 0; +} + +// sum of all proposition delegated balances +ghost mathint sumDelegatedBalancesP { + init_state axiom sumDelegatedBalancesP == 0; +} + +// sum of all voting undelegated balances +ghost mathint sumUndelegatedBalancesP { + init_state axiom sumUndelegatedBalancesP == 0; +} + +// token balances of each address +ghost mapping(address => uint104) balances { + init_state axiom forall address a. balances[a] == 0; +} + +/* + + Hooks + +*/ + + +/* + + This hook updates the sum of delegated and undelegated balances on each change of delegation state. + If the user moves from not delegating to delegating, their balance is moved from undelegated to delegating, + and etc. + +*/ +hook Sstore _balances[KEY address user].delegationState uint8 new_state (uint8 old_state) STORAGE { + + bool willDelegateP = !DELEGATING_PROPOSITION(old_state) && DELEGATING_PROPOSITION(new_state); + bool wasDelegatingP = DELEGATING_PROPOSITION(old_state) && !DELEGATING_PROPOSITION(new_state); + + // we cannot use if statements inside hooks, hence the ternary operator + sumUndelegatedBalancesP = willDelegateP ? (sumUndelegatedBalancesP - balances[user]) : sumUndelegatedBalancesP; + sumUndelegatedBalancesP = wasDelegatingP ? (sumUndelegatedBalancesP + balances[user]) : sumUndelegatedBalancesP; + sumDelegatedBalancesP = willDelegateP ? (sumDelegatedBalancesP + balances[user]) : sumDelegatedBalancesP; + sumDelegatedBalancesP = wasDelegatingP ? (sumDelegatedBalancesP - balances[user]) : sumDelegatedBalancesP; + + // change the delegating state only if a change is stored + + isDelegatingProposition[user] = new_state == old_state + ? isDelegatingProposition[user] + : new_state == PROPOSITION_DELEGATED() || new_state == FULL_POWER_DELEGATED(); + + + bool willDelegateV = !DELEGATING_VOTING(old_state) && DELEGATING_VOTING(new_state); + bool wasDelegatingV = DELEGATING_VOTING(old_state) && !DELEGATING_VOTING(new_state); + sumUndelegatedBalancesV = willDelegateV ? (sumUndelegatedBalancesV - balances[user]) : sumUndelegatedBalancesV; + sumUndelegatedBalancesV = wasDelegatingV ? (sumUndelegatedBalancesV + balances[user]) : sumUndelegatedBalancesV; + sumDelegatedBalancesV = willDelegateV ? (sumDelegatedBalancesV + balances[user]) : sumDelegatedBalancesV; + sumDelegatedBalancesV = wasDelegatingV ? (sumDelegatedBalancesV - balances[user]) : sumDelegatedBalancesV; + + // change the delegating state only if a change is stored + + isDelegatingVoting[user] = new_state == old_state + ? isDelegatingVoting[user] + : new_state == VOTING_DELEGATED() || new_state == FULL_POWER_DELEGATED(); +} + + +/* + + This hook updates the sum of delegated and undelegated balances on each change of user balance. + Depending on the delegation state, either the delegated or the undelegated balance get updated. + +*/ +hook Sstore _balances[KEY address user].balance uint104 balance (uint104 old_balance) STORAGE { + balances[user] = balances[user] - old_balance + balance; + // we cannot use if statements inside hooks, hence the ternary operator + sumDelegatedBalancesV = isDelegatingVoting[user] + ? sumDelegatedBalancesV + to_mathint(balance) - to_mathint(old_balance) + : sumDelegatedBalancesV; + sumUndelegatedBalancesV = !isDelegatingVoting[user] + ? sumUndelegatedBalancesV + to_mathint(balance) - to_mathint(old_balance) + : sumUndelegatedBalancesV; + sumDelegatedBalancesP = isDelegatingProposition[user] + ? sumDelegatedBalancesP + to_mathint(balance) - to_mathint(old_balance) + : sumDelegatedBalancesP; + sumUndelegatedBalancesP = !isDelegatingProposition[user] + ? sumUndelegatedBalancesP + to_mathint(balance) - to_mathint(old_balance) + : sumUndelegatedBalancesP; + +} + +// user's delegation state is always valid, i.e. one of the 4 legitimate states +// (NO_DELEGATION, VOTING_DELEGATED, PROPOSITION_DELEGATED, FULL_POWER_DELEGATED) +// passes +invariant delegationStateValid(address user) + validDelegationState(user) + + +/* + @Rule + + @Description: + User's delegation flag is switched on iff user is delegating to an address + other than his own own or 0 + + @Notes: + + + @Link: + +*/ +invariant delegateCorrectness(address user) + ((getVotingDelegate(user) == user || getVotingDelegate(user) == 0) <=> !getDelegatingVoting(user)) + && + ((getPropositionDelegate(user) == user || getPropositionDelegate(user) == 0) <=> !getDelegatingProposition(user)) + { + preserved { + requireInvariant delegationStateValid(user); + } + } + +/* + @Rule + + @Description: + Sum of delegated voting balances and undelegated balances is equal to total supply + + @Notes: + + + @Link: + +*/ +invariant sumOfVBalancesCorrectness() sumDelegatedBalancesV + sumUndelegatedBalancesV == totalSupply() + +/* + @Rule + + @Description: + Sum of delegated proposition balances and undelegated balances is equal to total supply + + @Notes: + + + @Link: + +*/ +invariant sumOfPBalancesCorrectness() sumDelegatedBalancesP + sumUndelegatedBalancesP == totalSupply() + +/* + @Rule + + @Description: + Transfers don't change voting delegation state + + @Notes: + + + @Link: + +*/ +rule transferDoesntChangeDelegationState() { + env e; + address from; address to; address charlie; + require (charlie != from && charlie != to); + uint amount; + + uint8 stateFromBefore = getDelegationState(from); + uint8 stateToBefore = getDelegationState(to); + uint8 stateCharlieBefore = getDelegationState(charlie); + require stateFromBefore <= FULL_POWER_DELEGATED() && stateToBefore <= FULL_POWER_DELEGATED(); + bool testFromBefore = isDelegatingVoting[from]; + bool testToBefore = isDelegatingVoting[to]; + + transferFrom(e, from, to, amount); + + uint8 stateFromAfter = getDelegationState(from); + uint8 stateToAfter = getDelegationState(to); + bool testFromAfter = isDelegatingVoting[from]; + bool testToAfter = isDelegatingVoting[to]; + + assert testFromBefore == testFromAfter && testToBefore == testToAfter; + assert getDelegationState(charlie) == stateCharlieBefore; +} From 65e71adb221b8e99121c864ac89f5cdd03d5ba18 Mon Sep 17 00:00:00 2001 From: Tadeas Kucera Date: Tue, 28 Mar 2023 13:09:46 +0200 Subject: [PATCH 51/58] Fix applyHarness.patch and Makefile --- certora/Makefile | 9 +- certora/applyHarness.patch | 1601 ++++++++++++++++++++++++++++++++++-- 2 files changed, 1547 insertions(+), 63 deletions(-) diff --git a/certora/Makefile b/certora/Makefile index 5e6e1bc..a4c6cec 100644 --- a/certora/Makefile +++ b/certora/Makefile @@ -13,12 +13,17 @@ help: munged: $(wildcard $(CONTRACTS_DIR)/*.sol) $(PATCH) rm -rf $@ - cp -r $(CONTRACTS_DIR) $@ + mkdir $@ cp -r $(LIB_DIR) $@ + cp -r $(CONTRACTS_DIR) $@ patch -p0 -d $@ < $(PATCH) record: - diff -uN $(CONTRACTS_DIR) $(MUNGED_DIR) | sed 's+$(CONTRACTS_DIR)/++g' | sed 's+$(MUNGED_DIR)++g' > $(PATCH) + mkdir tmp_make_r + cp -r $(LIB_DIR) tmp_make_r + cp -r $(CONTRACTS_DIR) tmp_make_r + diff -ruN tmp_make_r $(MUNGED_DIR) | sed 's+tmp_make_r/++g' | sed 's+$(MUNGED_DIR)/++g' > $(PATCH) + rm -rf tmp_make_r clean: git clean -fdX diff --git a/certora/applyHarness.patch b/certora/applyHarness.patch index 3e4b33e..4649613 100644 --- a/certora/applyHarness.patch +++ b/certora/applyHarness.patch @@ -1,69 +1,1266 @@ -diff -uN AaveTokenV3.sol /AaveTokenV3.sol ---- AaveTokenV3.sol 2022-10-11 16:03:49.000000000 +0300 -+++ /AaveTokenV3.sol 2022-10-11 16:13:48.000000000 +0300 -@@ -210,7 +210,7 @@ - fromBalanceAfter = fromUserState.balance - uint104(amount); - } - _balances[from].balance = fromBalanceAfter; -- if (fromUserState.delegationState != DelegationState.NO_DELEGATION) { +diff -ruN AaveTokenV3.sol AaveTokenV3.sol +--- AaveTokenV3.sol 1970-01-01 01:00:00 ++++ AaveTokenV3.sol 2023-03-28 12:58:17 +@@ -0,0 +1,388 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity ^0.8.0; ++ ++import {VersionedInitializable} from './utils/VersionedInitializable.sol'; ++import {IGovernancePowerDelegationToken} from './interfaces/IGovernancePowerDelegationToken.sol'; ++import {BaseAaveTokenV2} from './BaseAaveTokenV2.sol'; ++ ++contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { ++ mapping(address => address) internal _votingDelegateeV2; ++ mapping(address => address) internal _propositionDelegateeV2; ++ ++ /// @dev we assume that for the governance system 18 decimals of precision is not needed, ++ // by this constant we reduce it by 10, to 8 decimals ++ uint256 public constant POWER_SCALE_FACTOR = 1e10; ++ ++ bytes32 public constant DELEGATE_BY_TYPE_TYPEHASH = ++ keccak256( ++ 'DelegateByType(address delegator,address delegatee,GovernancePowerType delegationType,uint256 nonce,uint256 deadline)' ++ ); ++ bytes32 public constant DELEGATE_TYPEHASH = ++ keccak256('Delegate(address delegator,address delegatee,uint256 nonce,uint256 deadline)'); ++ ++ /** ++ * @dev initializes the contract upon assignment to the InitializableAdminUpgradeabilityProxy ++ */ ++ function initialize() external virtual initializer {} ++ ++ /// @inheritdoc IGovernancePowerDelegationToken ++ function delegateByType(address delegatee, GovernancePowerType delegationType) ++ external ++ virtual ++ override ++ { ++ _delegateByType(msg.sender, delegatee, delegationType); ++ } ++ ++ /// @inheritdoc IGovernancePowerDelegationToken ++ function delegate(address delegatee) external override { ++ _delegateByType(msg.sender, delegatee, GovernancePowerType.VOTING); ++ _delegateByType(msg.sender, delegatee, GovernancePowerType.PROPOSITION); ++ } ++ ++ /// @inheritdoc IGovernancePowerDelegationToken ++ function getDelegateeByType(address delegator, GovernancePowerType delegationType) ++ external ++ view ++ override ++ returns (address) ++ { ++ return _getDelegateeByType(delegator, _balances[delegator], delegationType); ++ } ++ ++ /// @inheritdoc IGovernancePowerDelegationToken ++ function getDelegates(address delegator) external view override returns (address, address) { ++ DelegationAwareBalance memory delegatorBalance = _balances[delegator]; ++ return ( ++ _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.VOTING), ++ _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.PROPOSITION) ++ ); ++ } ++ ++ /// @inheritdoc IGovernancePowerDelegationToken ++ function getPowerCurrent(address user, GovernancePowerType delegationType) ++ public ++ view ++ override ++ returns (uint256) ++ { ++ DelegationAwareBalance memory userState = _balances[user]; ++ uint256 userOwnPower = uint8(userState.delegationState) & (uint8(delegationType) + 1) == 0 ++ ? _balances[user].balance ++ : 0; ++ uint256 userDelegatedPower = _getDelegatedPowerByType(userState, delegationType); ++ return userOwnPower + userDelegatedPower; ++ } ++ ++ /// @inheritdoc IGovernancePowerDelegationToken ++ function getPowersCurrent(address user) external view override returns (uint256, uint256) { ++ return ( ++ getPowerCurrent(user, GovernancePowerType.VOTING), ++ getPowerCurrent(user, GovernancePowerType.PROPOSITION) ++ ); ++ } ++ ++ /// @inheritdoc IGovernancePowerDelegationToken ++ function metaDelegateByType( ++ address delegator, ++ address delegatee, ++ GovernancePowerType delegationType, ++ uint256 deadline, ++ uint8 v, ++ bytes32 r, ++ bytes32 s ++ ) external override { ++ require(delegator != address(0), 'INVALID_OWNER'); ++ //solium-disable-next-line ++ require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); ++ uint256 currentValidNonce = _nonces[delegator]; ++ bytes32 digest = keccak256( ++ abi.encodePacked( ++ '\x19\x01', ++ DOMAIN_SEPARATOR, ++ keccak256( ++ abi.encode( ++ DELEGATE_BY_TYPE_TYPEHASH, ++ delegator, ++ delegatee, ++ delegationType, ++ currentValidNonce, ++ deadline ++ ) ++ ) ++ ) ++ ); ++ ++ require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); ++ unchecked { ++ // Does not make sense to check because it's not realistic to reach uint256.max in nonce ++ _nonces[delegator] = currentValidNonce + 1; ++ } ++ _delegateByType(delegator, delegatee, delegationType); ++ } ++ ++ /// @inheritdoc IGovernancePowerDelegationToken ++ function metaDelegate( ++ address delegator, ++ address delegatee, ++ uint256 deadline, ++ uint8 v, ++ bytes32 r, ++ bytes32 s ++ ) external override { ++ require(delegator != address(0), 'INVALID_OWNER'); ++ //solium-disable-next-line ++ require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); ++ uint256 currentValidNonce = _nonces[delegator]; ++ bytes32 digest = keccak256( ++ abi.encodePacked( ++ '\x19\x01', ++ DOMAIN_SEPARATOR, ++ keccak256(abi.encode(DELEGATE_TYPEHASH, delegator, delegatee, currentValidNonce, deadline)) ++ ) ++ ); ++ ++ require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); ++ unchecked { ++ // does not make sense to check because it's not realistic to reach uint256.max in nonce ++ _nonces[delegator] = currentValidNonce + 1; ++ } ++ _delegateByType(delegator, delegatee, GovernancePowerType.VOTING); ++ _delegateByType(delegator, delegatee, GovernancePowerType.PROPOSITION); ++ } ++ ++ /** ++ * @dev Modifies the delegated power of a `delegatee` account by type (VOTING, PROPOSITION). ++ * Passing the impact on the delegation of `delegatee` account before and after to reduce conditionals and not lose ++ * any precision. ++ * @param impactOnDelegationBefore how much impact a balance of another account had over the delegation of a `delegatee` ++ * before an action. ++ * For example, if the action is a delegation from one account to another, the impact before the action will be 0. ++ * @param impactOnDelegationAfter how much impact a balance of another account will have over the delegation of a `delegatee` ++ * after an action. ++ * For example, if the action is a delegation from one account to another, the impact after the action will be the whole balance ++ * of the account changing the delegatee. ++ * @param delegatee the user whom delegated governance power will be changed ++ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) ++ **/ ++ function _governancePowerTransferByType( ++ uint104 impactOnDelegationBefore, ++ uint104 impactOnDelegationAfter, ++ address delegatee, ++ GovernancePowerType delegationType ++ ) internal { ++ if (delegatee == address(0)) return; ++ if (impactOnDelegationBefore == impactOnDelegationAfter) return; ++ ++ // To make delegated balance fit into uint72 we're decreasing precision of delegated balance by POWER_SCALE_FACTOR ++ uint72 impactOnDelegationBefore72 = uint72(impactOnDelegationBefore / POWER_SCALE_FACTOR); ++ uint72 impactOnDelegationAfter72 = uint72(impactOnDelegationAfter / POWER_SCALE_FACTOR); ++ ++ if (delegationType == GovernancePowerType.VOTING) { ++ _balances[delegatee].delegatedVotingBalance = ++ _balances[delegatee].delegatedVotingBalance - ++ impactOnDelegationBefore72 + ++ impactOnDelegationAfter72; ++ } else { ++ _balances[delegatee].delegatedPropositionBalance = ++ _balances[delegatee].delegatedPropositionBalance - ++ impactOnDelegationBefore72 + ++ impactOnDelegationAfter72; ++ } ++ } ++ ++ /** ++ * @dev performs all state changes related to balance transfer and corresponding delegation changes ++ * @param from token sender ++ * @param to token recipient ++ * @param amount amount of tokens sent ++ **/ ++ function _transferWithDelegation( ++ address from, ++ address to, ++ uint256 amount ++ ) internal override { ++ if (from == to) { ++ return; ++ } ++ ++ if (from != address(0)) { ++ DelegationAwareBalance memory fromUserState = _balances[from]; ++ require(fromUserState.balance >= amount, 'ERC20: transfer amount exceeds balance'); ++ ++ uint104 fromBalanceAfter; ++ unchecked { ++ fromBalanceAfter = fromUserState.balance - uint104(amount); ++ } ++ _balances[from].balance = fromBalanceAfter; + if (fromUserState.delegationState != uint8(DelegationState.NO_DELEGATION)) { - _governancePowerTransferByType( - fromUserState.balance, - fromBalanceAfter, -@@ -232,7 +232,7 @@ - toUserState.balance = toBalanceBefore + uint104(amount); - _balances[to] = toUserState; - -- if (toUserState.delegationState != DelegationState.NO_DELEGATION) { ++ _governancePowerTransferByType( ++ fromUserState.balance, ++ fromBalanceAfter, ++ _getDelegateeByType(from, fromUserState, GovernancePowerType.VOTING), ++ GovernancePowerType.VOTING ++ ); ++ _governancePowerTransferByType( ++ fromUserState.balance, ++ fromBalanceAfter, ++ _getDelegateeByType(from, fromUserState, GovernancePowerType.PROPOSITION), ++ GovernancePowerType.PROPOSITION ++ ); ++ } ++ } ++ ++ if (to != address(0)) { ++ DelegationAwareBalance memory toUserState = _balances[to]; ++ uint104 toBalanceBefore = toUserState.balance; ++ toUserState.balance = toBalanceBefore + uint104(amount); ++ _balances[to] = toUserState; ++ + if (toUserState.delegationState != uint8(DelegationState.NO_DELEGATION)) { - _governancePowerTransferByType( - toBalanceBefore, - toUserState.balance, -@@ -288,7 +288,7 @@ - : address(0); - } - return -- userState.delegationState >= DelegationState.PROPOSITION_DELEGATED ++ _governancePowerTransferByType( ++ toBalanceBefore, ++ toUserState.balance, ++ _getDelegateeByType(to, toUserState, GovernancePowerType.VOTING), ++ GovernancePowerType.VOTING ++ ); ++ _governancePowerTransferByType( ++ toBalanceBefore, ++ toUserState.balance, ++ _getDelegateeByType(to, toUserState, GovernancePowerType.PROPOSITION), ++ GovernancePowerType.PROPOSITION ++ ); ++ } ++ } ++ } ++ ++ /** ++ * @dev Extracts from state and returns delegated governance power (Voting, Proposition) ++ * @param userState the current state of a user ++ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) ++ **/ ++ function _getDelegatedPowerByType( ++ DelegationAwareBalance memory userState, ++ GovernancePowerType delegationType ++ ) internal pure returns (uint256) { ++ return ++ POWER_SCALE_FACTOR * ++ ( ++ delegationType == GovernancePowerType.VOTING ++ ? userState.delegatedVotingBalance ++ : userState.delegatedPropositionBalance ++ ); ++ } ++ ++ /** ++ * @dev Extracts from state and returns the delegatee of a delegator by type of governance power (Voting, Proposition) ++ * - If the delegator doesn't have any delegatee, returns address(0) ++ * @param delegator delegator ++ * @param userState the current state of a user ++ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) ++ **/ ++ function _getDelegateeByType( ++ address delegator, ++ DelegationAwareBalance memory userState, ++ GovernancePowerType delegationType ++ ) internal view returns (address) { ++ if (delegationType == GovernancePowerType.VOTING) { ++ return ++ /// With the & operation, we cover both VOTING_DELEGATED delegation and FULL_POWER_DELEGATED ++ /// as VOTING_DELEGATED is equivalent to 01 in binary and FULL_POWER_DELEGATED is equivalent to 11 ++ (uint8(userState.delegationState) & uint8(DelegationState.VOTING_DELEGATED)) != 0 ++ ? _votingDelegateeV2[delegator] ++ : address(0); ++ } ++ return + userState.delegationState >= uint8(DelegationState.PROPOSITION_DELEGATED) - ? _propositionDelegateeV2[delegator] - : address(0); - } -@@ -325,16 +325,12 @@ - ) internal pure returns (DelegationAwareBalance memory) { - if (willDelegate) { - // Because GovernancePowerType starts from 0, we should add 1 first, then we apply bitwise OR -- userState.delegationState = DelegationState( -- uint8(userState.delegationState) | (uint8(delegationType) + 1) -- ); ++ ? _propositionDelegateeV2[delegator] ++ : address(0); ++ } ++ ++ /** ++ * @dev Changes user's delegatee address by type of governance power (Voting, Proposition) ++ * @param delegator delegator ++ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) ++ * @param _newDelegatee the new delegatee ++ **/ ++ function _updateDelegateeByType( ++ address delegator, ++ GovernancePowerType delegationType, ++ address _newDelegatee ++ ) internal { ++ address newDelegatee = _newDelegatee == delegator ? address(0) : _newDelegatee; ++ if (delegationType == GovernancePowerType.VOTING) { ++ _votingDelegateeV2[delegator] = newDelegatee; ++ } else { ++ _propositionDelegateeV2[delegator] = newDelegatee; ++ } ++ } ++ ++ /** ++ * @dev Updates the specific flag which signaling about existence of delegation of governance power (Voting, Proposition) ++ * @param userState a user state to change ++ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) ++ * @param willDelegate next state of delegation ++ **/ ++ function _updateDelegationFlagByType( ++ DelegationAwareBalance memory userState, ++ GovernancePowerType delegationType, ++ bool willDelegate ++ ) internal pure returns (DelegationAwareBalance memory) { ++ if (willDelegate) { ++ // Because GovernancePowerType starts from 0, we should add 1 first, then we apply bitwise OR + userState.delegationState = userState.delegationState | (uint8(delegationType) + 1); - } else { - // First bitwise NEGATION, ie was 01, after XOR with 11 will be 10, - // then bitwise AND, which means it will keep only another delegation type if it exists -- userState.delegationState = DelegationState( -- uint8(userState.delegationState) & -- ((uint8(delegationType) + 1) ^ uint8(DelegationState.FULL_POWER_DELEGATED)) -- ); ++ } else { ++ // First bitwise NEGATION, ie was 01, after XOR with 11 will be 10, ++ // then bitwise AND, which means it will keep only another delegation type if it exists + userState.delegationState = userState.delegationState & + ((uint8(delegationType) + 1) ^ uint8(DelegationState.FULL_POWER_DELEGATED)); - } - return userState; - } -diff -uN BaseAaveToken.sol /BaseAaveToken.sol ---- BaseAaveToken.sol 2022-10-11 17:36:35.000000000 +0300 -+++ /BaseAaveToken.sol 2022-10-11 16:20:40.000000000 +0300 -@@ -1,9 +1,9 @@ - // SPDX-License-Identifier: MIT - pragma solidity ^0.8.0; - --import {Context} from '../lib/openzeppelin-contracts/contracts/utils/Context.sol'; --import {IERC20} from '../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; --import {IERC20Metadata} from '../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; ++ } ++ return userState; ++ } ++ ++ /** ++ * @dev This is the equivalent of an ERC20 transfer(), but for a power type: an atomic transfer of a balance (power). ++ * When needed, it decreases the power of the `delegator` and when needed, it increases the power of the `delegatee` ++ * @param delegator delegator ++ * @param _delegatee the user which delegated power will change ++ * @param delegationType the type of delegation (VOTING, PROPOSITION) ++ **/ ++ function _delegateByType( ++ address delegator, ++ address _delegatee, ++ GovernancePowerType delegationType ++ ) internal { ++ // Here we unify the property that delegating power to address(0) == delegating power to yourself == no delegation ++ // So from now on, not being delegating is (exclusively) that delegatee == address(0) ++ address delegatee = _delegatee == delegator ? address(0) : _delegatee; ++ ++ // We read the whole struct before validating delegatee, because in the optimistic case ++ // (_delegatee != currentDelegatee) we will reuse userState in the rest of the function ++ DelegationAwareBalance memory delegatorState = _balances[delegator]; ++ address currentDelegatee = _getDelegateeByType(delegator, delegatorState, delegationType); ++ if (delegatee == currentDelegatee) return; ++ ++ bool delegatingNow = currentDelegatee != address(0); ++ bool willDelegateAfter = delegatee != address(0); ++ ++ if (delegatingNow) { ++ _governancePowerTransferByType(delegatorState.balance, 0, currentDelegatee, delegationType); ++ } ++ ++ if (willDelegateAfter) { ++ _governancePowerTransferByType(0, delegatorState.balance, delegatee, delegationType); ++ } ++ ++ _updateDelegateeByType(delegator, delegationType, delegatee); ++ ++ if (willDelegateAfter != delegatingNow) { ++ _balances[delegator] = _updateDelegationFlagByType( ++ delegatorState, ++ delegationType, ++ willDelegateAfter ++ ); ++ } ++ ++ emit DelegateChanged(delegator, delegatee, delegationType); ++ } ++} +\ No newline at end of file +diff -ruN AaveTokenV3.sol.orig AaveTokenV3.sol.orig +--- AaveTokenV3.sol.orig 1970-01-01 01:00:00 ++++ AaveTokenV3.sol.orig 2023-03-28 12:58:17 +@@ -0,0 +1,392 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity ^0.8.0; ++ ++import {VersionedInitializable} from './utils/VersionedInitializable.sol'; ++import {IGovernancePowerDelegationToken} from './interfaces/IGovernancePowerDelegationToken.sol'; ++import {BaseAaveTokenV2} from './BaseAaveTokenV2.sol'; ++ ++contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { ++ mapping(address => address) internal _votingDelegateeV2; ++ mapping(address => address) internal _propositionDelegateeV2; ++ ++ /// @dev we assume that for the governance system 18 decimals of precision is not needed, ++ // by this constant we reduce it by 10, to 8 decimals ++ uint256 public constant POWER_SCALE_FACTOR = 1e10; ++ ++ bytes32 public constant DELEGATE_BY_TYPE_TYPEHASH = ++ keccak256( ++ 'DelegateByType(address delegator,address delegatee,GovernancePowerType delegationType,uint256 nonce,uint256 deadline)' ++ ); ++ bytes32 public constant DELEGATE_TYPEHASH = ++ keccak256('Delegate(address delegator,address delegatee,uint256 nonce,uint256 deadline)'); ++ ++ /** ++ * @dev initializes the contract upon assignment to the InitializableAdminUpgradeabilityProxy ++ */ ++ function initialize() external virtual initializer {} ++ ++ /// @inheritdoc IGovernancePowerDelegationToken ++ function delegateByType(address delegatee, GovernancePowerType delegationType) ++ external ++ virtual ++ override ++ { ++ _delegateByType(msg.sender, delegatee, delegationType); ++ } ++ ++ /// @inheritdoc IGovernancePowerDelegationToken ++ function delegate(address delegatee) external override { ++ _delegateByType(msg.sender, delegatee, GovernancePowerType.VOTING); ++ _delegateByType(msg.sender, delegatee, GovernancePowerType.PROPOSITION); ++ } ++ ++ /// @inheritdoc IGovernancePowerDelegationToken ++ function getDelegateeByType(address delegator, GovernancePowerType delegationType) ++ external ++ view ++ override ++ returns (address) ++ { ++ return _getDelegateeByType(delegator, _balances[delegator], delegationType); ++ } ++ ++ /// @inheritdoc IGovernancePowerDelegationToken ++ function getDelegates(address delegator) external view override returns (address, address) { ++ DelegationAwareBalance memory delegatorBalance = _balances[delegator]; ++ return ( ++ _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.VOTING), ++ _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.PROPOSITION) ++ ); ++ } ++ ++ /// @inheritdoc IGovernancePowerDelegationToken ++ function getPowerCurrent(address user, GovernancePowerType delegationType) ++ public ++ view ++ override ++ returns (uint256) ++ { ++ DelegationAwareBalance memory userState = _balances[user]; ++ uint256 userOwnPower = uint8(userState.delegationState) & (uint8(delegationType) + 1) == 0 ++ ? _balances[user].balance ++ : 0; ++ uint256 userDelegatedPower = _getDelegatedPowerByType(userState, delegationType); ++ return userOwnPower + userDelegatedPower; ++ } ++ ++ /// @inheritdoc IGovernancePowerDelegationToken ++ function getPowersCurrent(address user) external view override returns (uint256, uint256) { ++ return ( ++ getPowerCurrent(user, GovernancePowerType.VOTING), ++ getPowerCurrent(user, GovernancePowerType.PROPOSITION) ++ ); ++ } ++ ++ /// @inheritdoc IGovernancePowerDelegationToken ++ function metaDelegateByType( ++ address delegator, ++ address delegatee, ++ GovernancePowerType delegationType, ++ uint256 deadline, ++ uint8 v, ++ bytes32 r, ++ bytes32 s ++ ) external override { ++ require(delegator != address(0), 'INVALID_OWNER'); ++ //solium-disable-next-line ++ require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); ++ uint256 currentValidNonce = _nonces[delegator]; ++ bytes32 digest = keccak256( ++ abi.encodePacked( ++ '\x19\x01', ++ DOMAIN_SEPARATOR, ++ keccak256( ++ abi.encode( ++ DELEGATE_BY_TYPE_TYPEHASH, ++ delegator, ++ delegatee, ++ delegationType, ++ currentValidNonce, ++ deadline ++ ) ++ ) ++ ) ++ ); ++ ++ require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); ++ unchecked { ++ // Does not make sense to check because it's not realistic to reach uint256.max in nonce ++ _nonces[delegator] = currentValidNonce + 1; ++ } ++ _delegateByType(delegator, delegatee, delegationType); ++ } ++ ++ /// @inheritdoc IGovernancePowerDelegationToken ++ function metaDelegate( ++ address delegator, ++ address delegatee, ++ uint256 deadline, ++ uint8 v, ++ bytes32 r, ++ bytes32 s ++ ) external override { ++ require(delegator != address(0), 'INVALID_OWNER'); ++ //solium-disable-next-line ++ require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); ++ uint256 currentValidNonce = _nonces[delegator]; ++ bytes32 digest = keccak256( ++ abi.encodePacked( ++ '\x19\x01', ++ DOMAIN_SEPARATOR, ++ keccak256(abi.encode(DELEGATE_TYPEHASH, delegator, delegatee, currentValidNonce, deadline)) ++ ) ++ ); ++ ++ require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); ++ unchecked { ++ // does not make sense to check because it's not realistic to reach uint256.max in nonce ++ _nonces[delegator] = currentValidNonce + 1; ++ } ++ _delegateByType(delegator, delegatee, GovernancePowerType.VOTING); ++ _delegateByType(delegator, delegatee, GovernancePowerType.PROPOSITION); ++ } ++ ++ /** ++ * @dev Modifies the delegated power of a `delegatee` account by type (VOTING, PROPOSITION). ++ * Passing the impact on the delegation of `delegatee` account before and after to reduce conditionals and not lose ++ * any precision. ++ * @param impactOnDelegationBefore how much impact a balance of another account had over the delegation of a `delegatee` ++ * before an action. ++ * For example, if the action is a delegation from one account to another, the impact before the action will be 0. ++ * @param impactOnDelegationAfter how much impact a balance of another account will have over the delegation of a `delegatee` ++ * after an action. ++ * For example, if the action is a delegation from one account to another, the impact after the action will be the whole balance ++ * of the account changing the delegatee. ++ * @param delegatee the user whom delegated governance power will be changed ++ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) ++ **/ ++ function _governancePowerTransferByType( ++ uint104 impactOnDelegationBefore, ++ uint104 impactOnDelegationAfter, ++ address delegatee, ++ GovernancePowerType delegationType ++ ) internal { ++ if (delegatee == address(0)) return; ++ if (impactOnDelegationBefore == impactOnDelegationAfter) return; ++ ++ // To make delegated balance fit into uint72 we're decreasing precision of delegated balance by POWER_SCALE_FACTOR ++ uint72 impactOnDelegationBefore72 = uint72(impactOnDelegationBefore / POWER_SCALE_FACTOR); ++ uint72 impactOnDelegationAfter72 = uint72(impactOnDelegationAfter / POWER_SCALE_FACTOR); ++ ++ if (delegationType == GovernancePowerType.VOTING) { ++ _balances[delegatee].delegatedVotingBalance = ++ _balances[delegatee].delegatedVotingBalance - ++ impactOnDelegationBefore72 + ++ impactOnDelegationAfter72; ++ } else { ++ _balances[delegatee].delegatedPropositionBalance = ++ _balances[delegatee].delegatedPropositionBalance - ++ impactOnDelegationBefore72 + ++ impactOnDelegationAfter72; ++ } ++ } ++ ++ /** ++ * @dev performs all state changes related to balance transfer and corresponding delegation changes ++ * @param from token sender ++ * @param to token recipient ++ * @param amount amount of tokens sent ++ **/ ++ function _transferWithDelegation( ++ address from, ++ address to, ++ uint256 amount ++ ) internal override { ++ if (from == to) { ++ return; ++ } ++ ++ if (from != address(0)) { ++ DelegationAwareBalance memory fromUserState = _balances[from]; ++ require(fromUserState.balance >= amount, 'ERC20: transfer amount exceeds balance'); ++ ++ uint104 fromBalanceAfter; ++ unchecked { ++ fromBalanceAfter = fromUserState.balance - uint104(amount); ++ } ++ _balances[from].balance = fromBalanceAfter; ++ if (fromUserState.delegationState != DelegationState.NO_DELEGATION) { ++ _governancePowerTransferByType( ++ fromUserState.balance, ++ fromBalanceAfter, ++ _getDelegateeByType(from, fromUserState, GovernancePowerType.VOTING), ++ GovernancePowerType.VOTING ++ ); ++ _governancePowerTransferByType( ++ fromUserState.balance, ++ fromBalanceAfter, ++ _getDelegateeByType(from, fromUserState, GovernancePowerType.PROPOSITION), ++ GovernancePowerType.PROPOSITION ++ ); ++ } ++ } ++ ++ if (to != address(0)) { ++ DelegationAwareBalance memory toUserState = _balances[to]; ++ uint104 toBalanceBefore = toUserState.balance; ++ toUserState.balance = toBalanceBefore + uint104(amount); ++ _balances[to] = toUserState; ++ ++ if (toUserState.delegationState != DelegationState.NO_DELEGATION) { ++ _governancePowerTransferByType( ++ toBalanceBefore, ++ toUserState.balance, ++ _getDelegateeByType(to, toUserState, GovernancePowerType.VOTING), ++ GovernancePowerType.VOTING ++ ); ++ _governancePowerTransferByType( ++ toBalanceBefore, ++ toUserState.balance, ++ _getDelegateeByType(to, toUserState, GovernancePowerType.PROPOSITION), ++ GovernancePowerType.PROPOSITION ++ ); ++ } ++ } ++ } ++ ++ /** ++ * @dev Extracts from state and returns delegated governance power (Voting, Proposition) ++ * @param userState the current state of a user ++ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) ++ **/ ++ function _getDelegatedPowerByType( ++ DelegationAwareBalance memory userState, ++ GovernancePowerType delegationType ++ ) internal pure returns (uint256) { ++ return ++ POWER_SCALE_FACTOR * ++ ( ++ delegationType == GovernancePowerType.VOTING ++ ? userState.delegatedVotingBalance ++ : userState.delegatedPropositionBalance ++ ); ++ } ++ ++ /** ++ * @dev Extracts from state and returns the delegatee of a delegator by type of governance power (Voting, Proposition) ++ * - If the delegator doesn't have any delegatee, returns address(0) ++ * @param delegator delegator ++ * @param userState the current state of a user ++ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) ++ **/ ++ function _getDelegateeByType( ++ address delegator, ++ DelegationAwareBalance memory userState, ++ GovernancePowerType delegationType ++ ) internal view returns (address) { ++ if (delegationType == GovernancePowerType.VOTING) { ++ return ++ /// With the & operation, we cover both VOTING_DELEGATED delegation and FULL_POWER_DELEGATED ++ /// as VOTING_DELEGATED is equivalent to 01 in binary and FULL_POWER_DELEGATED is equivalent to 11 ++ (uint8(userState.delegationState) & uint8(DelegationState.VOTING_DELEGATED)) != 0 ++ ? _votingDelegateeV2[delegator] ++ : address(0); ++ } ++ return ++ userState.delegationState >= DelegationState.PROPOSITION_DELEGATED ++ ? _propositionDelegateeV2[delegator] ++ : address(0); ++ } ++ ++ /** ++ * @dev Changes user's delegatee address by type of governance power (Voting, Proposition) ++ * @param delegator delegator ++ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) ++ * @param _newDelegatee the new delegatee ++ **/ ++ function _updateDelegateeByType( ++ address delegator, ++ GovernancePowerType delegationType, ++ address _newDelegatee ++ ) internal { ++ address newDelegatee = _newDelegatee == delegator ? address(0) : _newDelegatee; ++ if (delegationType == GovernancePowerType.VOTING) { ++ _votingDelegateeV2[delegator] = newDelegatee; ++ } else { ++ _propositionDelegateeV2[delegator] = newDelegatee; ++ } ++ } ++ ++ /** ++ * @dev Updates the specific flag which signaling about existence of delegation of governance power (Voting, Proposition) ++ * @param userState a user state to change ++ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) ++ * @param willDelegate next state of delegation ++ **/ ++ function _updateDelegationFlagByType( ++ DelegationAwareBalance memory userState, ++ GovernancePowerType delegationType, ++ bool willDelegate ++ ) internal pure returns (DelegationAwareBalance memory) { ++ if (willDelegate) { ++ // Because GovernancePowerType starts from 0, we should add 1 first, then we apply bitwise OR ++ userState.delegationState = DelegationState( ++ uint8(userState.delegationState) | (uint8(delegationType) + 1) ++ ); ++ } else { ++ // First bitwise NEGATION, ie was 01, after XOR with 11 will be 10, ++ // then bitwise AND, which means it will keep only another delegation type if it exists ++ userState.delegationState = DelegationState( ++ uint8(userState.delegationState) & ++ ((uint8(delegationType) + 1) ^ uint8(DelegationState.FULL_POWER_DELEGATED)) ++ ); ++ } ++ return userState; ++ } ++ ++ /** ++ * @dev This is the equivalent of an ERC20 transfer(), but for a power type: an atomic transfer of a balance (power). ++ * When needed, it decreases the power of the `delegator` and when needed, it increases the power of the `delegatee` ++ * @param delegator delegator ++ * @param _delegatee the user which delegated power will change ++ * @param delegationType the type of delegation (VOTING, PROPOSITION) ++ **/ ++ function _delegateByType( ++ address delegator, ++ address _delegatee, ++ GovernancePowerType delegationType ++ ) internal { ++ // Here we unify the property that delegating power to address(0) == delegating power to yourself == no delegation ++ // So from now on, not being delegating is (exclusively) that delegatee == address(0) ++ address delegatee = _delegatee == delegator ? address(0) : _delegatee; ++ ++ // We read the whole struct before validating delegatee, because in the optimistic case ++ // (_delegatee != currentDelegatee) we will reuse userState in the rest of the function ++ DelegationAwareBalance memory delegatorState = _balances[delegator]; ++ address currentDelegatee = _getDelegateeByType(delegator, delegatorState, delegationType); ++ if (delegatee == currentDelegatee) return; ++ ++ bool delegatingNow = currentDelegatee != address(0); ++ bool willDelegateAfter = delegatee != address(0); ++ ++ if (delegatingNow) { ++ _governancePowerTransferByType(delegatorState.balance, 0, currentDelegatee, delegationType); ++ } ++ ++ if (willDelegateAfter) { ++ _governancePowerTransferByType(0, delegatorState.balance, delegatee, delegationType); ++ } ++ ++ _updateDelegateeByType(delegator, delegationType, delegatee); ++ ++ if (willDelegateAfter != delegatingNow) { ++ _balances[delegator] = _updateDelegationFlagByType( ++ delegatorState, ++ delegationType, ++ willDelegateAfter ++ ); ++ } ++ ++ emit DelegateChanged(delegator, delegatee, delegationType); ++ } ++} +\ No newline at end of file +diff -ruN BaseAaveToken.sol BaseAaveToken.sol +--- BaseAaveToken.sol 1970-01-01 01:00:00 ++++ BaseAaveToken.sol 2023-03-28 12:58:17 +@@ -0,0 +1,162 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity ^0.8.0; ++ +import {Context} from './lib/openzeppelin-contracts/contracts/utils/Context.sol'; +import {IERC20} from './lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; +import {IERC20Metadata} from './lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; - - // Inspired by OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) - abstract contract BaseAaveToken is Context, IERC20Metadata { ++ ++// Inspired by OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) ++abstract contract BaseAaveToken is Context, IERC20Metadata { ++ enum DelegationState { ++ NO_DELEGATION, ++ VOTING_DELEGATED, ++ PROPOSITION_DELEGATED, ++ FULL_POWER_DELEGATED ++ } ++ ++ // reorder fields to make hooks syntax simpler ++ struct DelegationAwareBalance { ++ uint104 balance; ++ uint72 delegatedPropositionBalance; ++ uint72 delegatedVotingBalance; ++ uint8 delegationState; // refactored from enum ++ } ++ ++ mapping(address => DelegationAwareBalance) internal _balances; ++ ++ mapping(address => mapping(address => uint256)) internal _allowances; ++ ++ uint256 internal _totalSupply; ++ ++ string internal _name; ++ string internal _symbol; ++ ++ // @dev DEPRECATED ++ // kept for backwards compatibility with old storage layout ++ uint8 private ______DEPRECATED_OLD_ERC20_DECIMALS; ++ ++ /** ++ * @dev Returns the name of the token. ++ */ ++ function name() public view virtual override returns (string memory) { ++ return _name; ++ } ++ ++ /** ++ * @dev Returns the symbol of the token, usually a shorter version of the ++ * name. ++ */ ++ function symbol() public view virtual override returns (string memory) { ++ return _symbol; ++ } ++ ++ function decimals() public view virtual override returns (uint8) { ++ return 18; ++ } ++ ++ function totalSupply() public view virtual override returns (uint256) { ++ return _totalSupply; ++ } ++ ++ function balanceOf(address account) public view virtual override returns (uint256) { ++ return _balances[account].balance; ++ } ++ ++ function transfer(address to, uint256 amount) public virtual override returns (bool) { ++ address owner = _msgSender(); ++ _transfer(owner, to, amount); ++ return true; ++ } ++ ++ function allowance(address owner, address spender) ++ public ++ view ++ virtual ++ override ++ returns (uint256) ++ { ++ return _allowances[owner][spender]; ++ } ++ ++ function approve(address spender, uint256 amount) public virtual override returns (bool) { ++ address owner = _msgSender(); ++ _approve(owner, spender, amount); ++ return true; ++ } ++ ++ function transferFrom( ++ address from, ++ address to, ++ uint256 amount ++ ) public virtual override returns (bool) { ++ address spender = _msgSender(); ++ _spendAllowance(from, spender, amount); ++ _transfer(from, to, amount); ++ return true; ++ } ++ ++ function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { ++ address owner = _msgSender(); ++ _approve(owner, spender, _allowances[owner][spender] + addedValue); ++ return true; ++ } ++ ++ function decreaseAllowance(address spender, uint256 subtractedValue) ++ public ++ virtual ++ returns (bool) ++ { ++ address owner = _msgSender(); ++ uint256 currentAllowance = _allowances[owner][spender]; ++ require(currentAllowance >= subtractedValue, 'ERC20: decreased allowance below zero'); ++ unchecked { ++ _approve(owner, spender, currentAllowance - subtractedValue); ++ } ++ ++ return true; ++ } ++ ++ function _transfer( ++ address from, ++ address to, ++ uint256 amount ++ ) internal virtual { ++ require(from != address(0), 'ERC20: transfer from the zero address'); ++ require(to != address(0), 'ERC20: transfer to the zero address'); ++ ++ _transferWithDelegation(from, to, amount); ++ emit Transfer(from, to, amount); ++ } ++ ++ function _approve( ++ address owner, ++ address spender, ++ uint256 amount ++ ) internal virtual { ++ require(owner != address(0), 'ERC20: approve from the zero address'); ++ require(spender != address(0), 'ERC20: approve to the zero address'); ++ ++ _allowances[owner][spender] = amount; ++ emit Approval(owner, spender, amount); ++ } ++ ++ function _spendAllowance( ++ address owner, ++ address spender, ++ uint256 amount ++ ) internal virtual { ++ uint256 currentAllowance = allowance(owner, spender); ++ if (currentAllowance != type(uint256).max) { ++ require(currentAllowance >= amount, 'ERC20: insufficient allowance'); ++ unchecked { ++ _approve(owner, spender, currentAllowance - amount); ++ } ++ } ++ } ++ ++ function _transferWithDelegation( ++ address from, ++ address to, ++ uint256 amount ++ ) internal virtual {} ++} +diff -ruN BaseAaveTokenV2.sol BaseAaveTokenV2.sol +--- BaseAaveTokenV2.sol 1970-01-01 01:00:00 ++++ BaseAaveTokenV2.sol 2023-03-28 12:58:17 +@@ -0,0 +1,77 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity ^0.8.0; ++ ++import {VersionedInitializable} from './utils/VersionedInitializable.sol'; ++import {BaseAaveToken} from './BaseAaveToken.sol'; ++ ++abstract contract BaseAaveTokenV2 is BaseAaveToken, VersionedInitializable { ++ /// @dev owner => next valid nonce to submit with permit() ++ mapping(address => uint256) public _nonces; ++ ++ ///////// @dev DEPRECATED from AaveToken v1 ////////////////////////// ++ //////// kept for backwards compatibility with old storage layout //// ++ uint256[3] private ______DEPRECATED_FROM_AAVE_V1; ++ ///////// @dev END OF DEPRECATED from AaveToken v1 ////////////////////////// ++ ++ bytes32 public DOMAIN_SEPARATOR; ++ ++ ///////// @dev DEPRECATED from AaveToken v2 ////////////////////////// ++ //////// kept for backwards compatibility with old storage layout //// ++ uint256[4] private ______DEPRECATED_FROM_AAVE_V2; ++ ///////// @dev END OF DEPRECATED from AaveToken v2 ////////////////////////// ++ ++ bytes public constant EIP712_REVISION = bytes('1'); ++ bytes32 internal constant EIP712_DOMAIN = ++ keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'); ++ bytes32 public constant PERMIT_TYPEHASH = ++ keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'); ++ ++ uint256 public constant REVISION = 3; ++ ++ /** ++ * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md ++ * @param owner the owner of the funds ++ * @param spender the spender ++ * @param value the amount ++ * @param deadline the deadline timestamp, type(uint256).max for no deadline ++ * @param v signature param ++ * @param s signature param ++ * @param r signature param ++ */ ++ ++ function permit( ++ address owner, ++ address spender, ++ uint256 value, ++ uint256 deadline, ++ uint8 v, ++ bytes32 r, ++ bytes32 s ++ ) external { ++ require(owner != address(0), 'INVALID_OWNER'); ++ //solium-disable-next-line ++ require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); ++ uint256 currentValidNonce = _nonces[owner]; ++ bytes32 digest = keccak256( ++ abi.encodePacked( ++ '\x19\x01', ++ DOMAIN_SEPARATOR, ++ keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline)) ++ ) ++ ); ++ ++ require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); ++ unchecked { ++ // does not make sense to check because it's not realistic to reach uint256.max in nonce ++ _nonces[owner] = currentValidNonce + 1; ++ } ++ _approve(owner, spender, value); ++ } ++ ++ /** ++ * @dev returns the revision of the implementation contract ++ */ ++ function getRevision() internal pure override returns (uint256) { ++ return REVISION; ++ } ++} +diff -ruN BaseAaveTokenV3.sol BaseAaveTokenV3.sol +--- BaseAaveTokenV3.sol 1970-01-01 01:00:00 ++++ BaseAaveTokenV3.sol 2023-03-28 12:58:17 +@@ -0,0 +1,84 @@ ++// SPDX-License-Identifier: MIT ++ ++pragma solidity ^0.8.0; ++ ++import {VersionedInitializable} from './utils/VersionedInitializable.sol'; ++ ++import {BaseAaveToken} from './BaseAaveToken.sol'; ++ ++abstract contract BaseAaveTokenV2 is BaseAaveToken, VersionedInitializable { ++ /// @dev owner => next valid nonce to submit with permit() ++ mapping(address => uint256) public _nonces; ++ ++ ///////// @dev DEPRECATED from AaveToken v1 ////////////////////////// ++ //////// kept for backwards compatibility with old storage layout //// ++ uint256[3] private ______DEPRECATED_FROM_AAVE_V1; ++ ///////// @dev END OF DEPRECATED from AaveToken v1 ////////////////////////// ++ ++ bytes32 public DOMAIN_SEPARATOR; ++ ++ ///////// @dev DEPRECATED from AaveToken v2 ////////////////////////// ++ //////// kept for backwards compatibility with old storage layout //// ++ uint256[4] private ______DEPRECATED_FROM_AAVE_V2; ++ ///////// @dev END OF DEPRECATED from AaveToken v2 ////////////////////////// ++ ++ bytes public constant EIP712_REVISION = bytes('1'); ++ bytes32 internal constant EIP712_DOMAIN = ++ keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'); ++ bytes32 public constant PERMIT_TYPEHASH = ++ keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'); ++ ++ uint256 public constant REVISION = 3; // TODO: CHECK, but most probably was 2 before ++ ++ /** ++ * @dev initializes the contract upon assignment to the InitializableAdminUpgradeabilityProxy ++ */ ++ function initialize() external initializer {} ++ ++ /** ++ * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md ++ * @param owner the owner of the funds ++ * @param spender the spender ++ * @param value the amount ++ * @param deadline the deadline timestamp, type(uint256).max for no deadline ++ * @param v signature param ++ * @param s signature param ++ * @param r signature param ++ */ ++ ++ function permit( ++ address owner, ++ address spender, ++ uint256 value, ++ uint256 deadline, ++ uint8 v, ++ bytes32 r, ++ bytes32 s ++ ) external { ++ require(owner != address(0), 'INVALID_OWNER'); ++ //solium-disable-next-line ++ require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); ++ uint256 currentValidNonce = _nonces[owner]; ++ bytes32 digest = keccak256( ++ abi.encodePacked( ++ '\x19\x01', ++ DOMAIN_SEPARATOR, ++ keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline)) ++ ) ++ ); ++ ++ require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); ++ unchecked { ++ // does not make sense to check because it's not realistic to reach uint256.max in nonce ++ _nonces[owner] = currentValidNonce + 1; ++ } ++ _approve(owner, spender, value); ++ } ++ ++ /** ++ * @dev returns the revision of the implementation contract ++ */ ++ function getRevision() internal pure override returns (uint256) { ++ return REVISION; ++ } ++} +diff -ruN interfaces/IGovernancePowerDelegationToken.sol interfaces/IGovernancePowerDelegationToken.sol +--- interfaces/IGovernancePowerDelegationToken.sol 1970-01-01 01:00:00 ++++ interfaces/IGovernancePowerDelegationToken.sol 2023-03-28 12:58:17 +@@ -0,0 +1,117 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity ^0.8.0; ++ ++interface IGovernancePowerDelegationToken { ++ enum GovernancePowerType { ++ VOTING, ++ PROPOSITION ++ } ++ ++ /** ++ * @dev emitted when a user delegates to another ++ * @param delegator the user which delegated governance power ++ * @param delegatee the delegatee ++ * @param delegationType the type of delegation (VOTING, PROPOSITION) ++ **/ ++ event DelegateChanged( ++ address indexed delegator, ++ address indexed delegatee, ++ GovernancePowerType delegationType ++ ); ++ ++ // @dev we removed DelegatedPowerChanged event because to reconstruct the full state of the system, ++ // is enough to have Transfer and DelegateChanged TODO: document it ++ ++ /** ++ * @dev delegates the specific power to a delegatee ++ * @param delegatee the user which delegated power will change ++ * @param delegationType the type of delegation (VOTING, PROPOSITION) ++ **/ ++ function delegateByType(address delegatee, GovernancePowerType delegationType) external; ++ ++ /** ++ * @dev delegates all the governance powers to a specific user ++ * @param delegatee the user to which the powers will be delegated ++ **/ ++ function delegate(address delegatee) external; ++ ++ /** ++ * @dev returns the delegatee of an user ++ * @param delegator the address of the delegator ++ * @param delegationType the type of delegation (VOTING, PROPOSITION) ++ * @return address of the specified delegatee ++ **/ ++ function getDelegateeByType(address delegator, GovernancePowerType delegationType) ++ external ++ view ++ returns (address); ++ ++ /** ++ * @dev returns delegates of an user ++ * @param delegator the address of the delegator ++ * @return a tuple of addresses the VOTING and PROPOSITION delegatee ++ **/ ++ function getDelegates(address delegator) ++ external ++ view ++ returns (address, address); ++ ++ /** ++ * @dev returns the current voting or proposition power of a user. ++ * @param user the user ++ * @param delegationType the type of delegation (VOTING, PROPOSITION) ++ * @return the current voting or proposition power of a user ++ **/ ++ function getPowerCurrent(address user, GovernancePowerType delegationType) ++ external ++ view ++ returns (uint256); ++ ++ /** ++ * @dev returns the current voting or proposition power of a user. ++ * @param user the user ++ * @return the current voting and proposition power of a user ++ **/ ++ function getPowersCurrent(address user) ++ external ++ view ++ returns (uint256, uint256); ++ ++ /** ++ * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md ++ * @param delegator the owner of the funds ++ * @param delegatee the user to who owner delegates his governance power ++ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) ++ * @param deadline the deadline timestamp, type(uint256).max for no deadline ++ * @param v signature param ++ * @param s signature param ++ * @param r signature param ++ */ ++ function metaDelegateByType( ++ address delegator, ++ address delegatee, ++ GovernancePowerType delegationType, ++ uint256 deadline, ++ uint8 v, ++ bytes32 r, ++ bytes32 s ++ ) external; ++ ++ /** ++ * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md ++ * @param delegator the owner of the funds ++ * @param delegatee the user to who delegator delegates his voting and proposition governance power ++ * @param deadline the deadline timestamp, type(uint256).max for no deadline ++ * @param v signature param ++ * @param s signature param ++ * @param r signature param ++ */ ++ function metaDelegate( ++ address delegator, ++ address delegatee, ++ uint256 deadline, ++ uint8 v, ++ bytes32 r, ++ bytes32 s ++ ) external; ++} +diff -ruN interfaces/ITransferHook.sol interfaces/ITransferHook.sol +--- interfaces/ITransferHook.sol 1970-01-01 01:00:00 ++++ interfaces/ITransferHook.sol 2023-03-28 12:58:17 +@@ -0,0 +1,10 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity ^0.8.0; ++ ++interface ITransferHook { ++ function onTransfer( ++ address from, ++ address to, ++ uint256 amount ++ ) external; ++} +diff -ruN src/BaseAaveToken.sol src/BaseAaveToken.sol +--- src/BaseAaveToken.sol 2023-03-28 13:07:07 ++++ src/BaseAaveToken.sol 2023-03-28 13:03:35 @@ -16,10 +16,10 @@ // reorder fields to make hooks syntax simpler @@ -76,7 +1273,289 @@ diff -uN BaseAaveToken.sol /BaseAaveToken.sol } mapping(address => DelegationAwareBalance) internal _balances; -Common subdirectories: interfaces and /interfaces -Common subdirectories: lib and /lib -Common subdirectories: test and /test -Common subdirectories: utils and /utils +diff -ruN test/InternalDelegationFunctionsTest.t.sol test/InternalDelegationFunctionsTest.t.sol +--- test/InternalDelegationFunctionsTest.t.sol 1970-01-01 01:00:00 ++++ test/InternalDelegationFunctionsTest.t.sol 2023-03-28 12:58:17 +@@ -0,0 +1,127 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity ^0.8.0; ++ ++import {Strings} from '../../lib/openzeppelin-contracts/contracts/utils/Strings.sol'; ++import {IERC20Metadata} from '../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; ++import {AaveTokenV3} from '../AaveTokenV3.sol'; ++ ++import {AaveUtils, console} from './utils/AaveUtils.sol'; ++ ++contract StorageTest is AaveTokenV3, AaveUtils { ++ function setUp() public {} ++ ++ function testFor_getDelegatedPowerByType() public { ++ DelegationAwareBalance memory userState; ++ userState.delegatedPropositionBalance = 100; ++ userState.delegatedVotingBalance = 200; ++ assertEq( ++ _getDelegatedPowerByType(userState, GovernancePowerType.VOTING), ++ userState.delegatedVotingBalance * POWER_SCALE_FACTOR ++ ); ++ assertEq( ++ _getDelegatedPowerByType(userState, GovernancePowerType.PROPOSITION), ++ userState.delegatedPropositionBalance * POWER_SCALE_FACTOR ++ ); ++ } ++ ++ function testFor_getDelegateeByType() public { ++ address user = address(0x1); ++ address user2 = address(0x2); ++ address user3 = address(0x3); ++ DelegationAwareBalance memory userState; ++ ++ _votingDelegateeV2[user] = address(user2); ++ _propositionDelegateeV2[user] = address(user3); ++ ++ userState.delegationState = DelegationState.VOTING_DELEGATED; ++ assertEq(_getDelegateeByType(user, userState, GovernancePowerType.VOTING), user2); ++ assertEq(_getDelegateeByType(user, userState, GovernancePowerType.PROPOSITION), address(0)); ++ ++ userState.delegationState = DelegationState.PROPOSITION_DELEGATED; ++ assertEq(_getDelegateeByType(user, userState, GovernancePowerType.VOTING), address(0)); ++ assertEq(_getDelegateeByType(user, userState, GovernancePowerType.PROPOSITION), user3); ++ ++ userState.delegationState = DelegationState.FULL_POWER_DELEGATED; ++ assertEq(_getDelegateeByType(user, userState, GovernancePowerType.VOTING), user2); ++ assertEq(_getDelegateeByType(user, userState, GovernancePowerType.PROPOSITION), user3); ++ } ++ ++ function _setDelegationStateAndTest( ++ DelegationState initialState, ++ GovernancePowerType governancePowerType, ++ bool willDelegate, ++ DelegationState expectedState ++ ) internal { ++ DelegationAwareBalance memory userState; ++ DelegationAwareBalance memory updatedUserState; ++ userState.delegationState = initialState; ++ updatedUserState = _updateDelegationFlagByType(userState, governancePowerType, willDelegate); ++ assertTrue( ++ updatedUserState.delegationState == expectedState, ++ Strings.toString(uint8(updatedUserState.delegationState)) ++ ); ++ } ++ ++ function testFor_updateDelegationFlagByType() public { ++ _setDelegationStateAndTest( ++ DelegationState.NO_DELEGATION, ++ GovernancePowerType.VOTING, ++ true, ++ DelegationState.VOTING_DELEGATED ++ ); ++ _setDelegationStateAndTest( ++ DelegationState.NO_DELEGATION, ++ GovernancePowerType.VOTING, ++ false, ++ DelegationState.NO_DELEGATION ++ ); ++ _setDelegationStateAndTest( ++ DelegationState.VOTING_DELEGATED, ++ GovernancePowerType.VOTING, ++ true, ++ DelegationState.VOTING_DELEGATED ++ ); ++ _setDelegationStateAndTest( ++ DelegationState.FULL_POWER_DELEGATED, ++ GovernancePowerType.VOTING, ++ false, ++ DelegationState.PROPOSITION_DELEGATED ++ ); ++ _setDelegationStateAndTest( ++ DelegationState.NO_DELEGATION, ++ GovernancePowerType.PROPOSITION, ++ true, ++ DelegationState.PROPOSITION_DELEGATED ++ ); ++ _setDelegationStateAndTest( ++ DelegationState.PROPOSITION_DELEGATED, ++ GovernancePowerType.PROPOSITION, ++ false, ++ DelegationState.NO_DELEGATION ++ ); ++ _setDelegationStateAndTest( ++ DelegationState.PROPOSITION_DELEGATED, ++ GovernancePowerType.VOTING, ++ true, ++ DelegationState.FULL_POWER_DELEGATED ++ ); ++ _setDelegationStateAndTest( ++ DelegationState.FULL_POWER_DELEGATED, ++ GovernancePowerType.VOTING, ++ true, ++ DelegationState.FULL_POWER_DELEGATED ++ ); ++ _setDelegationStateAndTest( ++ DelegationState.VOTING_DELEGATED, ++ GovernancePowerType.PROPOSITION, ++ true, ++ DelegationState.FULL_POWER_DELEGATED ++ ); ++ _setDelegationStateAndTest( ++ DelegationState.FULL_POWER_DELEGATED, ++ GovernancePowerType.PROPOSITION, ++ true, ++ DelegationState.FULL_POWER_DELEGATED ++ ); ++ } ++} +diff -ruN test/StorageTest.t.sol test/StorageTest.t.sol +--- test/StorageTest.t.sol 1970-01-01 01:00:00 ++++ test/StorageTest.t.sol 2023-03-28 12:58:17 +@@ -0,0 +1,44 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity ^0.8.0; ++ ++import {IERC20Metadata} from '../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; ++import {AaveTokenV3} from '../AaveTokenV3.sol'; ++ ++import {AaveUtils, console} from './utils/AaveUtils.sol'; ++ ++contract StorageTest is AaveUtils { ++ function setUp() public { ++ revertAaveImplementationUpdate(); ++ } ++ ++ function testForBaseMetadata() public { ++ string memory nameBefore = AAVE_TOKEN.name(); ++ string memory symbolBefore = AAVE_TOKEN.symbol(); ++ uint256 decimalsBefore = AAVE_TOKEN.decimals(); ++ ++ updateAaveImplementation(AAVE_IMPLEMENTATION_V3); ++ ++ assertEq(AAVE_TOKEN.name(), nameBefore); ++ assertEq(AAVE_TOKEN.symbol(), symbolBefore); ++ assertEq(AAVE_TOKEN.decimals(), decimalsBefore); ++ } ++ ++ function testForTotalSupply() public { ++ uint256 totalSupplyBefore = AAVE_TOKEN.totalSupply(); ++ ++ updateAaveImplementation(AAVE_IMPLEMENTATION_V3); ++ ++ assertEq(AAVE_TOKEN.totalSupply(), totalSupplyBefore); ++ } ++ ++ function testForBalances() public { ++ uint256[] memory balancesBefore = new uint256[](AAVE_HOLDERS.length); ++ for (uint256 i = 0; i < AAVE_HOLDERS.length; i += 1) { ++ balancesBefore[i] = AAVE_TOKEN.balanceOf(AAVE_HOLDERS[i]); ++ } ++ updateAaveImplementation(AAVE_IMPLEMENTATION_V3); ++ for (uint256 i = 0; i < AAVE_HOLDERS.length; i += 1) { ++ assertEq(AAVE_TOKEN.balanceOf(AAVE_HOLDERS[i]), balancesBefore[i]); ++ } ++ } ++} +diff -ruN test/utils/AaveUtils.sol test/utils/AaveUtils.sol +--- test/utils/AaveUtils.sol 1970-01-01 01:00:00 ++++ test/utils/AaveUtils.sol 2023-03-28 12:58:17 +@@ -0,0 +1,47 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity ^0.8.0; ++ ++import 'forge-std/Test.sol'; ++ ++import {IERC20Metadata} from '../../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; ++ ++import {AaveTokenV3} from '../../AaveTokenV3.sol'; ++ ++import {IBaseAdminUpgradeabilityProxy} from './IBaseAdminUpgradeabilityProxy.sol'; ++ ++abstract contract AaveUtils is Test { ++ address[] public AAVE_HOLDERS; ++ IERC20Metadata public constant AAVE_TOKEN = ++ IERC20Metadata(0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9); ++ ++ address public constant AAVE_V2_IMPLEMENTATION = 0xC13eac3B4F9EED480045113B7af00F7B5655Ece8; ++ ++ address public constant AAVE_TOKEN_PROXY_ADMIN = 0x61910EcD7e8e942136CE7Fe7943f956cea1CC2f7; ++ address public AAVE_IMPLEMENTATION_V3; ++ ++ constructor() { ++ AAVE_IMPLEMENTATION_V3 = address(new AaveTokenV3()); ++ AAVE_HOLDERS = new address[](10); ++ AAVE_HOLDERS = [ ++ 0x4da27a545c0c5B758a6BA100e3a049001de870f5, ++ 0xFFC97d72E13E01096502Cb8Eb52dEe56f74DAD7B, ++ 0x25F2226B597E8F9514B3F68F00f494cF4f286491, ++ 0xC697051d1C6296C24aE3bceF39acA743861D9A81, ++ 0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8, ++ 0x317625234562B1526Ea2FaC4030Ea499C5291de4, ++ 0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503, ++ 0xF977814e90dA44bFA03b6295A0616a897441aceC, ++ 0x26a78D5b6d7a7acEEDD1e6eE3229b372A624d8b7, ++ 0x28C6c06298d514Db089934071355E5743bf21d60 ++ ]; ++ } ++ ++ function updateAaveImplementation(address newImplementation) public { ++ vm.prank(AAVE_TOKEN_PROXY_ADMIN); ++ IBaseAdminUpgradeabilityProxy(address(AAVE_TOKEN)).upgradeTo(newImplementation); ++ } ++ ++ function revertAaveImplementationUpdate() public { ++ updateAaveImplementation(AAVE_V2_IMPLEMENTATION); ++ } ++} +diff -ruN test/utils/IBaseAdminUpgradeabilityProxy.sol test/utils/IBaseAdminUpgradeabilityProxy.sol +--- test/utils/IBaseAdminUpgradeabilityProxy.sol 1970-01-01 01:00:00 ++++ test/utils/IBaseAdminUpgradeabilityProxy.sol 2023-03-28 12:58:17 +@@ -0,0 +1,6 @@ ++// SPDX-License-Identifier: MIT ++pragma solidity ^0.8.0; ++ ++interface IBaseAdminUpgradeabilityProxy { ++ function upgradeTo(address newImplementation) external; ++} +diff -ruN utils/VersionedInitializable.sol utils/VersionedInitializable.sol +--- utils/VersionedInitializable.sol 1970-01-01 01:00:00 ++++ utils/VersionedInitializable.sol 2023-03-28 12:58:17 +@@ -0,0 +1,42 @@ ++// SPDX-License-Identifier: agpl-3.0 ++pragma solidity ^0.8.0; ++ ++/** ++ * @title VersionedInitializable ++ * ++ * @dev Helper contract to support initializer functions. To use it, replace ++ * the constructor with a function that has the `initializer` modifier. ++ * WARNING: Unlike constructors, initializer functions must be manually ++ * invoked. This applies both to deploying an Initializable contract, as well ++ * as extending an Initializable contract via inheritance. ++ * WARNING: When used with inheritance, manual care must be taken to not invoke ++ * a parent initializer twice, or ensure that all initializers are idempotent, ++ * because this is not dealt with automatically as with constructors. ++ * ++ * @author Aave, inspired by the OpenZeppelin Initializable contract ++ */ ++abstract contract VersionedInitializable { ++ /** ++ * @dev Indicates that the contract has been initialized. ++ */ ++ uint256 internal lastInitializedRevision = 0; ++ ++ /** ++ * @dev Modifier to use in the initializer function of a contract. ++ */ ++ modifier initializer() { ++ uint256 revision = getRevision(); ++ require(revision > lastInitializedRevision, 'Contract instance has already been initialized'); ++ ++ lastInitializedRevision = revision; ++ ++ _; ++ } ++ ++ /// @dev returns the revision number of the contract. ++ /// Needs to be defined in the inherited class as a constant. ++ function getRevision() internal pure virtual returns (uint256); ++ ++ // Reserved storage space to allow for layout changes in the future. ++ uint256[50] private ______gap; ++} From 924cb6fe1b925edc027cd97340b1d1fd262822a2 Mon Sep 17 00:00:00 2001 From: Andrei Kozlov Date: Tue, 28 Mar 2023 14:16:31 +0300 Subject: [PATCH 52/58] draft: BaseDelegation --- src/BaseDelegation.sol | 419 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 419 insertions(+) create mode 100644 src/BaseDelegation.sol diff --git a/src/BaseDelegation.sol b/src/BaseDelegation.sol new file mode 100644 index 0000000..48add33 --- /dev/null +++ b/src/BaseDelegation.sol @@ -0,0 +1,419 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {VersionedInitializable} from './utils/VersionedInitializable.sol'; +import {IGovernancePowerDelegationToken} from './interfaces/IGovernancePowerDelegationToken.sol'; + +abstract contract BaseDelegation is IGovernancePowerDelegationToken { + enum DelegationState { + NO_DELEGATION, + VOTING_DELEGATED, + PROPOSITION_DELEGATED, + FULL_POWER_DELEGATED + } + + struct DelegationAwareBalance { + uint104 balance; + uint72 delegatedPropositionBalance; + uint72 delegatedVotingBalance; + DelegationState delegationState; + } + + mapping(address => address) internal _votingDelegateeV2; + mapping(address => address) internal _propositionDelegateeV2; + + /// @dev we assume that for the governance system 18 decimals of precision is not needed, + // by this constant we reduce it by 10, to 8 decimals + uint256 public constant POWER_SCALE_FACTOR = 1e10; + + bytes32 public constant DELEGATE_BY_TYPE_TYPEHASH = + keccak256( + 'DelegateByType(address delegator,address delegatee,GovernancePowerType delegationType,uint256 nonce,uint256 deadline)' + ); + bytes32 public constant DELEGATE_TYPEHASH = + keccak256('Delegate(address delegator,address delegatee,uint256 nonce,uint256 deadline)'); + + function DOMAIN_SEPARATOR() public view virtual returns (bytes32); + + function _getDelegationAwareBalance(address user) + internal + view + virtual + returns (DelegationAwareBalance memory); + + function _getNonces(address user) internal view virtual returns (uint256); + + function _setNonces(address user, uint256 nonce) internal virtual; + + function _setDelegationAwareBalance(address user, DelegationAwareBalance memory balance) + internal + virtual; + + /// @inheritdoc IGovernancePowerDelegationToken + function delegateByType(address delegatee, GovernancePowerType delegationType) + external + virtual + override + { + _delegateByType(msg.sender, delegatee, delegationType); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function delegate(address delegatee) external override { + _delegateByType(msg.sender, delegatee, GovernancePowerType.VOTING); + _delegateByType(msg.sender, delegatee, GovernancePowerType.PROPOSITION); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function getDelegateeByType(address delegator, GovernancePowerType delegationType) + external + view + override + returns (address) + { + return _getDelegateeByType(delegator, _getDelegationAwareBalance(delegator), delegationType); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function getDelegates(address delegator) external view override returns (address, address) { + DelegationAwareBalance memory delegatorBalance = _getDelegationAwareBalance(delegator); + return ( + _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.VOTING), + _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.PROPOSITION) + ); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function getPowerCurrent(address user, GovernancePowerType delegationType) + public + view + override + returns (uint256) + { + DelegationAwareBalance memory userState = _getDelegationAwareBalance(user); + uint256 userOwnPower = uint8(userState.delegationState) & (uint8(delegationType) + 1) == 0 + ? userState.balance + : 0; + uint256 userDelegatedPower = _getDelegatedPowerByType(userState, delegationType); + return userOwnPower + userDelegatedPower; + } + + /// @inheritdoc IGovernancePowerDelegationToken + function getPowersCurrent(address user) external view override returns (uint256, uint256) { + return ( + getPowerCurrent(user, GovernancePowerType.VOTING), + getPowerCurrent(user, GovernancePowerType.PROPOSITION) + ); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function metaDelegateByType( + address delegator, + address delegatee, + GovernancePowerType delegationType, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external override { + require(delegator != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _getNonces(delegator); + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR(), + keccak256( + abi.encode( + DELEGATE_BY_TYPE_TYPEHASH, + delegator, + delegatee, + delegationType, + currentValidNonce, + deadline + ) + ) + ) + ); + + require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + unchecked { + // Does not make sense to check because it's not realistic to reach uint256.max in nonce + _setNonces(delegator, currentValidNonce + 1); + } + _delegateByType(delegator, delegatee, delegationType); + } + + /// @inheritdoc IGovernancePowerDelegationToken + function metaDelegate( + address delegator, + address delegatee, + uint256 deadline, + uint8 v, + bytes32 r, + bytes32 s + ) external override { + require(delegator != address(0), 'INVALID_OWNER'); + //solium-disable-next-line + require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); + uint256 currentValidNonce = _getNonces(delegator); + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR(), + keccak256(abi.encode(DELEGATE_TYPEHASH, delegator, delegatee, currentValidNonce, deadline)) + ) + ); + + require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); + unchecked { + // does not make sense to check because it's not realistic to reach uint256.max in nonce + _setNonces(delegator, currentValidNonce + 1); + } + _delegateByType(delegator, delegatee, GovernancePowerType.VOTING); + _delegateByType(delegator, delegatee, GovernancePowerType.PROPOSITION); + } + + /** + * @dev Modifies the delegated power of a `delegatee` account by type (VOTING, PROPOSITION). + * Passing the impact on the delegation of `delegatee` account before and after to reduce conditionals and not lose + * any precision. + * @param impactOnDelegationBefore how much impact a balance of another account had over the delegation of a `delegatee` + * before an action. + * For example, if the action is a delegation from one account to another, the impact before the action will be 0. + * @param impactOnDelegationAfter how much impact a balance of another account will have over the delegation of a `delegatee` + * after an action. + * For example, if the action is a delegation from one account to another, the impact after the action will be the whole balance + * of the account changing the delegatee. + * @param delegatee the user whom delegated governance power will be changed + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + **/ + function _governancePowerTransferByType( + uint104 impactOnDelegationBefore, + uint104 impactOnDelegationAfter, + address delegatee, + GovernancePowerType delegationType + ) internal { + if (delegatee == address(0)) return; + if (impactOnDelegationBefore == impactOnDelegationAfter) return; + + // To make delegated balance fit into uint72 we're decreasing precision of delegated balance by POWER_SCALE_FACTOR + uint72 impactOnDelegationBefore72 = uint72(impactOnDelegationBefore / POWER_SCALE_FACTOR); + uint72 impactOnDelegationAfter72 = uint72(impactOnDelegationAfter / POWER_SCALE_FACTOR); + + DelegationAwareBalance memory delegateeState = _getDelegationAwareBalance(delegatee); + if (delegationType == GovernancePowerType.VOTING) { + delegateeState.delegatedVotingBalance = + delegateeState.delegatedVotingBalance - + impactOnDelegationBefore72 + + impactOnDelegationAfter72; + } else { + delegateeState.delegatedPropositionBalance = + delegateeState.delegatedPropositionBalance - + impactOnDelegationBefore72 + + impactOnDelegationAfter72; + } + _setDelegationAwareBalance(delegatee, delegateeState); + } + + /** + * @dev performs all state changes related to balance transfer and corresponding delegation changes + * @param from token sender + * @param to token recipient + * @param amount amount of tokens sent + **/ + function _transferWithDelegation( + address from, + address to, + uint256 amount + ) internal { + if (from == to) { + return; + } + + if (from != address(0)) { + DelegationAwareBalance memory fromUserState = _getDelegationAwareBalance(from); + require(fromUserState.balance >= amount, 'ERC20: transfer amount exceeds balance'); + + uint104 fromBalanceBefore = fromUserState.balance; + uint104 fromBalanceAfter; + unchecked { + fromBalanceAfter = fromUserState.balance - uint104(amount); + } + fromUserState.balance = fromBalanceAfter; + _setDelegationAwareBalance(from, fromUserState); + if (fromUserState.delegationState != DelegationState.NO_DELEGATION) { + _governancePowerTransferByType( + fromBalanceBefore, + fromBalanceAfter, + _getDelegateeByType(from, fromUserState, GovernancePowerType.VOTING), + GovernancePowerType.VOTING + ); + _governancePowerTransferByType( + fromBalanceBefore, + fromBalanceAfter, + _getDelegateeByType(from, fromUserState, GovernancePowerType.PROPOSITION), + GovernancePowerType.PROPOSITION + ); + } + } + + if (to != address(0)) { + DelegationAwareBalance memory toUserState = _getDelegationAwareBalance(to); + uint104 toBalanceBefore = toUserState.balance; + toUserState.balance = toBalanceBefore + uint104(amount); + _setDelegationAwareBalance(to, toUserState); + + if (toUserState.delegationState != DelegationState.NO_DELEGATION) { + _governancePowerTransferByType( + toBalanceBefore, + toUserState.balance, + _getDelegateeByType(to, toUserState, GovernancePowerType.VOTING), + GovernancePowerType.VOTING + ); + _governancePowerTransferByType( + toBalanceBefore, + toUserState.balance, + _getDelegateeByType(to, toUserState, GovernancePowerType.PROPOSITION), + GovernancePowerType.PROPOSITION + ); + } + } + } + + /** + * @dev Extracts from state and returns delegated governance power (Voting, Proposition) + * @param userState the current state of a user + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + **/ + function _getDelegatedPowerByType( + DelegationAwareBalance memory userState, + GovernancePowerType delegationType + ) internal pure returns (uint256) { + return + POWER_SCALE_FACTOR * + ( + delegationType == GovernancePowerType.VOTING + ? userState.delegatedVotingBalance + : userState.delegatedPropositionBalance + ); + } + + /** + * @dev Extracts from state and returns the delegatee of a delegator by type of governance power (Voting, Proposition) + * - If the delegator doesn't have any delegatee, returns address(0) + * @param delegator delegator + * @param userState the current state of a user + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + **/ + function _getDelegateeByType( + address delegator, + DelegationAwareBalance memory userState, + GovernancePowerType delegationType + ) internal view returns (address) { + if (delegationType == GovernancePowerType.VOTING) { + return + /// With the & operation, we cover both VOTING_DELEGATED delegation and FULL_POWER_DELEGATED + /// as VOTING_DELEGATED is equivalent to 01 in binary and FULL_POWER_DELEGATED is equivalent to 11 + (uint8(userState.delegationState) & uint8(DelegationState.VOTING_DELEGATED)) != 0 + ? _votingDelegateeV2[delegator] + : address(0); + } + return + userState.delegationState >= DelegationState.PROPOSITION_DELEGATED + ? _propositionDelegateeV2[delegator] + : address(0); + } + + /** + * @dev Changes user's delegatee address by type of governance power (Voting, Proposition) + * @param delegator delegator + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + * @param _newDelegatee the new delegatee + **/ + function _updateDelegateeByType( + address delegator, + GovernancePowerType delegationType, + address _newDelegatee + ) internal { + address newDelegatee = _newDelegatee == delegator ? address(0) : _newDelegatee; + if (delegationType == GovernancePowerType.VOTING) { + _votingDelegateeV2[delegator] = newDelegatee; + } else { + _propositionDelegateeV2[delegator] = newDelegatee; + } + } + + /** + * @dev Updates the specific flag which signaling about existence of delegation of governance power (Voting, Proposition) + * @param userState a user state to change + * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) + * @param willDelegate next state of delegation + **/ + function _updateDelegationFlagByType( + DelegationAwareBalance memory userState, + GovernancePowerType delegationType, + bool willDelegate + ) internal pure returns (DelegationAwareBalance memory) { + if (willDelegate) { + // Because GovernancePowerType starts from 0, we should add 1 first, then we apply bitwise OR + userState.delegationState = DelegationState( + uint8(userState.delegationState) | (uint8(delegationType) + 1) + ); + } else { + // First bitwise NEGATION, ie was 01, after XOR with 11 will be 10, + // then bitwise AND, which means it will keep only another delegation type if it exists + userState.delegationState = DelegationState( + uint8(userState.delegationState) & + ((uint8(delegationType) + 1) ^ uint8(DelegationState.FULL_POWER_DELEGATED)) + ); + } + return userState; + } + + /** + * @dev This is the equivalent of an ERC20 transfer(), but for a power type: an atomic transfer of a balance (power). + * When needed, it decreases the power of the `delegator` and when needed, it increases the power of the `delegatee` + * @param delegator delegator + * @param _delegatee the user which delegated power will change + * @param delegationType the type of delegation (VOTING, PROPOSITION) + **/ + function _delegateByType( + address delegator, + address _delegatee, + GovernancePowerType delegationType + ) internal { + // Here we unify the property that delegating power to address(0) == delegating power to yourself == no delegation + // So from now on, not being delegating is (exclusively) that delegatee == address(0) + address delegatee = _delegatee == delegator ? address(0) : _delegatee; + + // We read the whole struct before validating delegatee, because in the optimistic case + // (_delegatee != currentDelegatee) we will reuse userState in the rest of the function + DelegationAwareBalance memory delegatorState = _getDelegationAwareBalance(delegator); + address currentDelegatee = _getDelegateeByType(delegator, delegatorState, delegationType); + if (delegatee == currentDelegatee) return; + + bool delegatingNow = currentDelegatee != address(0); + bool willDelegateAfter = delegatee != address(0); + + if (delegatingNow) { + _governancePowerTransferByType(delegatorState.balance, 0, currentDelegatee, delegationType); + } + + if (willDelegateAfter) { + _governancePowerTransferByType(0, delegatorState.balance, delegatee, delegationType); + } + + _updateDelegateeByType(delegator, delegationType, delegatee); + + if (willDelegateAfter != delegatingNow) { + _setDelegationAwareBalance( + delegator, + _updateDelegationFlagByType(delegatorState, delegationType, willDelegateAfter) + ); + } + + emit DelegateChanged(delegator, delegatee, delegationType); + } +} From 7f345bea4409492b3fa672636ddedf3724b0a29c Mon Sep 17 00:00:00 2001 From: Tadeas Kucera Date: Tue, 28 Mar 2023 13:19:26 +0200 Subject: [PATCH 53/58] Fix scripts --- certora/scripts/erc20.sh | 4 ++-- certora/scripts/verifyCommunity.sh | 4 ++-- certora/scripts/verifyDelegate.sh | 4 ++-- certora/scripts/verifyGeneral.sh | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/certora/scripts/erc20.sh b/certora/scripts/erc20.sh index 2b886ae..4b28024 100644 --- a/certora/scripts/erc20.sh +++ b/certora/scripts/erc20.sh @@ -8,6 +8,6 @@ certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3Harness \ $RULE \ --solc solc8.13 \ --optimistic_loop \ - --send_only \ + --cloud \ --msg "AaveTokenV3:erc20.spec $1" - + diff --git a/certora/scripts/verifyCommunity.sh b/certora/scripts/verifyCommunity.sh index 4e8eeca..46947f9 100644 --- a/certora/scripts/verifyCommunity.sh +++ b/certora/scripts/verifyCommunity.sh @@ -8,7 +8,7 @@ certoraRun certora/harness/AaveTokenV3HarnessCommunity.sol:AaveTokenV3Harness \ $RULE \ --solc solc8.13 \ --optimistic_loop \ - --send_only \ + --cloud \ --msg "AaveTokenV3HarnessCommunity:community.spec $1" # --sanity - + diff --git a/certora/scripts/verifyDelegate.sh b/certora/scripts/verifyDelegate.sh index f05bccf..527014c 100755 --- a/certora/scripts/verifyDelegate.sh +++ b/certora/scripts/verifyDelegate.sh @@ -8,7 +8,7 @@ certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3Harness \ $RULE \ --solc solc8.13 \ --optimistic_loop \ - --send_only \ + --cloud \ --msg "AaveTokenV3Harness:delegate.spec $1" # --sanity - + diff --git a/certora/scripts/verifyGeneral.sh b/certora/scripts/verifyGeneral.sh index 9e6d7ac..6655c1c 100644 --- a/certora/scripts/verifyGeneral.sh +++ b/certora/scripts/verifyGeneral.sh @@ -9,6 +9,6 @@ certoraRun certora/harness/AaveTokenV3HarnessStorage.sol:AaveTokenV3Harness \ --solc solc8.13 \ --optimistic_loop \ --settings -smt_bitVectorTheory=true \ - --send_only \ + --cloud \ --msg "AaveTokenV3:general.spec $1" - + From b5c3f585c18a45cff29ada4607bb7b12dea56540 Mon Sep 17 00:00:00 2001 From: Tadeas Kucera Date: Tue, 28 Mar 2023 13:34:34 +0200 Subject: [PATCH 54/58] Add certora-to-main-fork github action trigger on PRs --- .github/workflows/certora.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/certora.yml b/.github/workflows/certora.yml index 7dfd691..a72b568 100644 --- a/.github/workflows/certora.yml +++ b/.github/workflows/certora.yml @@ -7,6 +7,7 @@ on: pull_request: branches: - main + - certora-to-main-fork workflow_dispatch: @@ -32,13 +33,13 @@ jobs: - name: Install certora cli run: pip install certora-cli - + - name: Install solc run: | wget https://github.com/ethereum/solidity/releases/download/v0.8.13/solc-static-linux chmod +x solc-static-linux sudo mv solc-static-linux /usr/local/bin/solc8.13 - + - name: Verify rule ${{ matrix.rule }} run: | cd certora @@ -49,7 +50,7 @@ jobs: sh certora/scripts/${{ matrix.rule }} env: CERTORAKEY: ${{ secrets.CERTORAKEY }} - + strategy: fail-fast: false max-parallel: 16 From 7a97d9dd4fbd1f83f274b6d68d673c34820b933d Mon Sep 17 00:00:00 2001 From: Andrei Kozlov Date: Tue, 28 Mar 2023 14:38:47 +0300 Subject: [PATCH 55/58] change GovernancePowerType to uint8 on DELEGATE_BY_TYPE_TYPEHASH --- src/BaseDelegation.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BaseDelegation.sol b/src/BaseDelegation.sol index 48add33..9bf84a3 100644 --- a/src/BaseDelegation.sol +++ b/src/BaseDelegation.sol @@ -28,7 +28,7 @@ abstract contract BaseDelegation is IGovernancePowerDelegationToken { bytes32 public constant DELEGATE_BY_TYPE_TYPEHASH = keccak256( - 'DelegateByType(address delegator,address delegatee,GovernancePowerType delegationType,uint256 nonce,uint256 deadline)' + 'DelegateByType(address delegator,address delegatee,uint8 delegationType,uint256 nonce,uint256 deadline)' ); bytes32 public constant DELEGATE_TYPEHASH = keccak256('Delegate(address delegator,address delegatee,uint256 nonce,uint256 deadline)'); From 7524631dad85b755e7120b28b4cc7844d8e14e39 Mon Sep 17 00:00:00 2001 From: Tadeas Kucera Date: Tue, 28 Mar 2023 13:53:28 +0200 Subject: [PATCH 56/58] Delete unused imports from munged BaseAaveToken.sol by applyHarness.patch --- certora/applyHarness.patch | 41 ++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/certora/applyHarness.patch b/certora/applyHarness.patch index 4649613..c03439e 100644 --- a/certora/applyHarness.patch +++ b/certora/applyHarness.patch @@ -1,6 +1,6 @@ diff -ruN AaveTokenV3.sol AaveTokenV3.sol --- AaveTokenV3.sol 1970-01-01 01:00:00 -+++ AaveTokenV3.sol 2023-03-28 12:58:17 ++++ AaveTokenV3.sol 2023-03-28 13:08:24 @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; @@ -393,7 +393,7 @@ diff -ruN AaveTokenV3.sol AaveTokenV3.sol \ No newline at end of file diff -ruN AaveTokenV3.sol.orig AaveTokenV3.sol.orig --- AaveTokenV3.sol.orig 1970-01-01 01:00:00 -+++ AaveTokenV3.sol.orig 2023-03-28 12:58:17 ++++ AaveTokenV3.sol.orig 2023-03-28 13:08:24 @@ -0,0 +1,392 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; @@ -790,7 +790,7 @@ diff -ruN AaveTokenV3.sol.orig AaveTokenV3.sol.orig \ No newline at end of file diff -ruN BaseAaveToken.sol BaseAaveToken.sol --- BaseAaveToken.sol 1970-01-01 01:00:00 -+++ BaseAaveToken.sol 2023-03-28 12:58:17 ++++ BaseAaveToken.sol 2023-03-28 13:08:24 @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; @@ -956,7 +956,7 @@ diff -ruN BaseAaveToken.sol BaseAaveToken.sol +} diff -ruN BaseAaveTokenV2.sol BaseAaveTokenV2.sol --- BaseAaveTokenV2.sol 1970-01-01 01:00:00 -+++ BaseAaveTokenV2.sol 2023-03-28 12:58:17 ++++ BaseAaveTokenV2.sol 2023-03-28 13:08:24 @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; @@ -1037,7 +1037,7 @@ diff -ruN BaseAaveTokenV2.sol BaseAaveTokenV2.sol +} diff -ruN BaseAaveTokenV3.sol BaseAaveTokenV3.sol --- BaseAaveTokenV3.sol 1970-01-01 01:00:00 -+++ BaseAaveTokenV3.sol 2023-03-28 12:58:17 ++++ BaseAaveTokenV3.sol 2023-03-28 13:08:24 @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT + @@ -1125,7 +1125,7 @@ diff -ruN BaseAaveTokenV3.sol BaseAaveTokenV3.sol +} diff -ruN interfaces/IGovernancePowerDelegationToken.sol interfaces/IGovernancePowerDelegationToken.sol --- interfaces/IGovernancePowerDelegationToken.sol 1970-01-01 01:00:00 -+++ interfaces/IGovernancePowerDelegationToken.sol 2023-03-28 12:58:17 ++++ interfaces/IGovernancePowerDelegationToken.sol 2023-03-28 13:08:24 @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; @@ -1246,7 +1246,7 @@ diff -ruN interfaces/IGovernancePowerDelegationToken.sol interfaces/IGovernanceP +} diff -ruN interfaces/ITransferHook.sol interfaces/ITransferHook.sol --- interfaces/ITransferHook.sol 1970-01-01 01:00:00 -+++ interfaces/ITransferHook.sol 2023-03-28 12:58:17 ++++ interfaces/ITransferHook.sol 2023-03-28 13:08:24 @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; @@ -1259,9 +1259,20 @@ diff -ruN interfaces/ITransferHook.sol interfaces/ITransferHook.sol + ) external; +} diff -ruN src/BaseAaveToken.sol src/BaseAaveToken.sol ---- src/BaseAaveToken.sol 2023-03-28 13:07:07 -+++ src/BaseAaveToken.sol 2023-03-28 13:03:35 -@@ -16,10 +16,10 @@ +--- src/BaseAaveToken.sol 2023-03-28 13:52:04 ++++ src/BaseAaveToken.sol 2023-03-28 13:51:59 +@@ -1,10 +1,6 @@ + // SPDX-License-Identifier: MIT + pragma solidity ^0.8.0; + +-import {Context} from '../lib/openzeppelin-contracts/contracts/utils/Context.sol'; +-import {IERC20} from '../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; +-import {IERC20Metadata} from '../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; +- + // Inspired by OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) + abstract contract BaseAaveToken is Context, IERC20Metadata { + enum DelegationState { +@@ -16,10 +12,10 @@ // reorder fields to make hooks syntax simpler struct DelegationAwareBalance { @@ -1275,7 +1286,7 @@ diff -ruN src/BaseAaveToken.sol src/BaseAaveToken.sol mapping(address => DelegationAwareBalance) internal _balances; diff -ruN test/InternalDelegationFunctionsTest.t.sol test/InternalDelegationFunctionsTest.t.sol --- test/InternalDelegationFunctionsTest.t.sol 1970-01-01 01:00:00 -+++ test/InternalDelegationFunctionsTest.t.sol 2023-03-28 12:58:17 ++++ test/InternalDelegationFunctionsTest.t.sol 2023-03-28 13:08:24 @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; @@ -1406,7 +1417,7 @@ diff -ruN test/InternalDelegationFunctionsTest.t.sol test/InternalDelegationFunc +} diff -ruN test/StorageTest.t.sol test/StorageTest.t.sol --- test/StorageTest.t.sol 1970-01-01 01:00:00 -+++ test/StorageTest.t.sol 2023-03-28 12:58:17 ++++ test/StorageTest.t.sol 2023-03-28 13:08:24 @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; @@ -1454,7 +1465,7 @@ diff -ruN test/StorageTest.t.sol test/StorageTest.t.sol +} diff -ruN test/utils/AaveUtils.sol test/utils/AaveUtils.sol --- test/utils/AaveUtils.sol 1970-01-01 01:00:00 -+++ test/utils/AaveUtils.sol 2023-03-28 12:58:17 ++++ test/utils/AaveUtils.sol 2023-03-28 13:08:24 @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; @@ -1505,7 +1516,7 @@ diff -ruN test/utils/AaveUtils.sol test/utils/AaveUtils.sol +} diff -ruN test/utils/IBaseAdminUpgradeabilityProxy.sol test/utils/IBaseAdminUpgradeabilityProxy.sol --- test/utils/IBaseAdminUpgradeabilityProxy.sol 1970-01-01 01:00:00 -+++ test/utils/IBaseAdminUpgradeabilityProxy.sol 2023-03-28 12:58:17 ++++ test/utils/IBaseAdminUpgradeabilityProxy.sol 2023-03-28 13:08:24 @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; @@ -1515,7 +1526,7 @@ diff -ruN test/utils/IBaseAdminUpgradeabilityProxy.sol test/utils/IBaseAdminUpgr +} diff -ruN utils/VersionedInitializable.sol utils/VersionedInitializable.sol --- utils/VersionedInitializable.sol 1970-01-01 01:00:00 -+++ utils/VersionedInitializable.sol 2023-03-28 12:58:17 ++++ utils/VersionedInitializable.sol 2023-03-28 13:08:24 @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: agpl-3.0 +pragma solidity ^0.8.0; From b3b97b3c4766552c0f5eff0e344ad2c523aebb31 Mon Sep 17 00:00:00 2001 From: Tadeas Kucera Date: Tue, 28 Mar 2023 15:14:14 +0200 Subject: [PATCH 57/58] Fix applyHarness.patch --- certora/applyHarness.patch | 1602 ++---------------------------------- 1 file changed, 48 insertions(+), 1554 deletions(-) diff --git a/certora/applyHarness.patch b/certora/applyHarness.patch index c03439e..37e57b6 100644 --- a/certora/applyHarness.patch +++ b/certora/applyHarness.patch @@ -1,1278 +1,58 @@ -diff -ruN AaveTokenV3.sol AaveTokenV3.sol ---- AaveTokenV3.sol 1970-01-01 01:00:00 -+++ AaveTokenV3.sol 2023-03-28 13:08:24 -@@ -0,0 +1,388 @@ -+// SPDX-License-Identifier: MIT -+pragma solidity ^0.8.0; -+ -+import {VersionedInitializable} from './utils/VersionedInitializable.sol'; -+import {IGovernancePowerDelegationToken} from './interfaces/IGovernancePowerDelegationToken.sol'; -+import {BaseAaveTokenV2} from './BaseAaveTokenV2.sol'; -+ -+contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { -+ mapping(address => address) internal _votingDelegateeV2; -+ mapping(address => address) internal _propositionDelegateeV2; -+ -+ /// @dev we assume that for the governance system 18 decimals of precision is not needed, -+ // by this constant we reduce it by 10, to 8 decimals -+ uint256 public constant POWER_SCALE_FACTOR = 1e10; -+ -+ bytes32 public constant DELEGATE_BY_TYPE_TYPEHASH = -+ keccak256( -+ 'DelegateByType(address delegator,address delegatee,GovernancePowerType delegationType,uint256 nonce,uint256 deadline)' -+ ); -+ bytes32 public constant DELEGATE_TYPEHASH = -+ keccak256('Delegate(address delegator,address delegatee,uint256 nonce,uint256 deadline)'); -+ -+ /** -+ * @dev initializes the contract upon assignment to the InitializableAdminUpgradeabilityProxy -+ */ -+ function initialize() external virtual initializer {} -+ -+ /// @inheritdoc IGovernancePowerDelegationToken -+ function delegateByType(address delegatee, GovernancePowerType delegationType) -+ external -+ virtual -+ override -+ { -+ _delegateByType(msg.sender, delegatee, delegationType); -+ } -+ -+ /// @inheritdoc IGovernancePowerDelegationToken -+ function delegate(address delegatee) external override { -+ _delegateByType(msg.sender, delegatee, GovernancePowerType.VOTING); -+ _delegateByType(msg.sender, delegatee, GovernancePowerType.PROPOSITION); -+ } -+ -+ /// @inheritdoc IGovernancePowerDelegationToken -+ function getDelegateeByType(address delegator, GovernancePowerType delegationType) -+ external -+ view -+ override -+ returns (address) -+ { -+ return _getDelegateeByType(delegator, _balances[delegator], delegationType); -+ } -+ -+ /// @inheritdoc IGovernancePowerDelegationToken -+ function getDelegates(address delegator) external view override returns (address, address) { -+ DelegationAwareBalance memory delegatorBalance = _balances[delegator]; -+ return ( -+ _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.VOTING), -+ _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.PROPOSITION) -+ ); -+ } -+ -+ /// @inheritdoc IGovernancePowerDelegationToken -+ function getPowerCurrent(address user, GovernancePowerType delegationType) -+ public -+ view -+ override -+ returns (uint256) -+ { -+ DelegationAwareBalance memory userState = _balances[user]; -+ uint256 userOwnPower = uint8(userState.delegationState) & (uint8(delegationType) + 1) == 0 -+ ? _balances[user].balance -+ : 0; -+ uint256 userDelegatedPower = _getDelegatedPowerByType(userState, delegationType); -+ return userOwnPower + userDelegatedPower; -+ } -+ -+ /// @inheritdoc IGovernancePowerDelegationToken -+ function getPowersCurrent(address user) external view override returns (uint256, uint256) { -+ return ( -+ getPowerCurrent(user, GovernancePowerType.VOTING), -+ getPowerCurrent(user, GovernancePowerType.PROPOSITION) -+ ); -+ } -+ -+ /// @inheritdoc IGovernancePowerDelegationToken -+ function metaDelegateByType( -+ address delegator, -+ address delegatee, -+ GovernancePowerType delegationType, -+ uint256 deadline, -+ uint8 v, -+ bytes32 r, -+ bytes32 s -+ ) external override { -+ require(delegator != address(0), 'INVALID_OWNER'); -+ //solium-disable-next-line -+ require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); -+ uint256 currentValidNonce = _nonces[delegator]; -+ bytes32 digest = keccak256( -+ abi.encodePacked( -+ '\x19\x01', -+ DOMAIN_SEPARATOR, -+ keccak256( -+ abi.encode( -+ DELEGATE_BY_TYPE_TYPEHASH, -+ delegator, -+ delegatee, -+ delegationType, -+ currentValidNonce, -+ deadline -+ ) -+ ) -+ ) -+ ); -+ -+ require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); -+ unchecked { -+ // Does not make sense to check because it's not realistic to reach uint256.max in nonce -+ _nonces[delegator] = currentValidNonce + 1; -+ } -+ _delegateByType(delegator, delegatee, delegationType); -+ } -+ -+ /// @inheritdoc IGovernancePowerDelegationToken -+ function metaDelegate( -+ address delegator, -+ address delegatee, -+ uint256 deadline, -+ uint8 v, -+ bytes32 r, -+ bytes32 s -+ ) external override { -+ require(delegator != address(0), 'INVALID_OWNER'); -+ //solium-disable-next-line -+ require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); -+ uint256 currentValidNonce = _nonces[delegator]; -+ bytes32 digest = keccak256( -+ abi.encodePacked( -+ '\x19\x01', -+ DOMAIN_SEPARATOR, -+ keccak256(abi.encode(DELEGATE_TYPEHASH, delegator, delegatee, currentValidNonce, deadline)) -+ ) -+ ); -+ -+ require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); -+ unchecked { -+ // does not make sense to check because it's not realistic to reach uint256.max in nonce -+ _nonces[delegator] = currentValidNonce + 1; -+ } -+ _delegateByType(delegator, delegatee, GovernancePowerType.VOTING); -+ _delegateByType(delegator, delegatee, GovernancePowerType.PROPOSITION); -+ } -+ -+ /** -+ * @dev Modifies the delegated power of a `delegatee` account by type (VOTING, PROPOSITION). -+ * Passing the impact on the delegation of `delegatee` account before and after to reduce conditionals and not lose -+ * any precision. -+ * @param impactOnDelegationBefore how much impact a balance of another account had over the delegation of a `delegatee` -+ * before an action. -+ * For example, if the action is a delegation from one account to another, the impact before the action will be 0. -+ * @param impactOnDelegationAfter how much impact a balance of another account will have over the delegation of a `delegatee` -+ * after an action. -+ * For example, if the action is a delegation from one account to another, the impact after the action will be the whole balance -+ * of the account changing the delegatee. -+ * @param delegatee the user whom delegated governance power will be changed -+ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) -+ **/ -+ function _governancePowerTransferByType( -+ uint104 impactOnDelegationBefore, -+ uint104 impactOnDelegationAfter, -+ address delegatee, -+ GovernancePowerType delegationType -+ ) internal { -+ if (delegatee == address(0)) return; -+ if (impactOnDelegationBefore == impactOnDelegationAfter) return; -+ -+ // To make delegated balance fit into uint72 we're decreasing precision of delegated balance by POWER_SCALE_FACTOR -+ uint72 impactOnDelegationBefore72 = uint72(impactOnDelegationBefore / POWER_SCALE_FACTOR); -+ uint72 impactOnDelegationAfter72 = uint72(impactOnDelegationAfter / POWER_SCALE_FACTOR); -+ -+ if (delegationType == GovernancePowerType.VOTING) { -+ _balances[delegatee].delegatedVotingBalance = -+ _balances[delegatee].delegatedVotingBalance - -+ impactOnDelegationBefore72 + -+ impactOnDelegationAfter72; -+ } else { -+ _balances[delegatee].delegatedPropositionBalance = -+ _balances[delegatee].delegatedPropositionBalance - -+ impactOnDelegationBefore72 + -+ impactOnDelegationAfter72; -+ } -+ } -+ -+ /** -+ * @dev performs all state changes related to balance transfer and corresponding delegation changes -+ * @param from token sender -+ * @param to token recipient -+ * @param amount amount of tokens sent -+ **/ -+ function _transferWithDelegation( -+ address from, -+ address to, -+ uint256 amount -+ ) internal override { -+ if (from == to) { -+ return; -+ } -+ -+ if (from != address(0)) { -+ DelegationAwareBalance memory fromUserState = _balances[from]; -+ require(fromUserState.balance >= amount, 'ERC20: transfer amount exceeds balance'); -+ -+ uint104 fromBalanceAfter; -+ unchecked { -+ fromBalanceAfter = fromUserState.balance - uint104(amount); -+ } -+ _balances[from].balance = fromBalanceAfter; +Binary files .DS_Store and .DS_Store differ +diff -ruN src/AaveTokenV3.sol src/AaveTokenV3.sol +--- src/AaveTokenV3.sol 2023-03-28 15:10:26 ++++ src/AaveTokenV3.sol 2023-03-28 15:05:22 +@@ -215,7 +215,7 @@ + fromBalanceAfter = fromUserState.balance - uint104(amount); + } + _balances[from].balance = fromBalanceAfter; +- if (fromUserState.delegationState != DelegationState.NO_DELEGATION) { + if (fromUserState.delegationState != uint8(DelegationState.NO_DELEGATION)) { -+ _governancePowerTransferByType( -+ fromUserState.balance, -+ fromBalanceAfter, -+ _getDelegateeByType(from, fromUserState, GovernancePowerType.VOTING), -+ GovernancePowerType.VOTING -+ ); -+ _governancePowerTransferByType( -+ fromUserState.balance, -+ fromBalanceAfter, -+ _getDelegateeByType(from, fromUserState, GovernancePowerType.PROPOSITION), -+ GovernancePowerType.PROPOSITION -+ ); -+ } -+ } -+ -+ if (to != address(0)) { -+ DelegationAwareBalance memory toUserState = _balances[to]; -+ uint104 toBalanceBefore = toUserState.balance; -+ toUserState.balance = toBalanceBefore + uint104(amount); -+ _balances[to] = toUserState; -+ + _governancePowerTransferByType( + fromUserState.balance, + fromBalanceAfter, +@@ -237,7 +237,7 @@ + toUserState.balance = toBalanceBefore + uint104(amount); + _balances[to] = toUserState; + +- if (toUserState.delegationState != DelegationState.NO_DELEGATION) { + if (toUserState.delegationState != uint8(DelegationState.NO_DELEGATION)) { -+ _governancePowerTransferByType( -+ toBalanceBefore, -+ toUserState.balance, -+ _getDelegateeByType(to, toUserState, GovernancePowerType.VOTING), -+ GovernancePowerType.VOTING -+ ); -+ _governancePowerTransferByType( -+ toBalanceBefore, -+ toUserState.balance, -+ _getDelegateeByType(to, toUserState, GovernancePowerType.PROPOSITION), -+ GovernancePowerType.PROPOSITION -+ ); -+ } -+ } -+ } -+ -+ /** -+ * @dev Extracts from state and returns delegated governance power (Voting, Proposition) -+ * @param userState the current state of a user -+ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) -+ **/ -+ function _getDelegatedPowerByType( -+ DelegationAwareBalance memory userState, -+ GovernancePowerType delegationType -+ ) internal pure returns (uint256) { -+ return -+ POWER_SCALE_FACTOR * -+ ( -+ delegationType == GovernancePowerType.VOTING -+ ? userState.delegatedVotingBalance -+ : userState.delegatedPropositionBalance -+ ); -+ } -+ -+ /** -+ * @dev Extracts from state and returns the delegatee of a delegator by type of governance power (Voting, Proposition) -+ * - If the delegator doesn't have any delegatee, returns address(0) -+ * @param delegator delegator -+ * @param userState the current state of a user -+ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) -+ **/ -+ function _getDelegateeByType( -+ address delegator, -+ DelegationAwareBalance memory userState, -+ GovernancePowerType delegationType -+ ) internal view returns (address) { -+ if (delegationType == GovernancePowerType.VOTING) { -+ return -+ /// With the & operation, we cover both VOTING_DELEGATED delegation and FULL_POWER_DELEGATED -+ /// as VOTING_DELEGATED is equivalent to 01 in binary and FULL_POWER_DELEGATED is equivalent to 11 -+ (uint8(userState.delegationState) & uint8(DelegationState.VOTING_DELEGATED)) != 0 -+ ? _votingDelegateeV2[delegator] -+ : address(0); -+ } -+ return + _governancePowerTransferByType( + toBalanceBefore, + toUserState.balance, +@@ -293,7 +293,7 @@ + : address(0); + } + return +- userState.delegationState >= DelegationState.PROPOSITION_DELEGATED + userState.delegationState >= uint8(DelegationState.PROPOSITION_DELEGATED) -+ ? _propositionDelegateeV2[delegator] -+ : address(0); -+ } -+ -+ /** -+ * @dev Changes user's delegatee address by type of governance power (Voting, Proposition) -+ * @param delegator delegator -+ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) -+ * @param _newDelegatee the new delegatee -+ **/ -+ function _updateDelegateeByType( -+ address delegator, -+ GovernancePowerType delegationType, -+ address _newDelegatee -+ ) internal { -+ address newDelegatee = _newDelegatee == delegator ? address(0) : _newDelegatee; -+ if (delegationType == GovernancePowerType.VOTING) { -+ _votingDelegateeV2[delegator] = newDelegatee; -+ } else { -+ _propositionDelegateeV2[delegator] = newDelegatee; -+ } -+ } -+ -+ /** -+ * @dev Updates the specific flag which signaling about existence of delegation of governance power (Voting, Proposition) -+ * @param userState a user state to change -+ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) -+ * @param willDelegate next state of delegation -+ **/ -+ function _updateDelegationFlagByType( -+ DelegationAwareBalance memory userState, -+ GovernancePowerType delegationType, -+ bool willDelegate -+ ) internal pure returns (DelegationAwareBalance memory) { -+ if (willDelegate) { -+ // Because GovernancePowerType starts from 0, we should add 1 first, then we apply bitwise OR + ? _propositionDelegateeV2[delegator] + : address(0); + } +@@ -330,16 +330,12 @@ + ) internal pure returns (DelegationAwareBalance memory) { + if (willDelegate) { + // Because GovernancePowerType starts from 0, we should add 1 first, then we apply bitwise OR +- userState.delegationState = DelegationState( +- uint8(userState.delegationState) | (uint8(delegationType) + 1) +- ); + userState.delegationState = userState.delegationState | (uint8(delegationType) + 1); -+ } else { -+ // First bitwise NEGATION, ie was 01, after XOR with 11 will be 10, -+ // then bitwise AND, which means it will keep only another delegation type if it exists + } else { + // First bitwise NEGATION, ie was 01, after XOR with 11 will be 10, + // then bitwise AND, which means it will keep only another delegation type if it exists +- userState.delegationState = DelegationState( +- uint8(userState.delegationState) & +- ((uint8(delegationType) + 1) ^ uint8(DelegationState.FULL_POWER_DELEGATED)) +- ); + userState.delegationState = userState.delegationState & + ((uint8(delegationType) + 1) ^ uint8(DelegationState.FULL_POWER_DELEGATED)); -+ } -+ return userState; -+ } -+ -+ /** -+ * @dev This is the equivalent of an ERC20 transfer(), but for a power type: an atomic transfer of a balance (power). -+ * When needed, it decreases the power of the `delegator` and when needed, it increases the power of the `delegatee` -+ * @param delegator delegator -+ * @param _delegatee the user which delegated power will change -+ * @param delegationType the type of delegation (VOTING, PROPOSITION) -+ **/ -+ function _delegateByType( -+ address delegator, -+ address _delegatee, -+ GovernancePowerType delegationType -+ ) internal { -+ // Here we unify the property that delegating power to address(0) == delegating power to yourself == no delegation -+ // So from now on, not being delegating is (exclusively) that delegatee == address(0) -+ address delegatee = _delegatee == delegator ? address(0) : _delegatee; -+ -+ // We read the whole struct before validating delegatee, because in the optimistic case -+ // (_delegatee != currentDelegatee) we will reuse userState in the rest of the function -+ DelegationAwareBalance memory delegatorState = _balances[delegator]; -+ address currentDelegatee = _getDelegateeByType(delegator, delegatorState, delegationType); -+ if (delegatee == currentDelegatee) return; -+ -+ bool delegatingNow = currentDelegatee != address(0); -+ bool willDelegateAfter = delegatee != address(0); -+ -+ if (delegatingNow) { -+ _governancePowerTransferByType(delegatorState.balance, 0, currentDelegatee, delegationType); -+ } -+ -+ if (willDelegateAfter) { -+ _governancePowerTransferByType(0, delegatorState.balance, delegatee, delegationType); -+ } -+ -+ _updateDelegateeByType(delegator, delegationType, delegatee); -+ -+ if (willDelegateAfter != delegatingNow) { -+ _balances[delegator] = _updateDelegationFlagByType( -+ delegatorState, -+ delegationType, -+ willDelegateAfter -+ ); -+ } -+ -+ emit DelegateChanged(delegator, delegatee, delegationType); -+ } -+} -\ No newline at end of file -diff -ruN AaveTokenV3.sol.orig AaveTokenV3.sol.orig ---- AaveTokenV3.sol.orig 1970-01-01 01:00:00 -+++ AaveTokenV3.sol.orig 2023-03-28 13:08:24 -@@ -0,0 +1,392 @@ -+// SPDX-License-Identifier: MIT -+pragma solidity ^0.8.0; -+ -+import {VersionedInitializable} from './utils/VersionedInitializable.sol'; -+import {IGovernancePowerDelegationToken} from './interfaces/IGovernancePowerDelegationToken.sol'; -+import {BaseAaveTokenV2} from './BaseAaveTokenV2.sol'; -+ -+contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { -+ mapping(address => address) internal _votingDelegateeV2; -+ mapping(address => address) internal _propositionDelegateeV2; -+ -+ /// @dev we assume that for the governance system 18 decimals of precision is not needed, -+ // by this constant we reduce it by 10, to 8 decimals -+ uint256 public constant POWER_SCALE_FACTOR = 1e10; -+ -+ bytes32 public constant DELEGATE_BY_TYPE_TYPEHASH = -+ keccak256( -+ 'DelegateByType(address delegator,address delegatee,GovernancePowerType delegationType,uint256 nonce,uint256 deadline)' -+ ); -+ bytes32 public constant DELEGATE_TYPEHASH = -+ keccak256('Delegate(address delegator,address delegatee,uint256 nonce,uint256 deadline)'); -+ -+ /** -+ * @dev initializes the contract upon assignment to the InitializableAdminUpgradeabilityProxy -+ */ -+ function initialize() external virtual initializer {} -+ -+ /// @inheritdoc IGovernancePowerDelegationToken -+ function delegateByType(address delegatee, GovernancePowerType delegationType) -+ external -+ virtual -+ override -+ { -+ _delegateByType(msg.sender, delegatee, delegationType); -+ } -+ -+ /// @inheritdoc IGovernancePowerDelegationToken -+ function delegate(address delegatee) external override { -+ _delegateByType(msg.sender, delegatee, GovernancePowerType.VOTING); -+ _delegateByType(msg.sender, delegatee, GovernancePowerType.PROPOSITION); -+ } -+ -+ /// @inheritdoc IGovernancePowerDelegationToken -+ function getDelegateeByType(address delegator, GovernancePowerType delegationType) -+ external -+ view -+ override -+ returns (address) -+ { -+ return _getDelegateeByType(delegator, _balances[delegator], delegationType); -+ } -+ -+ /// @inheritdoc IGovernancePowerDelegationToken -+ function getDelegates(address delegator) external view override returns (address, address) { -+ DelegationAwareBalance memory delegatorBalance = _balances[delegator]; -+ return ( -+ _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.VOTING), -+ _getDelegateeByType(delegator, delegatorBalance, GovernancePowerType.PROPOSITION) -+ ); -+ } -+ -+ /// @inheritdoc IGovernancePowerDelegationToken -+ function getPowerCurrent(address user, GovernancePowerType delegationType) -+ public -+ view -+ override -+ returns (uint256) -+ { -+ DelegationAwareBalance memory userState = _balances[user]; -+ uint256 userOwnPower = uint8(userState.delegationState) & (uint8(delegationType) + 1) == 0 -+ ? _balances[user].balance -+ : 0; -+ uint256 userDelegatedPower = _getDelegatedPowerByType(userState, delegationType); -+ return userOwnPower + userDelegatedPower; -+ } -+ -+ /// @inheritdoc IGovernancePowerDelegationToken -+ function getPowersCurrent(address user) external view override returns (uint256, uint256) { -+ return ( -+ getPowerCurrent(user, GovernancePowerType.VOTING), -+ getPowerCurrent(user, GovernancePowerType.PROPOSITION) -+ ); -+ } -+ -+ /// @inheritdoc IGovernancePowerDelegationToken -+ function metaDelegateByType( -+ address delegator, -+ address delegatee, -+ GovernancePowerType delegationType, -+ uint256 deadline, -+ uint8 v, -+ bytes32 r, -+ bytes32 s -+ ) external override { -+ require(delegator != address(0), 'INVALID_OWNER'); -+ //solium-disable-next-line -+ require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); -+ uint256 currentValidNonce = _nonces[delegator]; -+ bytes32 digest = keccak256( -+ abi.encodePacked( -+ '\x19\x01', -+ DOMAIN_SEPARATOR, -+ keccak256( -+ abi.encode( -+ DELEGATE_BY_TYPE_TYPEHASH, -+ delegator, -+ delegatee, -+ delegationType, -+ currentValidNonce, -+ deadline -+ ) -+ ) -+ ) -+ ); -+ -+ require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); -+ unchecked { -+ // Does not make sense to check because it's not realistic to reach uint256.max in nonce -+ _nonces[delegator] = currentValidNonce + 1; -+ } -+ _delegateByType(delegator, delegatee, delegationType); -+ } -+ -+ /// @inheritdoc IGovernancePowerDelegationToken -+ function metaDelegate( -+ address delegator, -+ address delegatee, -+ uint256 deadline, -+ uint8 v, -+ bytes32 r, -+ bytes32 s -+ ) external override { -+ require(delegator != address(0), 'INVALID_OWNER'); -+ //solium-disable-next-line -+ require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); -+ uint256 currentValidNonce = _nonces[delegator]; -+ bytes32 digest = keccak256( -+ abi.encodePacked( -+ '\x19\x01', -+ DOMAIN_SEPARATOR, -+ keccak256(abi.encode(DELEGATE_TYPEHASH, delegator, delegatee, currentValidNonce, deadline)) -+ ) -+ ); -+ -+ require(delegator == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); -+ unchecked { -+ // does not make sense to check because it's not realistic to reach uint256.max in nonce -+ _nonces[delegator] = currentValidNonce + 1; -+ } -+ _delegateByType(delegator, delegatee, GovernancePowerType.VOTING); -+ _delegateByType(delegator, delegatee, GovernancePowerType.PROPOSITION); -+ } -+ -+ /** -+ * @dev Modifies the delegated power of a `delegatee` account by type (VOTING, PROPOSITION). -+ * Passing the impact on the delegation of `delegatee` account before and after to reduce conditionals and not lose -+ * any precision. -+ * @param impactOnDelegationBefore how much impact a balance of another account had over the delegation of a `delegatee` -+ * before an action. -+ * For example, if the action is a delegation from one account to another, the impact before the action will be 0. -+ * @param impactOnDelegationAfter how much impact a balance of another account will have over the delegation of a `delegatee` -+ * after an action. -+ * For example, if the action is a delegation from one account to another, the impact after the action will be the whole balance -+ * of the account changing the delegatee. -+ * @param delegatee the user whom delegated governance power will be changed -+ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) -+ **/ -+ function _governancePowerTransferByType( -+ uint104 impactOnDelegationBefore, -+ uint104 impactOnDelegationAfter, -+ address delegatee, -+ GovernancePowerType delegationType -+ ) internal { -+ if (delegatee == address(0)) return; -+ if (impactOnDelegationBefore == impactOnDelegationAfter) return; -+ -+ // To make delegated balance fit into uint72 we're decreasing precision of delegated balance by POWER_SCALE_FACTOR -+ uint72 impactOnDelegationBefore72 = uint72(impactOnDelegationBefore / POWER_SCALE_FACTOR); -+ uint72 impactOnDelegationAfter72 = uint72(impactOnDelegationAfter / POWER_SCALE_FACTOR); -+ -+ if (delegationType == GovernancePowerType.VOTING) { -+ _balances[delegatee].delegatedVotingBalance = -+ _balances[delegatee].delegatedVotingBalance - -+ impactOnDelegationBefore72 + -+ impactOnDelegationAfter72; -+ } else { -+ _balances[delegatee].delegatedPropositionBalance = -+ _balances[delegatee].delegatedPropositionBalance - -+ impactOnDelegationBefore72 + -+ impactOnDelegationAfter72; -+ } -+ } -+ -+ /** -+ * @dev performs all state changes related to balance transfer and corresponding delegation changes -+ * @param from token sender -+ * @param to token recipient -+ * @param amount amount of tokens sent -+ **/ -+ function _transferWithDelegation( -+ address from, -+ address to, -+ uint256 amount -+ ) internal override { -+ if (from == to) { -+ return; -+ } -+ -+ if (from != address(0)) { -+ DelegationAwareBalance memory fromUserState = _balances[from]; -+ require(fromUserState.balance >= amount, 'ERC20: transfer amount exceeds balance'); -+ -+ uint104 fromBalanceAfter; -+ unchecked { -+ fromBalanceAfter = fromUserState.balance - uint104(amount); -+ } -+ _balances[from].balance = fromBalanceAfter; -+ if (fromUserState.delegationState != DelegationState.NO_DELEGATION) { -+ _governancePowerTransferByType( -+ fromUserState.balance, -+ fromBalanceAfter, -+ _getDelegateeByType(from, fromUserState, GovernancePowerType.VOTING), -+ GovernancePowerType.VOTING -+ ); -+ _governancePowerTransferByType( -+ fromUserState.balance, -+ fromBalanceAfter, -+ _getDelegateeByType(from, fromUserState, GovernancePowerType.PROPOSITION), -+ GovernancePowerType.PROPOSITION -+ ); -+ } -+ } -+ -+ if (to != address(0)) { -+ DelegationAwareBalance memory toUserState = _balances[to]; -+ uint104 toBalanceBefore = toUserState.balance; -+ toUserState.balance = toBalanceBefore + uint104(amount); -+ _balances[to] = toUserState; -+ -+ if (toUserState.delegationState != DelegationState.NO_DELEGATION) { -+ _governancePowerTransferByType( -+ toBalanceBefore, -+ toUserState.balance, -+ _getDelegateeByType(to, toUserState, GovernancePowerType.VOTING), -+ GovernancePowerType.VOTING -+ ); -+ _governancePowerTransferByType( -+ toBalanceBefore, -+ toUserState.balance, -+ _getDelegateeByType(to, toUserState, GovernancePowerType.PROPOSITION), -+ GovernancePowerType.PROPOSITION -+ ); -+ } -+ } -+ } -+ -+ /** -+ * @dev Extracts from state and returns delegated governance power (Voting, Proposition) -+ * @param userState the current state of a user -+ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) -+ **/ -+ function _getDelegatedPowerByType( -+ DelegationAwareBalance memory userState, -+ GovernancePowerType delegationType -+ ) internal pure returns (uint256) { -+ return -+ POWER_SCALE_FACTOR * -+ ( -+ delegationType == GovernancePowerType.VOTING -+ ? userState.delegatedVotingBalance -+ : userState.delegatedPropositionBalance -+ ); -+ } -+ -+ /** -+ * @dev Extracts from state and returns the delegatee of a delegator by type of governance power (Voting, Proposition) -+ * - If the delegator doesn't have any delegatee, returns address(0) -+ * @param delegator delegator -+ * @param userState the current state of a user -+ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) -+ **/ -+ function _getDelegateeByType( -+ address delegator, -+ DelegationAwareBalance memory userState, -+ GovernancePowerType delegationType -+ ) internal view returns (address) { -+ if (delegationType == GovernancePowerType.VOTING) { -+ return -+ /// With the & operation, we cover both VOTING_DELEGATED delegation and FULL_POWER_DELEGATED -+ /// as VOTING_DELEGATED is equivalent to 01 in binary and FULL_POWER_DELEGATED is equivalent to 11 -+ (uint8(userState.delegationState) & uint8(DelegationState.VOTING_DELEGATED)) != 0 -+ ? _votingDelegateeV2[delegator] -+ : address(0); -+ } -+ return -+ userState.delegationState >= DelegationState.PROPOSITION_DELEGATED -+ ? _propositionDelegateeV2[delegator] -+ : address(0); -+ } -+ -+ /** -+ * @dev Changes user's delegatee address by type of governance power (Voting, Proposition) -+ * @param delegator delegator -+ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) -+ * @param _newDelegatee the new delegatee -+ **/ -+ function _updateDelegateeByType( -+ address delegator, -+ GovernancePowerType delegationType, -+ address _newDelegatee -+ ) internal { -+ address newDelegatee = _newDelegatee == delegator ? address(0) : _newDelegatee; -+ if (delegationType == GovernancePowerType.VOTING) { -+ _votingDelegateeV2[delegator] = newDelegatee; -+ } else { -+ _propositionDelegateeV2[delegator] = newDelegatee; -+ } -+ } -+ -+ /** -+ * @dev Updates the specific flag which signaling about existence of delegation of governance power (Voting, Proposition) -+ * @param userState a user state to change -+ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) -+ * @param willDelegate next state of delegation -+ **/ -+ function _updateDelegationFlagByType( -+ DelegationAwareBalance memory userState, -+ GovernancePowerType delegationType, -+ bool willDelegate -+ ) internal pure returns (DelegationAwareBalance memory) { -+ if (willDelegate) { -+ // Because GovernancePowerType starts from 0, we should add 1 first, then we apply bitwise OR -+ userState.delegationState = DelegationState( -+ uint8(userState.delegationState) | (uint8(delegationType) + 1) -+ ); -+ } else { -+ // First bitwise NEGATION, ie was 01, after XOR with 11 will be 10, -+ // then bitwise AND, which means it will keep only another delegation type if it exists -+ userState.delegationState = DelegationState( -+ uint8(userState.delegationState) & -+ ((uint8(delegationType) + 1) ^ uint8(DelegationState.FULL_POWER_DELEGATED)) -+ ); -+ } -+ return userState; -+ } -+ -+ /** -+ * @dev This is the equivalent of an ERC20 transfer(), but for a power type: an atomic transfer of a balance (power). -+ * When needed, it decreases the power of the `delegator` and when needed, it increases the power of the `delegatee` -+ * @param delegator delegator -+ * @param _delegatee the user which delegated power will change -+ * @param delegationType the type of delegation (VOTING, PROPOSITION) -+ **/ -+ function _delegateByType( -+ address delegator, -+ address _delegatee, -+ GovernancePowerType delegationType -+ ) internal { -+ // Here we unify the property that delegating power to address(0) == delegating power to yourself == no delegation -+ // So from now on, not being delegating is (exclusively) that delegatee == address(0) -+ address delegatee = _delegatee == delegator ? address(0) : _delegatee; -+ -+ // We read the whole struct before validating delegatee, because in the optimistic case -+ // (_delegatee != currentDelegatee) we will reuse userState in the rest of the function -+ DelegationAwareBalance memory delegatorState = _balances[delegator]; -+ address currentDelegatee = _getDelegateeByType(delegator, delegatorState, delegationType); -+ if (delegatee == currentDelegatee) return; -+ -+ bool delegatingNow = currentDelegatee != address(0); -+ bool willDelegateAfter = delegatee != address(0); -+ -+ if (delegatingNow) { -+ _governancePowerTransferByType(delegatorState.balance, 0, currentDelegatee, delegationType); -+ } -+ -+ if (willDelegateAfter) { -+ _governancePowerTransferByType(0, delegatorState.balance, delegatee, delegationType); -+ } -+ -+ _updateDelegateeByType(delegator, delegationType, delegatee); -+ -+ if (willDelegateAfter != delegatingNow) { -+ _balances[delegator] = _updateDelegationFlagByType( -+ delegatorState, -+ delegationType, -+ willDelegateAfter -+ ); -+ } -+ -+ emit DelegateChanged(delegator, delegatee, delegationType); -+ } -+} -\ No newline at end of file -diff -ruN BaseAaveToken.sol BaseAaveToken.sol ---- BaseAaveToken.sol 1970-01-01 01:00:00 -+++ BaseAaveToken.sol 2023-03-28 13:08:24 -@@ -0,0 +1,162 @@ -+// SPDX-License-Identifier: MIT -+pragma solidity ^0.8.0; -+ -+import {Context} from './lib/openzeppelin-contracts/contracts/utils/Context.sol'; -+import {IERC20} from './lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; -+import {IERC20Metadata} from './lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; -+ -+// Inspired by OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) -+abstract contract BaseAaveToken is Context, IERC20Metadata { -+ enum DelegationState { -+ NO_DELEGATION, -+ VOTING_DELEGATED, -+ PROPOSITION_DELEGATED, -+ FULL_POWER_DELEGATED -+ } -+ -+ // reorder fields to make hooks syntax simpler -+ struct DelegationAwareBalance { -+ uint104 balance; -+ uint72 delegatedPropositionBalance; -+ uint72 delegatedVotingBalance; -+ uint8 delegationState; // refactored from enum -+ } -+ -+ mapping(address => DelegationAwareBalance) internal _balances; -+ -+ mapping(address => mapping(address => uint256)) internal _allowances; -+ -+ uint256 internal _totalSupply; -+ -+ string internal _name; -+ string internal _symbol; -+ -+ // @dev DEPRECATED -+ // kept for backwards compatibility with old storage layout -+ uint8 private ______DEPRECATED_OLD_ERC20_DECIMALS; -+ -+ /** -+ * @dev Returns the name of the token. -+ */ -+ function name() public view virtual override returns (string memory) { -+ return _name; -+ } -+ -+ /** -+ * @dev Returns the symbol of the token, usually a shorter version of the -+ * name. -+ */ -+ function symbol() public view virtual override returns (string memory) { -+ return _symbol; -+ } -+ -+ function decimals() public view virtual override returns (uint8) { -+ return 18; -+ } -+ -+ function totalSupply() public view virtual override returns (uint256) { -+ return _totalSupply; -+ } -+ -+ function balanceOf(address account) public view virtual override returns (uint256) { -+ return _balances[account].balance; -+ } -+ -+ function transfer(address to, uint256 amount) public virtual override returns (bool) { -+ address owner = _msgSender(); -+ _transfer(owner, to, amount); -+ return true; -+ } -+ -+ function allowance(address owner, address spender) -+ public -+ view -+ virtual -+ override -+ returns (uint256) -+ { -+ return _allowances[owner][spender]; -+ } -+ -+ function approve(address spender, uint256 amount) public virtual override returns (bool) { -+ address owner = _msgSender(); -+ _approve(owner, spender, amount); -+ return true; -+ } -+ -+ function transferFrom( -+ address from, -+ address to, -+ uint256 amount -+ ) public virtual override returns (bool) { -+ address spender = _msgSender(); -+ _spendAllowance(from, spender, amount); -+ _transfer(from, to, amount); -+ return true; -+ } -+ -+ function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) { -+ address owner = _msgSender(); -+ _approve(owner, spender, _allowances[owner][spender] + addedValue); -+ return true; -+ } -+ -+ function decreaseAllowance(address spender, uint256 subtractedValue) -+ public -+ virtual -+ returns (bool) -+ { -+ address owner = _msgSender(); -+ uint256 currentAllowance = _allowances[owner][spender]; -+ require(currentAllowance >= subtractedValue, 'ERC20: decreased allowance below zero'); -+ unchecked { -+ _approve(owner, spender, currentAllowance - subtractedValue); -+ } -+ -+ return true; -+ } -+ -+ function _transfer( -+ address from, -+ address to, -+ uint256 amount -+ ) internal virtual { -+ require(from != address(0), 'ERC20: transfer from the zero address'); -+ require(to != address(0), 'ERC20: transfer to the zero address'); -+ -+ _transferWithDelegation(from, to, amount); -+ emit Transfer(from, to, amount); -+ } -+ -+ function _approve( -+ address owner, -+ address spender, -+ uint256 amount -+ ) internal virtual { -+ require(owner != address(0), 'ERC20: approve from the zero address'); -+ require(spender != address(0), 'ERC20: approve to the zero address'); -+ -+ _allowances[owner][spender] = amount; -+ emit Approval(owner, spender, amount); -+ } -+ -+ function _spendAllowance( -+ address owner, -+ address spender, -+ uint256 amount -+ ) internal virtual { -+ uint256 currentAllowance = allowance(owner, spender); -+ if (currentAllowance != type(uint256).max) { -+ require(currentAllowance >= amount, 'ERC20: insufficient allowance'); -+ unchecked { -+ _approve(owner, spender, currentAllowance - amount); -+ } -+ } -+ } -+ -+ function _transferWithDelegation( -+ address from, -+ address to, -+ uint256 amount -+ ) internal virtual {} -+} -diff -ruN BaseAaveTokenV2.sol BaseAaveTokenV2.sol ---- BaseAaveTokenV2.sol 1970-01-01 01:00:00 -+++ BaseAaveTokenV2.sol 2023-03-28 13:08:24 -@@ -0,0 +1,77 @@ -+// SPDX-License-Identifier: MIT -+pragma solidity ^0.8.0; -+ -+import {VersionedInitializable} from './utils/VersionedInitializable.sol'; -+import {BaseAaveToken} from './BaseAaveToken.sol'; -+ -+abstract contract BaseAaveTokenV2 is BaseAaveToken, VersionedInitializable { -+ /// @dev owner => next valid nonce to submit with permit() -+ mapping(address => uint256) public _nonces; -+ -+ ///////// @dev DEPRECATED from AaveToken v1 ////////////////////////// -+ //////// kept for backwards compatibility with old storage layout //// -+ uint256[3] private ______DEPRECATED_FROM_AAVE_V1; -+ ///////// @dev END OF DEPRECATED from AaveToken v1 ////////////////////////// -+ -+ bytes32 public DOMAIN_SEPARATOR; -+ -+ ///////// @dev DEPRECATED from AaveToken v2 ////////////////////////// -+ //////// kept for backwards compatibility with old storage layout //// -+ uint256[4] private ______DEPRECATED_FROM_AAVE_V2; -+ ///////// @dev END OF DEPRECATED from AaveToken v2 ////////////////////////// -+ -+ bytes public constant EIP712_REVISION = bytes('1'); -+ bytes32 internal constant EIP712_DOMAIN = -+ keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'); -+ bytes32 public constant PERMIT_TYPEHASH = -+ keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'); -+ -+ uint256 public constant REVISION = 3; -+ -+ /** -+ * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md -+ * @param owner the owner of the funds -+ * @param spender the spender -+ * @param value the amount -+ * @param deadline the deadline timestamp, type(uint256).max for no deadline -+ * @param v signature param -+ * @param s signature param -+ * @param r signature param -+ */ -+ -+ function permit( -+ address owner, -+ address spender, -+ uint256 value, -+ uint256 deadline, -+ uint8 v, -+ bytes32 r, -+ bytes32 s -+ ) external { -+ require(owner != address(0), 'INVALID_OWNER'); -+ //solium-disable-next-line -+ require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); -+ uint256 currentValidNonce = _nonces[owner]; -+ bytes32 digest = keccak256( -+ abi.encodePacked( -+ '\x19\x01', -+ DOMAIN_SEPARATOR, -+ keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline)) -+ ) -+ ); -+ -+ require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); -+ unchecked { -+ // does not make sense to check because it's not realistic to reach uint256.max in nonce -+ _nonces[owner] = currentValidNonce + 1; -+ } -+ _approve(owner, spender, value); -+ } -+ -+ /** -+ * @dev returns the revision of the implementation contract -+ */ -+ function getRevision() internal pure override returns (uint256) { -+ return REVISION; -+ } -+} -diff -ruN BaseAaveTokenV3.sol BaseAaveTokenV3.sol ---- BaseAaveTokenV3.sol 1970-01-01 01:00:00 -+++ BaseAaveTokenV3.sol 2023-03-28 13:08:24 -@@ -0,0 +1,84 @@ -+// SPDX-License-Identifier: MIT -+ -+pragma solidity ^0.8.0; -+ -+import {VersionedInitializable} from './utils/VersionedInitializable.sol'; -+ -+import {BaseAaveToken} from './BaseAaveToken.sol'; -+ -+abstract contract BaseAaveTokenV2 is BaseAaveToken, VersionedInitializable { -+ /// @dev owner => next valid nonce to submit with permit() -+ mapping(address => uint256) public _nonces; -+ -+ ///////// @dev DEPRECATED from AaveToken v1 ////////////////////////// -+ //////// kept for backwards compatibility with old storage layout //// -+ uint256[3] private ______DEPRECATED_FROM_AAVE_V1; -+ ///////// @dev END OF DEPRECATED from AaveToken v1 ////////////////////////// -+ -+ bytes32 public DOMAIN_SEPARATOR; -+ -+ ///////// @dev DEPRECATED from AaveToken v2 ////////////////////////// -+ //////// kept for backwards compatibility with old storage layout //// -+ uint256[4] private ______DEPRECATED_FROM_AAVE_V2; -+ ///////// @dev END OF DEPRECATED from AaveToken v2 ////////////////////////// -+ -+ bytes public constant EIP712_REVISION = bytes('1'); -+ bytes32 internal constant EIP712_DOMAIN = -+ keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'); -+ bytes32 public constant PERMIT_TYPEHASH = -+ keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'); -+ -+ uint256 public constant REVISION = 3; // TODO: CHECK, but most probably was 2 before -+ -+ /** -+ * @dev initializes the contract upon assignment to the InitializableAdminUpgradeabilityProxy -+ */ -+ function initialize() external initializer {} -+ -+ /** -+ * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md -+ * @param owner the owner of the funds -+ * @param spender the spender -+ * @param value the amount -+ * @param deadline the deadline timestamp, type(uint256).max for no deadline -+ * @param v signature param -+ * @param s signature param -+ * @param r signature param -+ */ -+ -+ function permit( -+ address owner, -+ address spender, -+ uint256 value, -+ uint256 deadline, -+ uint8 v, -+ bytes32 r, -+ bytes32 s -+ ) external { -+ require(owner != address(0), 'INVALID_OWNER'); -+ //solium-disable-next-line -+ require(block.timestamp <= deadline, 'INVALID_EXPIRATION'); -+ uint256 currentValidNonce = _nonces[owner]; -+ bytes32 digest = keccak256( -+ abi.encodePacked( -+ '\x19\x01', -+ DOMAIN_SEPARATOR, -+ keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline)) -+ ) -+ ); -+ -+ require(owner == ecrecover(digest, v, r, s), 'INVALID_SIGNATURE'); -+ unchecked { -+ // does not make sense to check because it's not realistic to reach uint256.max in nonce -+ _nonces[owner] = currentValidNonce + 1; -+ } -+ _approve(owner, spender, value); -+ } -+ -+ /** -+ * @dev returns the revision of the implementation contract -+ */ -+ function getRevision() internal pure override returns (uint256) { -+ return REVISION; -+ } -+} -diff -ruN interfaces/IGovernancePowerDelegationToken.sol interfaces/IGovernancePowerDelegationToken.sol ---- interfaces/IGovernancePowerDelegationToken.sol 1970-01-01 01:00:00 -+++ interfaces/IGovernancePowerDelegationToken.sol 2023-03-28 13:08:24 -@@ -0,0 +1,117 @@ -+// SPDX-License-Identifier: MIT -+pragma solidity ^0.8.0; -+ -+interface IGovernancePowerDelegationToken { -+ enum GovernancePowerType { -+ VOTING, -+ PROPOSITION -+ } -+ -+ /** -+ * @dev emitted when a user delegates to another -+ * @param delegator the user which delegated governance power -+ * @param delegatee the delegatee -+ * @param delegationType the type of delegation (VOTING, PROPOSITION) -+ **/ -+ event DelegateChanged( -+ address indexed delegator, -+ address indexed delegatee, -+ GovernancePowerType delegationType -+ ); -+ -+ // @dev we removed DelegatedPowerChanged event because to reconstruct the full state of the system, -+ // is enough to have Transfer and DelegateChanged TODO: document it -+ -+ /** -+ * @dev delegates the specific power to a delegatee -+ * @param delegatee the user which delegated power will change -+ * @param delegationType the type of delegation (VOTING, PROPOSITION) -+ **/ -+ function delegateByType(address delegatee, GovernancePowerType delegationType) external; -+ -+ /** -+ * @dev delegates all the governance powers to a specific user -+ * @param delegatee the user to which the powers will be delegated -+ **/ -+ function delegate(address delegatee) external; -+ -+ /** -+ * @dev returns the delegatee of an user -+ * @param delegator the address of the delegator -+ * @param delegationType the type of delegation (VOTING, PROPOSITION) -+ * @return address of the specified delegatee -+ **/ -+ function getDelegateeByType(address delegator, GovernancePowerType delegationType) -+ external -+ view -+ returns (address); -+ -+ /** -+ * @dev returns delegates of an user -+ * @param delegator the address of the delegator -+ * @return a tuple of addresses the VOTING and PROPOSITION delegatee -+ **/ -+ function getDelegates(address delegator) -+ external -+ view -+ returns (address, address); -+ -+ /** -+ * @dev returns the current voting or proposition power of a user. -+ * @param user the user -+ * @param delegationType the type of delegation (VOTING, PROPOSITION) -+ * @return the current voting or proposition power of a user -+ **/ -+ function getPowerCurrent(address user, GovernancePowerType delegationType) -+ external -+ view -+ returns (uint256); -+ -+ /** -+ * @dev returns the current voting or proposition power of a user. -+ * @param user the user -+ * @return the current voting and proposition power of a user -+ **/ -+ function getPowersCurrent(address user) -+ external -+ view -+ returns (uint256, uint256); -+ -+ /** -+ * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md -+ * @param delegator the owner of the funds -+ * @param delegatee the user to who owner delegates his governance power -+ * @param delegationType the type of governance power delegation (VOTING, PROPOSITION) -+ * @param deadline the deadline timestamp, type(uint256).max for no deadline -+ * @param v signature param -+ * @param s signature param -+ * @param r signature param -+ */ -+ function metaDelegateByType( -+ address delegator, -+ address delegatee, -+ GovernancePowerType delegationType, -+ uint256 deadline, -+ uint8 v, -+ bytes32 r, -+ bytes32 s -+ ) external; -+ -+ /** -+ * @dev implements the permit function as for https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md -+ * @param delegator the owner of the funds -+ * @param delegatee the user to who delegator delegates his voting and proposition governance power -+ * @param deadline the deadline timestamp, type(uint256).max for no deadline -+ * @param v signature param -+ * @param s signature param -+ * @param r signature param -+ */ -+ function metaDelegate( -+ address delegator, -+ address delegatee, -+ uint256 deadline, -+ uint8 v, -+ bytes32 r, -+ bytes32 s -+ ) external; -+} -diff -ruN interfaces/ITransferHook.sol interfaces/ITransferHook.sol ---- interfaces/ITransferHook.sol 1970-01-01 01:00:00 -+++ interfaces/ITransferHook.sol 2023-03-28 13:08:24 -@@ -0,0 +1,10 @@ -+// SPDX-License-Identifier: MIT -+pragma solidity ^0.8.0; -+ -+interface ITransferHook { -+ function onTransfer( -+ address from, -+ address to, -+ uint256 amount -+ ) external; -+} + } + return userState; + } diff -ruN src/BaseAaveToken.sol src/BaseAaveToken.sol ---- src/BaseAaveToken.sol 2023-03-28 13:52:04 -+++ src/BaseAaveToken.sol 2023-03-28 13:51:59 -@@ -1,10 +1,6 @@ - // SPDX-License-Identifier: MIT - pragma solidity ^0.8.0; - --import {Context} from '../lib/openzeppelin-contracts/contracts/utils/Context.sol'; --import {IERC20} from '../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol'; --import {IERC20Metadata} from '../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; -- - // Inspired by OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/ERC20.sol) - abstract contract BaseAaveToken is Context, IERC20Metadata { - enum DelegationState { -@@ -16,10 +12,10 @@ +--- src/BaseAaveToken.sol 2023-03-28 15:10:26 ++++ src/BaseAaveToken.sol 2023-03-28 15:10:24 +@@ -16,10 +16,10 @@ // reorder fields to make hooks syntax simpler struct DelegationAwareBalance { @@ -1284,289 +64,3 @@ diff -ruN src/BaseAaveToken.sol src/BaseAaveToken.sol } mapping(address => DelegationAwareBalance) internal _balances; -diff -ruN test/InternalDelegationFunctionsTest.t.sol test/InternalDelegationFunctionsTest.t.sol ---- test/InternalDelegationFunctionsTest.t.sol 1970-01-01 01:00:00 -+++ test/InternalDelegationFunctionsTest.t.sol 2023-03-28 13:08:24 -@@ -0,0 +1,127 @@ -+// SPDX-License-Identifier: MIT -+pragma solidity ^0.8.0; -+ -+import {Strings} from '../../lib/openzeppelin-contracts/contracts/utils/Strings.sol'; -+import {IERC20Metadata} from '../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; -+import {AaveTokenV3} from '../AaveTokenV3.sol'; -+ -+import {AaveUtils, console} from './utils/AaveUtils.sol'; -+ -+contract StorageTest is AaveTokenV3, AaveUtils { -+ function setUp() public {} -+ -+ function testFor_getDelegatedPowerByType() public { -+ DelegationAwareBalance memory userState; -+ userState.delegatedPropositionBalance = 100; -+ userState.delegatedVotingBalance = 200; -+ assertEq( -+ _getDelegatedPowerByType(userState, GovernancePowerType.VOTING), -+ userState.delegatedVotingBalance * POWER_SCALE_FACTOR -+ ); -+ assertEq( -+ _getDelegatedPowerByType(userState, GovernancePowerType.PROPOSITION), -+ userState.delegatedPropositionBalance * POWER_SCALE_FACTOR -+ ); -+ } -+ -+ function testFor_getDelegateeByType() public { -+ address user = address(0x1); -+ address user2 = address(0x2); -+ address user3 = address(0x3); -+ DelegationAwareBalance memory userState; -+ -+ _votingDelegateeV2[user] = address(user2); -+ _propositionDelegateeV2[user] = address(user3); -+ -+ userState.delegationState = DelegationState.VOTING_DELEGATED; -+ assertEq(_getDelegateeByType(user, userState, GovernancePowerType.VOTING), user2); -+ assertEq(_getDelegateeByType(user, userState, GovernancePowerType.PROPOSITION), address(0)); -+ -+ userState.delegationState = DelegationState.PROPOSITION_DELEGATED; -+ assertEq(_getDelegateeByType(user, userState, GovernancePowerType.VOTING), address(0)); -+ assertEq(_getDelegateeByType(user, userState, GovernancePowerType.PROPOSITION), user3); -+ -+ userState.delegationState = DelegationState.FULL_POWER_DELEGATED; -+ assertEq(_getDelegateeByType(user, userState, GovernancePowerType.VOTING), user2); -+ assertEq(_getDelegateeByType(user, userState, GovernancePowerType.PROPOSITION), user3); -+ } -+ -+ function _setDelegationStateAndTest( -+ DelegationState initialState, -+ GovernancePowerType governancePowerType, -+ bool willDelegate, -+ DelegationState expectedState -+ ) internal { -+ DelegationAwareBalance memory userState; -+ DelegationAwareBalance memory updatedUserState; -+ userState.delegationState = initialState; -+ updatedUserState = _updateDelegationFlagByType(userState, governancePowerType, willDelegate); -+ assertTrue( -+ updatedUserState.delegationState == expectedState, -+ Strings.toString(uint8(updatedUserState.delegationState)) -+ ); -+ } -+ -+ function testFor_updateDelegationFlagByType() public { -+ _setDelegationStateAndTest( -+ DelegationState.NO_DELEGATION, -+ GovernancePowerType.VOTING, -+ true, -+ DelegationState.VOTING_DELEGATED -+ ); -+ _setDelegationStateAndTest( -+ DelegationState.NO_DELEGATION, -+ GovernancePowerType.VOTING, -+ false, -+ DelegationState.NO_DELEGATION -+ ); -+ _setDelegationStateAndTest( -+ DelegationState.VOTING_DELEGATED, -+ GovernancePowerType.VOTING, -+ true, -+ DelegationState.VOTING_DELEGATED -+ ); -+ _setDelegationStateAndTest( -+ DelegationState.FULL_POWER_DELEGATED, -+ GovernancePowerType.VOTING, -+ false, -+ DelegationState.PROPOSITION_DELEGATED -+ ); -+ _setDelegationStateAndTest( -+ DelegationState.NO_DELEGATION, -+ GovernancePowerType.PROPOSITION, -+ true, -+ DelegationState.PROPOSITION_DELEGATED -+ ); -+ _setDelegationStateAndTest( -+ DelegationState.PROPOSITION_DELEGATED, -+ GovernancePowerType.PROPOSITION, -+ false, -+ DelegationState.NO_DELEGATION -+ ); -+ _setDelegationStateAndTest( -+ DelegationState.PROPOSITION_DELEGATED, -+ GovernancePowerType.VOTING, -+ true, -+ DelegationState.FULL_POWER_DELEGATED -+ ); -+ _setDelegationStateAndTest( -+ DelegationState.FULL_POWER_DELEGATED, -+ GovernancePowerType.VOTING, -+ true, -+ DelegationState.FULL_POWER_DELEGATED -+ ); -+ _setDelegationStateAndTest( -+ DelegationState.VOTING_DELEGATED, -+ GovernancePowerType.PROPOSITION, -+ true, -+ DelegationState.FULL_POWER_DELEGATED -+ ); -+ _setDelegationStateAndTest( -+ DelegationState.FULL_POWER_DELEGATED, -+ GovernancePowerType.PROPOSITION, -+ true, -+ DelegationState.FULL_POWER_DELEGATED -+ ); -+ } -+} -diff -ruN test/StorageTest.t.sol test/StorageTest.t.sol ---- test/StorageTest.t.sol 1970-01-01 01:00:00 -+++ test/StorageTest.t.sol 2023-03-28 13:08:24 -@@ -0,0 +1,44 @@ -+// SPDX-License-Identifier: MIT -+pragma solidity ^0.8.0; -+ -+import {IERC20Metadata} from '../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; -+import {AaveTokenV3} from '../AaveTokenV3.sol'; -+ -+import {AaveUtils, console} from './utils/AaveUtils.sol'; -+ -+contract StorageTest is AaveUtils { -+ function setUp() public { -+ revertAaveImplementationUpdate(); -+ } -+ -+ function testForBaseMetadata() public { -+ string memory nameBefore = AAVE_TOKEN.name(); -+ string memory symbolBefore = AAVE_TOKEN.symbol(); -+ uint256 decimalsBefore = AAVE_TOKEN.decimals(); -+ -+ updateAaveImplementation(AAVE_IMPLEMENTATION_V3); -+ -+ assertEq(AAVE_TOKEN.name(), nameBefore); -+ assertEq(AAVE_TOKEN.symbol(), symbolBefore); -+ assertEq(AAVE_TOKEN.decimals(), decimalsBefore); -+ } -+ -+ function testForTotalSupply() public { -+ uint256 totalSupplyBefore = AAVE_TOKEN.totalSupply(); -+ -+ updateAaveImplementation(AAVE_IMPLEMENTATION_V3); -+ -+ assertEq(AAVE_TOKEN.totalSupply(), totalSupplyBefore); -+ } -+ -+ function testForBalances() public { -+ uint256[] memory balancesBefore = new uint256[](AAVE_HOLDERS.length); -+ for (uint256 i = 0; i < AAVE_HOLDERS.length; i += 1) { -+ balancesBefore[i] = AAVE_TOKEN.balanceOf(AAVE_HOLDERS[i]); -+ } -+ updateAaveImplementation(AAVE_IMPLEMENTATION_V3); -+ for (uint256 i = 0; i < AAVE_HOLDERS.length; i += 1) { -+ assertEq(AAVE_TOKEN.balanceOf(AAVE_HOLDERS[i]), balancesBefore[i]); -+ } -+ } -+} -diff -ruN test/utils/AaveUtils.sol test/utils/AaveUtils.sol ---- test/utils/AaveUtils.sol 1970-01-01 01:00:00 -+++ test/utils/AaveUtils.sol 2023-03-28 13:08:24 -@@ -0,0 +1,47 @@ -+// SPDX-License-Identifier: MIT -+pragma solidity ^0.8.0; -+ -+import 'forge-std/Test.sol'; -+ -+import {IERC20Metadata} from '../../../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol'; -+ -+import {AaveTokenV3} from '../../AaveTokenV3.sol'; -+ -+import {IBaseAdminUpgradeabilityProxy} from './IBaseAdminUpgradeabilityProxy.sol'; -+ -+abstract contract AaveUtils is Test { -+ address[] public AAVE_HOLDERS; -+ IERC20Metadata public constant AAVE_TOKEN = -+ IERC20Metadata(0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9); -+ -+ address public constant AAVE_V2_IMPLEMENTATION = 0xC13eac3B4F9EED480045113B7af00F7B5655Ece8; -+ -+ address public constant AAVE_TOKEN_PROXY_ADMIN = 0x61910EcD7e8e942136CE7Fe7943f956cea1CC2f7; -+ address public AAVE_IMPLEMENTATION_V3; -+ -+ constructor() { -+ AAVE_IMPLEMENTATION_V3 = address(new AaveTokenV3()); -+ AAVE_HOLDERS = new address[](10); -+ AAVE_HOLDERS = [ -+ 0x4da27a545c0c5B758a6BA100e3a049001de870f5, -+ 0xFFC97d72E13E01096502Cb8Eb52dEe56f74DAD7B, -+ 0x25F2226B597E8F9514B3F68F00f494cF4f286491, -+ 0xC697051d1C6296C24aE3bceF39acA743861D9A81, -+ 0xBE0eB53F46cd790Cd13851d5EFf43D12404d33E8, -+ 0x317625234562B1526Ea2FaC4030Ea499C5291de4, -+ 0x47ac0Fb4F2D84898e4D9E7b4DaB3C24507a6D503, -+ 0xF977814e90dA44bFA03b6295A0616a897441aceC, -+ 0x26a78D5b6d7a7acEEDD1e6eE3229b372A624d8b7, -+ 0x28C6c06298d514Db089934071355E5743bf21d60 -+ ]; -+ } -+ -+ function updateAaveImplementation(address newImplementation) public { -+ vm.prank(AAVE_TOKEN_PROXY_ADMIN); -+ IBaseAdminUpgradeabilityProxy(address(AAVE_TOKEN)).upgradeTo(newImplementation); -+ } -+ -+ function revertAaveImplementationUpdate() public { -+ updateAaveImplementation(AAVE_V2_IMPLEMENTATION); -+ } -+} -diff -ruN test/utils/IBaseAdminUpgradeabilityProxy.sol test/utils/IBaseAdminUpgradeabilityProxy.sol ---- test/utils/IBaseAdminUpgradeabilityProxy.sol 1970-01-01 01:00:00 -+++ test/utils/IBaseAdminUpgradeabilityProxy.sol 2023-03-28 13:08:24 -@@ -0,0 +1,6 @@ -+// SPDX-License-Identifier: MIT -+pragma solidity ^0.8.0; -+ -+interface IBaseAdminUpgradeabilityProxy { -+ function upgradeTo(address newImplementation) external; -+} -diff -ruN utils/VersionedInitializable.sol utils/VersionedInitializable.sol ---- utils/VersionedInitializable.sol 1970-01-01 01:00:00 -+++ utils/VersionedInitializable.sol 2023-03-28 13:08:24 -@@ -0,0 +1,42 @@ -+// SPDX-License-Identifier: agpl-3.0 -+pragma solidity ^0.8.0; -+ -+/** -+ * @title VersionedInitializable -+ * -+ * @dev Helper contract to support initializer functions. To use it, replace -+ * the constructor with a function that has the `initializer` modifier. -+ * WARNING: Unlike constructors, initializer functions must be manually -+ * invoked. This applies both to deploying an Initializable contract, as well -+ * as extending an Initializable contract via inheritance. -+ * WARNING: When used with inheritance, manual care must be taken to not invoke -+ * a parent initializer twice, or ensure that all initializers are idempotent, -+ * because this is not dealt with automatically as with constructors. -+ * -+ * @author Aave, inspired by the OpenZeppelin Initializable contract -+ */ -+abstract contract VersionedInitializable { -+ /** -+ * @dev Indicates that the contract has been initialized. -+ */ -+ uint256 internal lastInitializedRevision = 0; -+ -+ /** -+ * @dev Modifier to use in the initializer function of a contract. -+ */ -+ modifier initializer() { -+ uint256 revision = getRevision(); -+ require(revision > lastInitializedRevision, 'Contract instance has already been initialized'); -+ -+ lastInitializedRevision = revision; -+ -+ _; -+ } -+ -+ /// @dev returns the revision number of the contract. -+ /// Needs to be defined in the inherited class as a constant. -+ function getRevision() internal pure virtual returns (uint256); -+ -+ // Reserved storage space to allow for layout changes in the future. -+ uint256[50] private ______gap; -+} From 37c7f8a45f000d0adb5a29ef1ef4370d3e8d56cf Mon Sep 17 00:00:00 2001 From: Tadeas Kucera Date: Tue, 28 Mar 2023 15:49:38 +0200 Subject: [PATCH 58/58] Harness import contracts from munged/src instead od munged --- certora/harness/AaveTokenV3HarnessStorage.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/certora/harness/AaveTokenV3HarnessStorage.sol b/certora/harness/AaveTokenV3HarnessStorage.sol index 4c77b29..d9b746c 100644 --- a/certora/harness/AaveTokenV3HarnessStorage.sol +++ b/certora/harness/AaveTokenV3HarnessStorage.sol @@ -4,7 +4,7 @@ This is an extension of the harnessed AaveTokenV3 with added getters on the _balances fields. The imported harnessed AaveTokenV3 contract uses uint8 instead of an enum for delegation state. - + This modification is introduced to bypass a current Certora Prover limitation on accessing enum fields inside CVL hooks @@ -12,7 +12,7 @@ pragma solidity ^0.8.0; -import {AaveTokenV3} from "../munged/AaveTokenV3.sol"; +import {AaveTokenV3} from "../munged/src/AaveTokenV3.sol"; contract AaveTokenV3Harness is AaveTokenV3 { function getBalance(address user) view public returns (uint104) {