This commit is contained in:
Theo 2023-05-03 20:15:19 +03:00
commit 9d6c18fbdc
15 changed files with 322 additions and 0 deletions

1
.gitattributes vendored Normal file

@ -0,0 +1 @@
*.sol linguist-language=Solidity

2
.gitignore vendored Normal file

@ -0,0 +1,2 @@
out
cache

4
.gitmodules vendored Normal file

@ -0,0 +1,4 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
branch = v1.5.5

20
README.md Normal file

@ -0,0 +1,20 @@
## Requirements
- Foundryup ([Windows](https://github.com/altugbakan/foundryup-windows), [Linux](https://book.getfoundry.sh/getting-started/installation))
- Node 14 or higher ([Windows](https://github.com/coreybutler/nvm-windows), [Linux](https://github.com/nvm-sh/nvm))
## Installation
```
git clone --recurse-submodules https://git.tornado.ws/Theo/proposal-19-contract.git
```
## Testing
```
npm run test
```
## Contract
https://etherscan.io/address/0xfd533220d9f030a166ca0e126202d17fa4818d89#code

8
foundry.toml Normal file

@ -0,0 +1,8 @@
[profile.default]
solc-version = "0.8.19"
src = 'src'
out = 'out'
libs = ["node_modules", "lib"]
chain_id = 1
optimizer = true
optimizer-runs = 10_000_000

1
lib/forge-std Submodule

@ -0,0 +1 @@
Subproject commit 73d44ec7d124e3831bc5f832267889ffb6f9bc3f

20
package.json Normal file

@ -0,0 +1,20 @@
{
"name": "proposal-19-contract",
"version": "0.1.0",
"description": "Tornado proposal #19 smart contract code & tests",
"main": "index.js",
"directories": {
"lib": "lib",
"test": "test"
},
"scripts": {
"test": "forge test -vvvvv --fork-url https://rpc.mevblocker.io --block-number 17179667",
"build": "forge build --optimize"
},
"repository": {
"type": "git",
"url": "https://git.tornado.ws/Theo/proposal-19-contract"
},
"author": "Theo",
"license": "MIT"
}

6
remappings.txt Normal file

@ -0,0 +1,6 @@
@proprietary/=src/proprietary/
@interfaces/=src/interfaces/
@root/=src/
@forge-std/=lib/forge-std/src/

38
src/Proposal.sol Normal file

@ -0,0 +1,38 @@
pragma solidity 0.8.19;
import "@interfaces/ISablier.sol";
import "@interfaces/IERC20.sol";
contract Proposal {
function executeProposal() external {
uint256 FISCAL_QUARTER_DURATION = 91 days;
uint256 REMUNERATION_START_TS = block.timestamp;
uint256 REMUNERATION_AMOUNT = 4172 ether;
uint256 REMUNERATION_NORMALISED_AMOUNT = REMUNERATION_AMOUNT -
(REMUNERATION_AMOUNT % FISCAL_QUARTER_DURATION);
uint256 SERVICES_COST_REIMBURSEMENT_AMOUNT = 834 ether;
address _tokenAddress = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C;
address _sablierAddress = 0xCD18eAa163733Da39c232722cBC4E8940b1D8888;
address REMUNERATION_ADDRESS = 0x9Ff3C1Bea9ffB56a78824FE29f457F066257DD58;
IERC20(_tokenAddress).transfer(
REMUNERATION_ADDRESS,
SERVICES_COST_REIMBURSEMENT_AMOUNT
);
IERC20(_tokenAddress).approve(
_sablierAddress,
REMUNERATION_NORMALISED_AMOUNT
);
ISablier(_sablierAddress).createStream(
REMUNERATION_ADDRESS,
REMUNERATION_NORMALISED_AMOUNT,
_tokenAddress,
REMUNERATION_START_TS,
REMUNERATION_START_TS + FISCAL_QUARTER_DURATION
);
}
}

15
src/interfaces/IERC20.sol Normal file

@ -0,0 +1,15 @@
pragma solidity 0.8.19;
interface IERC20 {
function transfer(address to, uint256 amount) external returns (bool);
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
function balanceOf(address owner) external returns (uint256);
function approve(address spender, uint256 amount) external;
}

@ -0,0 +1,21 @@
pragma solidity 0.8.19;
interface IGovernance {
function propose(
address target,
string memory description
) external returns (uint256);
function castVote(uint256 proposalId, bool support) external;
function execute(uint256 proposalId) external payable;
function lock(
address owner,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
}

@ -0,0 +1,11 @@
pragma solidity 0.8.19;
interface ISablier {
function createStream(
address recipent,
uint256 deposit,
address tokenAddress,
uint256 startTime,
uint256 stopTime
) external returns (uint256);
}

40
src/proprietary/Mock.sol Normal file

@ -0,0 +1,40 @@
pragma solidity 0.8.19;
contract Mock {
uint256 constant TEST_PRIVATE_KEY_ONE =
0x66ddbd7cbe4a566df405f6ded0b908c669f88cdb1656380c050e3a457bd21df0;
uint256 constant TEST_PRIVATE_KEY_TWO =
0xa4c8c98120e77741a87a116074a2df4ddb20d1149069290fd4a3d7ee65c55064;
address constant TEST_ADDRESS_ONE =
0x118251976c65AFAf291f5255450ddb5b6A4d8B88;
address constant TEST_ADDRESS_TWO =
0x63aE7d90Eb37ca39FC62dD9991DbEfeE70673a20;
uint256 constant PROPOSAL_DURATION = 7 days;
uint256 constant PROPOSAL_THRESHOLD = 25000 ether;
string constant PROPOSAL_DESCRIPTION =
"{title:'Proposal #19: New developer and remuneration',description:''}";
address constant VERIFIER_ADDRESS =
0x77777FeDdddFfC19Ff86DB637967013e6C6A116C;
bytes32 constant PERMIT_TYPEHASH =
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
);
bytes32 constant EIP712_DOMAIN =
keccak256(
abi.encode(
keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
),
keccak256(bytes("TornadoCash")),
keccak256(bytes("1")),
1,
VERIFIER_ADDRESS
)
);
uint16 constant PERMIT_FUNC_SELECTOR = uint16(0x1901);
}

@ -0,0 +1,18 @@
pragma solidity 0.8.19;
contract Parameters {
uint256 FISCAL_QUARTER_DURATION = 91 days;
uint256 REMUNERATION_START_TS = block.timestamp;
uint256 REMUNERATION_AMOUNT = 4172 ether;
uint256 REMUNERATION_NORMALISED_AMOUNT =
REMUNERATION_AMOUNT - (REMUNERATION_AMOUNT % FISCAL_QUARTER_DURATION);
uint256 SERVICES_COST_REIMBURSEMENT_AMOUNT = 834 ether;
address REMUNERATION_ADDRESS = 0x9Ff3C1Bea9ffB56a78824FE29f457F066257DD58;
// Beneficary addresses
address public _governanceAddress =
0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce;
address public _tokenAddress = 0x77777FeDdddFfC19Ff86DB637967013e6C6A116C;
address public _sablierAddress = 0xCD18eAa163733Da39c232722cBC4E8940b1D8888;
}

117
test/Proposal.t.sol Normal file

@ -0,0 +1,117 @@
pragma solidity ^0.8.1;
import "@interfaces/IGovernance.sol";
import "@interfaces/IERC20.sol";
import "@proprietary/Parameters.sol";
import "@proprietary/Mock.sol";
import "@root/Proposal.sol";
import "@forge-std/Test.sol";
contract ProposalTest is Test, Parameters, Mock {
modifier conditionStateChecks() {
checkParameters();
_;
checkResults();
}
function testProposal() public conditionStateChecks {
uint256 proposalId = voteAndCreateProposal(address(new Proposal()));
IGovernance(_governanceAddress).execute(proposalId);
}
function voteAndCreateProposal(
address proposalAddress
) public returns (uint256) {
retrieveAndLockBalance(
TEST_PRIVATE_KEY_ONE,
TEST_ADDRESS_ONE,
PROPOSAL_THRESHOLD
);
retrieveAndLockBalance(TEST_PRIVATE_KEY_TWO, TEST_ADDRESS_TWO, 1 ether);
/* ----------PROPOSER------------ */
vm.startPrank(TEST_ADDRESS_ONE);
uint256 proposalId = IGovernance(_governanceAddress).propose(
proposalAddress,
PROPOSAL_DESCRIPTION
);
// TIME-TRAVEL
vm.warp(block.timestamp + 6 hours);
IGovernance(_governanceAddress).castVote(proposalId, true);
vm.stopPrank();
/* ------------------------------ */
/* -------------VOTER-------------*/
vm.startPrank(TEST_ADDRESS_TWO);
IGovernance(_governanceAddress).castVote(proposalId, true);
vm.stopPrank();
/* ------------------------------ */
// TIME-TRAVEL
vm.warp(block.timestamp + PROPOSAL_DURATION);
return proposalId;
}
function retrieveAndLockBalance(
uint256 privateKey,
address voter,
uint256 amount
) internal {
uint256 lockTimestamp = block.timestamp + PROPOSAL_DURATION;
bytes32 messageHash = keccak256(
abi.encodePacked(
PERMIT_FUNC_SELECTOR,
EIP712_DOMAIN,
keccak256(
abi.encode(
PERMIT_TYPEHASH,
voter,
_governanceAddress,
amount,
0,
lockTimestamp
)
)
)
);
/* ----------GOVERNANCE------- */
vm.startPrank(_governanceAddress);
IERC20(_tokenAddress).transfer(voter, amount);
vm.stopPrank();
/* ----------------------------*/
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, messageHash);
/* ----------VOTER------------ */
vm.startPrank(voter);
IGovernance(_governanceAddress).lock(
voter,
amount,
lockTimestamp,
v,
r,
s
);
vm.stopPrank();
/* ----------------------------*/
}
function checkParameters() internal {
require(REMUNERATION_NORMALISED_AMOUNT > 4100 ether);
}
function checkResults() internal {
require(
IERC20(_tokenAddress).balanceOf(REMUNERATION_ADDRESS) == 834 ether
);
}
}