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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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/28] 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 e0fe32bc1a284050f990ce3402a43b69e17a4b03 Mon Sep 17 00:00:00 2001 From: Yura Sherman Date: Wed, 20 Jul 2022 14:56:51 +0300 Subject: [PATCH 24/28] initial setup commit --- certora/harness/AaveTokenV3Harness.sol | 2 +- certora/harness/AaveTokenV3Harness_old.sol | 433 --------------- certora/harness/BaseAaveTokenHarness.sol | 161 ++++++ certora/harness/BaseAaveTokenV2Harness.sol | 82 +++ certora/properties.md | 43 -- certora/scripts/runComplexity.sh | 7 - certora/scripts/setup.sh | 5 +- certora/scripts/verifyBgdSpec.sh | 14 - certora/specs/bgdSpec.spec | 599 --------------------- certora/specs/complexity.spec | 103 ---- certora/specs/setup.spec | 50 +- 11 files changed, 290 insertions(+), 1209 deletions(-) delete mode 100644 certora/harness/AaveTokenV3Harness_old.sol create mode 100644 certora/harness/BaseAaveTokenHarness.sol create mode 100644 certora/harness/BaseAaveTokenV2Harness.sol delete mode 100644 certora/properties.md delete mode 100755 certora/scripts/runComplexity.sh delete mode 100755 certora/scripts/verifyBgdSpec.sh delete mode 100644 certora/specs/bgdSpec.spec delete mode 100644 certora/specs/complexity.spec diff --git a/certora/harness/AaveTokenV3Harness.sol b/certora/harness/AaveTokenV3Harness.sol index 1e24b2e..f91de1d 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; 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/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/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/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/verifyBgdSpec.sh b/certora/scripts/verifyBgdSpec.sh deleted file mode 100755 index 7ddf709..0000000 --- a/certora/scripts/verifyBgdSpec.sh +++ /dev/null @@ -1,14 +0,0 @@ -if [[ "$1" ]] -then - RULE="--rule $1" -fi - -certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ - --verify AaveTokenV3:certora/specs/bgdSpec.spec \ - --rule $1 \ - --solc solc8.13 \ - --optimistic_loop \ - --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 deleted file mode 100644 index aa67110..0000000 --- a/certora/specs/bgdSpec.spec +++ /dev/null @@ -1,599 +0,0 @@ -// 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()); -} - -// 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) { - 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); - - // 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; - -} - -/** - 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()); - 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); - 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/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/setup.spec b/certora/specs/setup.spec index 12354aa..077401f 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,32 @@ 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; +} + +/** + + This hook updates the sumBalances ghost whenever any address balance is updated + +*/ +hook Sstore _balances[KEY address user].balance uint104 balance + (uint104 old_balance) STORAGE { + sumBalances = sumBalances - to_mathint(old_balance) + to_mathint(balance); + } + +/** + + Invariant: + sum of all addresses' balances should be always equal to the total supply of the token + +*/ +invariant totalSupplyEqualsBalances() + sumBalances == totalSupply() \ No newline at end of file From 18e52499cdf476ef7606a6bc48b734617951202b Mon Sep 17 00:00:00 2001 From: Nurit Dor <57101353+nd-certora@users.noreply.github.com> Date: Sat, 23 Jul 2022 10:32:35 +0200 Subject: [PATCH 25/28] run on cloud --- certora/scripts/setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/certora/scripts/setup.sh b/certora/scripts/setup.sh index 6e7f0a5..03df365 100644 --- a/certora/scripts/setup.sh +++ b/certora/scripts/setup.sh @@ -9,5 +9,5 @@ certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ --solc solc8.13 \ --optimistic_loop \ --send_only \ - --staging \ + --cloud \ --msg "AaveTokenV3:setup.spec $1" From 1b0f650b7a34ca092077128ceec16d27de7e6b33 Mon Sep 17 00:00:00 2001 From: fyang1024 Date: Sat, 6 Aug 2022 11:24:45 +1000 Subject: [PATCH 26/28] add rules for AaveToken3 --- certora/harness/AaveTokenV3Harness.sol | 7 +- certora/scripts/AaveToken3_fyang1024.sh | 12 + ...AaveToken3_fyang1024_useBitVectorTheory.sh | 12 + certora/specs/AaveTokenV3_fyang1024.spec | 291 ++++++++++++++++++ ...eTokenV3_fyang1024_useBitVectorTheory.spec | 97 ++++++ 5 files changed, 417 insertions(+), 2 deletions(-) create mode 100644 certora/scripts/AaveToken3_fyang1024.sh create mode 100644 certora/scripts/AaveToken3_fyang1024_useBitVectorTheory.sh create mode 100644 certora/specs/AaveTokenV3_fyang1024.spec create mode 100644 certora/specs/AaveTokenV3_fyang1024_useBitVectorTheory.spec diff --git a/certora/harness/AaveTokenV3Harness.sol b/certora/harness/AaveTokenV3Harness.sol index f91de1d..c9371d4 100644 --- a/certora/harness/AaveTokenV3Harness.sol +++ b/certora/harness/AaveTokenV3Harness.sol @@ -441,8 +441,11 @@ contract AaveTokenV3 is BaseAaveTokenV2, IGovernancePowerDelegationToken { return _propositionDelegateeV2[user]; } - - + function getDelegatedPower(address user, GovernancePowerType delegationType) view external returns (uint256) { + DelegationAwareBalance memory userState = _balances[user]; + return _getDelegatedPowerByType(userState, delegationType); + } + /** End of harness section */ diff --git a/certora/scripts/AaveToken3_fyang1024.sh b/certora/scripts/AaveToken3_fyang1024.sh new file mode 100644 index 0000000..c9066f6 --- /dev/null +++ b/certora/scripts/AaveToken3_fyang1024.sh @@ -0,0 +1,12 @@ +if [[ "$1" ]] +then + RULE="--rule $1" +fi + +certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ + --verify AaveTokenV3:certora/specs/AaveToken3_fyang1024.spec \ + $RULE \ + --solc solc8.13 \ + --optimistic_loop \ + --send_only \ + --msg "AaveTokenV3:AaveToken3_fyang1024.spec $1" diff --git a/certora/scripts/AaveToken3_fyang1024_useBitVectorTheory.sh b/certora/scripts/AaveToken3_fyang1024_useBitVectorTheory.sh new file mode 100644 index 0000000..a573427 --- /dev/null +++ b/certora/scripts/AaveToken3_fyang1024_useBitVectorTheory.sh @@ -0,0 +1,12 @@ +if [[ "$1" ]] +then + RULE="--rule $1" +fi + +certoraRun certora/harness/AaveTokenV3Harness.sol:AaveTokenV3 \ + --verify AaveTokenV3:certora/specs/AaveToken3_fyang1024_useBitVectorTheory.spec \ + $RULE \ + --solc solc8.13 \ + --optimistic_loop \ + --settings -useBitVectorTheory \ + --msg "AaveTokenV3:AaveToken3_fyang1024_useBitVectorTheory.spec $1" diff --git a/certora/specs/AaveTokenV3_fyang1024.spec b/certora/specs/AaveTokenV3_fyang1024.spec new file mode 100644 index 0000000..b1e81b7 --- /dev/null +++ b/certora/specs/AaveTokenV3_fyang1024.spec @@ -0,0 +1,291 @@ +import "./setup.spec" + +methods { + delegateByType(address delegatee, uint8 delegationType) + getDelegatedPower(address user, uint8 delegationType) returns (uint256) envfree + getDelegateeByType(address delegator, uint8 delegationType) returns (address) envfree +} + +/* + @Invariant + + @Description: + A user's total power equals to + its delegated power if the user is delegating, or + its delegated power plus its own token balance if the user is not delegating + +*/ +invariant power_equations(address user) + getDelegatingVoting(user) => getPowerCurrent(user, VOTING_POWER()) == getDelegatedPower(user, VOTING_POWER()) + && + !getDelegatingVoting(user) => getPowerCurrent(user, VOTING_POWER()) == balanceOf(user) + getDelegatedPower(user, VOTING_POWER()) + && + getDelegatingProposition(user) => getPowerCurrent(user, PROPOSITION_POWER()) == getDelegatedPower(user, PROPOSITION_POWER()) + && + !getDelegatingProposition(user) => getPowerCurrent(user, PROPOSITION_POWER()) == balanceOf(user) + getDelegatedPower(user, PROPOSITION_POWER()) + +/* + @Rule + + @Description: + calling + delegate + delegateByType + metaDelegate + metaDelegateByType + functions should have the same effect on involved accounts' + voting power + proposition power + delegate +*/ +rule equivalence_of_delegate_functions(address other) { + env e; + // delegate not to self or to zero + address self = e.msg.sender; + require other != self && other != 0; + uint256 otherDelegatedBalance = getDelegatedVotingBalance(other); + // avoid unrealistic delegated balance + require(otherDelegatedBalance < MAX_DELEGATED_BALANCE()); + // make sure the sender doesn't already delegate to other + address delegateBefore = getVotingDelegate(self); + require delegateBefore != other; + + storage init = lastStorage; + + delegate(e, other); + uint256 _myVotingPower = getPowerCurrent(self, VOTING_POWER()); + uint256 _myPropositionPower = getPowerCurrent(self, PROPOSITION_POWER()); + uint256 _otherVotingPower = getPowerCurrent(other, VOTING_POWER()); + uint256 _otherPropositionPower = getPowerCurrent(other, PROPOSITION_POWER()); + address _myVotingDelegate = getVotingDelegate(self); + address _myPropositionDelegate = getPropositionDelegate(self); + + delegateByType(e, other, VOTING_POWER()) at init; + uint256 myVotingPower_ = getPowerCurrent(self, VOTING_POWER()); + uint256 otherVotingPower_ = getPowerCurrent(other, VOTING_POWER()); + address myVotingDelegate_ = getVotingDelegate(self); + + delegateByType(e, other, PROPOSITION_POWER()) at init; + uint256 myPropositionPower_ = getPowerCurrent(self, PROPOSITION_POWER()); + uint256 otherPropositionPower_ = getPowerCurrent(other, PROPOSITION_POWER()); + address myPropositionDelegate_ = getPropositionDelegate(self); + + uint256 deadline; + uint8 v; + bytes32 r; + bytes32 s; + metaDelegate(e, self, other, deadline, v, r, s) at init; + uint256 myVotingPower_metaDelegate = getPowerCurrent(self, VOTING_POWER()); + uint256 myPropositionPower_metaDelegate = getPowerCurrent(self, PROPOSITION_POWER()); + uint256 otherVotingPower_metaDelegate = getPowerCurrent(other, VOTING_POWER()); + uint256 otherPropositionPower_metaDelegate = getPowerCurrent(other, PROPOSITION_POWER()); + address myVotingDelegate_metaDelegate = getVotingDelegate(self); + address myPropositionDelegate_metaDelegate = getPropositionDelegate(self); + + metaDelegateByType(e, self, other, VOTING_POWER(), deadline, v, r, s) at init; + uint256 myVotingPower_metaDelegateByType = getPowerCurrent(self, VOTING_POWER()); + uint256 otherVotingPower_metaDelegateByType = getPowerCurrent(other, VOTING_POWER()); + address myVotingDelegate_metaDelegateByType = getVotingDelegate(self); + + metaDelegateByType(e, self, other, PROPOSITION_POWER(), deadline, v, r, s) at init; + uint256 myPropositionPower_metaDelegateByType = getPowerCurrent(self, PROPOSITION_POWER()); + uint256 otherPropositionPower_metaDelegateByType = getPowerCurrent(other, PROPOSITION_POWER()); + address myPropositionDelegate_metaDelegateByType = getPropositionDelegate(self); + + assert _myVotingPower == myVotingPower_; + assert _myPropositionPower == myPropositionPower_; + assert _myVotingDelegate == myVotingDelegate_; + assert _myPropositionDelegate == myPropositionDelegate_; + assert _otherVotingPower == otherVotingPower_; + assert _otherPropositionPower == otherPropositionPower_; + + assert _myVotingPower == myVotingPower_metaDelegate; + assert _myPropositionPower == myPropositionPower_metaDelegate; + assert _myVotingDelegate == myVotingDelegate_metaDelegate; + assert _myPropositionDelegate == myPropositionDelegate_metaDelegate; + assert _otherVotingPower == otherVotingPower_metaDelegate; + assert _otherPropositionPower == otherPropositionPower_metaDelegate; + + assert _myVotingPower == myVotingPower_metaDelegateByType; + assert _myPropositionPower == myPropositionPower_metaDelegateByType; + assert _myVotingDelegate == myVotingDelegate_metaDelegateByType; + assert _myPropositionDelegate == myPropositionDelegate_metaDelegateByType; + assert _otherVotingPower == otherVotingPower_metaDelegateByType; + assert _otherPropositionPower == otherPropositionPower_metaDelegateByType; +} + +/* + @Rule + + @Description: + delegate to self and delegate to zero address are equivalent +*/ +rule equivalence_of_self_and_zero_addresses(address other) { + env e; + address self = e.msg.sender; + require self != other; + + storage init = lastStorage; + + delegate(e, self); // delegate to self + + address _myVotingDelegate = getVotingDelegate(self); + address _myPropositionDelegate = getPropositionDelegate(self); + uint256 _myVotingPower = getPowerCurrent(self, VOTING_POWER()); + uint256 _myPropositionPower = getPowerCurrent(self, PROPOSITION_POWER()); + + address _otherVotingDelegate = getVotingDelegate(other); + address _otherPropositionDelegate = getPropositionDelegate(other); + uint256 _otherVotingPower = getPowerCurrent(other, VOTING_POWER()); + uint256 _otherPropositionPower = getPowerCurrent(other, PROPOSITION_POWER()); + + delegate(e, 0) at init; // delegate to 0 + + address myVotingDelegate_ = getVotingDelegate(self); + address myPropositionDelegate_ = getPropositionDelegate(self); + uint256 myVotingPower_ = getPowerCurrent(self, VOTING_POWER()); + uint256 myPropositionPower_ = getPowerCurrent(self, PROPOSITION_POWER()); + + address otherVotingDelegate_ = getVotingDelegate(other); + address otherPropositionDelegate_ = getPropositionDelegate(other); + uint256 otherVotingPower_ = getPowerCurrent(other, VOTING_POWER()); + uint256 otherPropositionPower_ = getPowerCurrent(other, PROPOSITION_POWER()); + + assert _myVotingDelegate == myVotingDelegate_; + assert _myPropositionDelegate == myPropositionDelegate_; + assert _myVotingPower == myVotingPower_; + assert _myPropositionPower == myPropositionPower_; + + assert _otherVotingDelegate == otherVotingDelegate_; + assert _otherPropositionDelegate == otherPropositionDelegate_; + assert _otherVotingPower == otherVotingPower_; + assert _otherPropositionPower == otherPropositionPower_; +} + + +/* + @Rule + + @Description: + If a user has not delegated to anyone and now delegates to self, + it should affect nothing. +*/ +rule self_delegation_affects_nothing(address other) { + env e; + address self = e.msg.sender; + require self != other; + + address _myVotingDelegate = getVotingDelegate(self); + address _myPropositionDelegate = getPropositionDelegate(self); + uint256 _myVotingPower = getPowerCurrent(self, VOTING_POWER()); + uint256 _myPropositionPower = getPowerCurrent(self, PROPOSITION_POWER()); + + require _myVotingDelegate == 0 && _myPropositionDelegate == 0; // not delegating to anyone + + address _otherVotingDelegate = getVotingDelegate(other); + address _otherPropositionDelegate = getPropositionDelegate(other); + uint256 _otherVotingPower = getPowerCurrent(other, VOTING_POWER()); + uint256 _otherPropositionPower = getPowerCurrent(other, PROPOSITION_POWER()); + + delegate(e, self); // delegate to self + + address myVotingDelegate_ = getVotingDelegate(self); + address myPropositionDelegate_ = getPropositionDelegate(self); + uint256 myVotingPower_ = getPowerCurrent(self, VOTING_POWER()); + uint256 myPropositionPower_ = getPowerCurrent(self, PROPOSITION_POWER()); + + address otherVotingDelegate_ = getVotingDelegate(other); + address otherPropositionDelegate_ = getPropositionDelegate(other); + uint256 otherVotingPower_ = getPowerCurrent(other, VOTING_POWER()); + uint256 otherPropositionPower_ = getPowerCurrent(other, PROPOSITION_POWER()); + + assert _myVotingDelegate == myVotingDelegate_; + assert _myPropositionDelegate == myPropositionDelegate_; + assert _myVotingPower == myVotingPower_; + assert _myPropositionPower == myPropositionPower_; + + assert _otherVotingDelegate == otherVotingDelegate_; + assert _otherPropositionDelegate == otherPropositionDelegate_; + assert _otherVotingPower == otherVotingPower_; + assert _otherPropositionPower == otherPropositionPower_; +} + +/* + @Rule + + @Description: + If a user has delegated to someone and now delegate to someone else, + it should only affect delegatees' power, while not affecting self and other's power. +*/ +rule changing_delegatee_affects_delegatees_only(address newDelegatee, uint8 delegationType, address other) { + env e; + address self = e.msg.sender; + require self != other && self != newDelegatee && newDelegatee != other && newDelegatee != 0; + require delegationType == VOTING_POWER() || delegationType == PROPOSITION_POWER(); + if (delegationType == VOTING_POWER()) { + require getDelegatingVoting(self); + } else { + require getDelegatingProposition(self); + } + address delegatee = getDelegateeByType(self, delegationType); + require delegatee != other && delegatee != self && delegatee != newDelegatee && delegatee != 0; + + uint256 selfBalance = balanceOf(self); + uint256 _selfPower = getPowerCurrent(self, delegationType); + uint256 _delegateePower = getPowerCurrent(delegatee, delegationType); + uint256 _newDelegateePower = getPowerCurrent(newDelegatee, delegationType); + uint256 _otherPower = getPowerCurrent(other, delegationType); + + require selfBalance < MAX_DELEGATED_BALANCE(); + require _selfPower < MAX_DELEGATED_BALANCE(); + require _delegateePower < MAX_DELEGATED_BALANCE(); + require _newDelegateePower < MAX_DELEGATED_BALANCE(); + require _selfPower >= selfBalance; + + delegateByType(e, newDelegatee, delegationType); // change delegate + + uint256 selfPower_ = getPowerCurrent(self, delegationType); + uint256 delegateePower_ = getPowerCurrent(delegatee, delegationType); + uint256 newDelegateePower_ = getPowerCurrent(newDelegatee, delegationType); + uint256 otherPower_ = getPowerCurrent(other, delegationType); + + assert getDelegateeByType(self, delegationType) == newDelegatee; + assert _selfPower == selfPower_; // selfPower does not change + assert _delegateePower - normalize(selfBalance) == delegateePower_; // delegateePower decreased by selfBalance + assert _newDelegateePower + normalize(selfBalance) == newDelegateePower_; // newDelegateePower increased by selfBalance + assert _otherPower == otherPower_; // otherPower does not change +} + +/* + @Rule + + @Description: + If a user delegates to the same address more than once, the subsequent delegations do not change anything +*/ +rule idempotency_of_delegation(address delegatee) { + env e; + address self = e.msg.sender; + + delegate(e, delegatee); // first delegation + uint256 _myVotingPower = getPowerCurrent(self, VOTING_POWER()); + uint256 _myPropositionPower = getPowerCurrent(self, PROPOSITION_POWER()); + uint256 _delegateeVotingPower = getPowerCurrent(delegatee, VOTING_POWER()); + uint256 _delegateePropositionPower = getPowerCurrent(delegatee, PROPOSITION_POWER()); + address _myVotingDelegate = getVotingDelegate(self); + address _myPropositionDelegate = getPropositionDelegate(self); + + delegate(e, delegatee); // second delegation + uint256 myVotingPower_ = getPowerCurrent(self, VOTING_POWER()); + uint256 myPropositionPower_ = getPowerCurrent(self, PROPOSITION_POWER()); + uint256 delegateeVotingPower_ = getPowerCurrent(delegatee, VOTING_POWER()); + uint256 delegateePropositionPower_ = getPowerCurrent(delegatee, PROPOSITION_POWER()); + address myVotingDelegate_ = getVotingDelegate(self); + address myPropositionDelegate_ = getPropositionDelegate(self); + + assert _myVotingDelegate == myVotingDelegate_; + assert _myPropositionDelegate == myPropositionDelegate_; + assert _myVotingPower == myVotingPower_; + assert _myPropositionPower == myPropositionPower_; + assert _delegateeVotingPower == delegateeVotingPower_; + assert _delegateePropositionPower == delegateePropositionPower_; +} \ No newline at end of file diff --git a/certora/specs/AaveTokenV3_fyang1024_useBitVectorTheory.spec b/certora/specs/AaveTokenV3_fyang1024_useBitVectorTheory.spec new file mode 100644 index 0000000..d519e2e --- /dev/null +++ b/certora/specs/AaveTokenV3_fyang1024_useBitVectorTheory.spec @@ -0,0 +1,97 @@ +import "./AaveTokenV3_fyang1024.spec" + +/* + @Rule + + @Description: + If a user has not delegated to anyone and now delegate, + it should decrease self power but increase delegatee power by its balance, while not affecting other's power. + + @Note: + This rule needs to be run with --settings -useBitVectorTheory flag, but the server threw + [java.lang.IllegalStateException: max size of bit vector 256 literals exceeded by + 115792089237316195423570985008687907853269984665640564039457584007913129639936], + hence this rule has not been verified yet. +*/ +rule delegate_affects_self_and_delegatee_only(address delegatee, uint8 delegationType, address other) { + env e; + address self = e.msg.sender; + require self != other && self != delegatee && delegatee != other && delegatee != 0; + require delegationType == VOTING_POWER() || delegationType == PROPOSITION_POWER(); + if (delegationType == VOTING_POWER()) { + require !getDelegatingVoting(self); + } else { + require !getDelegatingProposition(self); + } + require getDelegateeByType(self, delegationType) == 0; // not delegating now + + uint256 selfBalance = balanceOf(self); + uint256 _selfPower = getPowerCurrent(self, delegationType); + uint256 _delegateePower = getPowerCurrent(delegatee, delegationType); + uint256 _otherPower = getPowerCurrent(other, delegationType); + + require selfBalance < MAX_DELEGATED_BALANCE(); + require _selfPower < MAX_DELEGATED_BALANCE(); + require _delegateePower < MAX_DELEGATED_BALANCE(); + require _selfPower >= selfBalance; + + delegateByType(e, delegatee, delegationType); + + uint256 selfPower_ = getPowerCurrent(self, delegationType); + uint256 delegateePower_ = getPowerCurrent(delegatee, delegationType); + uint256 otherPower_ = getPowerCurrent(other, delegationType); + + assert getDelegateeByType(self, delegationType) == delegatee; + assert _selfPower - selfBalance == selfPower_; // selfPower decreased by selfBalance + assert _delegateePower + normalize(selfBalance) == delegateePower_; // delegateePower increased by selfBalance + assert _otherPower == otherPower_; // otherPower does not change +} + +/* + @Rule + + @Description: + If a user has delegated to someone and now undelegates (delegates to self), + it should increase self power but decrease delegatee power, while not affecting other power. + + @Note: + This rule needs to be run with --settings -useBitVectorTheory flag, but the server threw + [java.lang.IllegalStateException: max size of bit vector 256 literals exceeded by + 115792089237316195423570985008687907853269984665640564039457584007913129639936], + hence this rule has not been verified yet. +*/ +rule undelegate_affects_self_and_delegatee_only(uint8 delegationType, address other) { + env e; + address self = e.msg.sender; + require self != other; + require delegationType == VOTING_POWER() || delegationType == PROPOSITION_POWER(); + + if (delegationType == VOTING_POWER()) { + require getDelegatingVoting(self); + } else { + require getDelegatingProposition(self); + } + address delegatee = getDelegateeByType(self, delegationType); + require delegatee != other && delegatee != self && delegatee != 0; + + uint256 selfBalance = balanceOf(self); + uint256 _selfPower = getPowerCurrent(self, delegationType); + uint256 _delegateePower = getPowerCurrent(delegatee, delegationType); + uint256 _otherPower = getPowerCurrent(other, delegationType); + + require selfBalance < MAX_DELEGATED_BALANCE(); + require _selfPower < MAX_DELEGATED_BALANCE(); + require _delegateePower < MAX_DELEGATED_BALANCE(); + require _delegateePower >= selfBalance; + + delegateByType(e, self, delegationType); // undelegate + + uint256 selfPower_ = getPowerCurrent(self, delegationType); + uint256 delegateePower_ = getPowerCurrent(delegatee, delegationType); + uint256 otherPower_ = getPowerCurrent(other, delegationType); + + assert getDelegateeByType(self, delegationType) == 0; // no delegatee anymore + assert _selfPower + selfBalance == selfPower_; // selfPower increased by selfBalance + assert _delegateePower - normalize(selfBalance) == delegateePower_; // delegateePower decreased by selfBalance + assert _otherPower == otherPower_; // otherPower does not change +} \ No newline at end of file From 69b705b5d73d94954d74593c99d87bb0a64d0edd Mon Sep 17 00:00:00 2001 From: fyang1024 Date: Sat, 6 Aug 2022 12:18:25 +1000 Subject: [PATCH 27/28] add indepedency_of_delegation_type rule --- ...eTokenV3_fyang1024_useBitVectorTheory.spec | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/certora/specs/AaveTokenV3_fyang1024_useBitVectorTheory.spec b/certora/specs/AaveTokenV3_fyang1024_useBitVectorTheory.spec index d519e2e..ae81297 100644 --- a/certora/specs/AaveTokenV3_fyang1024_useBitVectorTheory.spec +++ b/certora/specs/AaveTokenV3_fyang1024_useBitVectorTheory.spec @@ -94,4 +94,68 @@ rule undelegate_affects_self_and_delegatee_only(uint8 delegationType, address ot assert _selfPower + selfBalance == selfPower_; // selfPower increased by selfBalance assert _delegateePower - normalize(selfBalance) == delegateePower_; // delegateePower decreased by selfBalance assert _otherPower == otherPower_; // otherPower does not change +} + +/* + @Rule + + @Description: + delegation of voting power should not affect proposition power, and vice versa. + + @Note: + This rule needs to be run with --settings -useBitVectorTheory flag, but the server threw + [java.lang.IllegalStateException: max size of bit vector 256 literals exceeded by + 115792089237316195423570985008687907853269984665640564039457584007913129639936], + hence this rule has not been verified yet. +*/ +rule independency_of_delegation_types(uint8 delegationType) { + env e; + address self = e.msg.sender; + address other; + require other != 0 && other != self; + require delegationType == VOTING_POWER() || delegationType == PROPOSITION_POWER(); + + uint8 otherDelegationType; + if (delegationType == VOTING_POWER()) { + otherDelegationType = PROPOSITION_POWER(); + } else { + otherDelegationType = VOTING_POWER(); + } + + uint256 _selfPower = getPowerCurrent(self, otherDelegationType); + uint256 _otherPower = getPowerCurrent(other, otherDelegationType); + address _delegatee; + if (otherDelegationType == VOTING_POWER()) { + _delegatee = getVotingDelegate(self); + } else { + _delegatee = getPropositionDelegate(self); + } + bool _delegating; + if (otherDelegationType == VOTING_POWER()) { + _delegating = getDelegatingVoting(self); + } else { + _delegating = getDelegatingProposition(self); + } + + delegateByType(e, other, delegationType); + + uint256 selfPower_ = getPowerCurrent(self, otherDelegationType); + uint256 otherPower_ = getPowerCurrent(other, otherDelegationType); + address delegatee_; + if (otherDelegationType == VOTING_POWER()) { + delegatee_ = getVotingDelegate(self); + } else { + delegatee_ = getPropositionDelegate(self); + } + bool delegating_; + if (otherDelegationType == VOTING_POWER()) { + delegating_ = getDelegatingVoting(self); + } else { + delegating_ = getDelegatingProposition(self); + } + + assert _selfPower == selfPower_; + assert _otherPower == otherPower_; + assert _delegatee == delegatee_; + assert _delegating == delegating_; } \ No newline at end of file From 3c340a91a38170bad39ed1ddeff2600fd3adc0ee Mon Sep 17 00:00:00 2001 From: fyang1024 Date: Sat, 6 Aug 2022 14:10:54 +1000 Subject: [PATCH 28/28] add only_transfer_affect_balance rule --- certora/specs/AaveTokenV3_fyang1024.spec | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/certora/specs/AaveTokenV3_fyang1024.spec b/certora/specs/AaveTokenV3_fyang1024.spec index b1e81b7..f0d8056 100644 --- a/certora/specs/AaveTokenV3_fyang1024.spec +++ b/certora/specs/AaveTokenV3_fyang1024.spec @@ -288,4 +288,25 @@ rule idempotency_of_delegation(address delegatee) { assert _myPropositionPower == myPropositionPower_; assert _delegateeVotingPower == delegateeVotingPower_; assert _delegateePropositionPower == delegateePropositionPower_; +} + +/* + @Rule + + @Description: + only transferring functions affect user's token balance +*/ +rule only_transfer_affect_balance(address user, method f) { + + uint256 _balance = balanceOf(user); + + env e; + calldataarg args; + f(e, args); + + uint256 balance_ = balanceOf(user); + + assert _balance != balance_ => + f.selector == transfer(address, uint256).selector || + f.selector == transferFrom(address, address, uint256).selector; } \ No newline at end of file