Skip to content

Commit

Permalink
Initial check-in
Browse files Browse the repository at this point in the history
Initial check-in
  • Loading branch information
chuacw committed Sep 10, 2024
1 parent 2e39081 commit 12d0921
Show file tree
Hide file tree
Showing 8 changed files with 397 additions and 38 deletions.
5 changes: 5 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,10 @@
src = "src"
out = "out"
libs = ["lib"]
evm_version = "cancun"
optimizer_runs = 800
via_ir = false
ffi = true
verbosity = 2

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
File renamed without changes.
19 changes: 19 additions & 0 deletions script/HookDonation.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Script, console} from "forge-std/Script.sol";
import {AfterSwapDonationHook} from "../src/HookDonation.sol";

contract CounterScript is Script {
AfterSwapDonationHook public donationHook;

function setUp() public {}

function run() public {
vm.startBroadcast();

// donationHook = new AfterSwapDonationHook();

vm.stopBroadcast();
}
}
14 changes: 0 additions & 14 deletions src/Counter.sol

This file was deleted.

121 changes: 121 additions & 0 deletions src/HookDonation.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {BaseHook} from "lib/v4-periphery/src/base/hooks/BaseHook.sol";
import {PoolKey} from "lib/v4-periphery/lib/v4-core/src/types/PoolKey.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {BalanceDelta} from "lib/v4-periphery/lib/v4-core/src/types/BalanceDelta.sol";
import {Hooks} from "lib/v4-periphery/lib/v4-core/src/libraries/Hooks.sol";
import {CurrencyLibrary, Currency} from "v4-core/types/Currency.sol";

contract AfterSwapDonationHook is BaseHook {
using CurrencyLibrary for Currency;
struct DonationMapping {
bool enabled;
address payable recipient;
uint256 percent; // how much to donate
}
address public owner;
address public pool;
mapping(address => DonationMapping) donationMap;

// -------------- begin donation associated functions ---------------
function disableDonation() public {
// Reset the value to the default value.
delete donationMap[msg.sender];
}

function enableDonation(address recipient, uint256 percent) public {
DonationMapping memory local;
local.recipient = payable(recipient);
local.percent = percent;

donationMap[msg.sender] = local;
}

// the following should all have internal view, not public
// but have been changed to public view for testing

function donationEnabled(address payee) public view returns (bool) {
return (donationMap[payee].recipient != payable(0x0));
}

function donationPercent(address payee) public view returns (uint256) {
return (donationMap[payee].percent);
}

function donationRecipient(address payee) public view returns (address) {
return (donationMap[payee].recipient);
}
// -------------- end donation associated functions ---------------

constructor(IPoolManager _poolManager) BaseHook(_poolManager) {
owner = msg.sender;
}

// Modifier to restrict access to the owner
modifier onlyOwner() {
require(msg.sender == owner, "Not authorized");
_;
}

/// @notice The hook called after a swap
/// @param sender The initial msg.sender for the swap call
/// @param key The key for the pool
/// @param swapParams The parameters for the swap
/// @param delta The amount owed to the caller (positive) or owed to the pool (negative)
/// @param ...Arbitrary data handed into the PoolManager by the swapper to be passed on to the hook
/// @return bytes4 The function selector for the hook
/// @return int128 The hook's delta in unspecified currency. Positive: the hook is owed/took currency, negative: the hook owes/sent currency
function afterSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata swapParams,
BalanceDelta delta,
bytes calldata
) external override returns (bytes4, int128) {
require(msg.sender == address(pool), "Unauthorized caller");

// Check that donation is enabled for the sender
if (!donationEnabled(sender))
return (this.afterSwap.selector, 0);

uint256 spendAmount = swapParams.amountSpecified < 0
? uint256(-swapParams.amountSpecified)
: uint256(int256(-delta.amount0()));

uint256 donationAmount = (spendAmount * donationPercent(sender)) / 100;
address recipient = donationRecipient(sender);

key.currency0.transfer(recipient, donationAmount);

return (this.afterSwap.selector, 0);
}

// Function to update the pool address if needed
function updatePool(address _newPool) external onlyOwner {
pool = _newPool;
}

// Only for other apps. Uniswap doesn't call this.
function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
return
Hooks.Permissions({
beforeInitialize: false,
afterInitialize: false,
beforeAddLiquidity: false,
beforeRemoveLiquidity: false,
afterAddLiquidity: false,
afterRemoveLiquidity: false,
beforeSwap: false,
afterSwap: true,
beforeDonate: false,
afterDonate: false,
beforeSwapReturnDelta: false,
afterSwapReturnDelta: false,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}

}
24 changes: 0 additions & 24 deletions test/Counter.t.sol

This file was deleted.

173 changes: 173 additions & 0 deletions test/HookDonation.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;

// forge-std/=lib/v4-periphery/lib/v4-core/lib/forge-std/src/
import "lib/v4-periphery/lib/v4-core/lib/forge-std/src/Test.sol";
import "lib/v4-periphery/lib/v4-core/lib/forge-std/src/console.sol";
import {Hooks} from "lib/v4-core/src/libraries/Hooks.sol";
import {Deployers} from "lib/v4-core/test/utils/Deployers.sol";
import {IPoolManager} from "lib/v4-core/src/interfaces/IPoolManager.sol";
import {AfterSwapDonationHook} from "../src/HookDonation.sol";
import {Currency, CurrencyLibrary} from "lib/v4-core/src/types/Currency.sol";
// solmate/=lib/v4-core/lib/solmate/
import {MockERC20} from "lib/v4-core/lib/solmate/src/test/utils/mocks/MockERC20.sol";
import {PoolKey} from "lib/v4-core/src/types/PoolKey.sol";
import {IHooks} from "lib/v4-core/src/interfaces/IHooks.sol";

contract DonationTest is Test, Deployers {
using CurrencyLibrary for Currency;

struct DonationMapping {
bool enabled;
address payable recipient;
uint256 percent; // how much to donate
}
mapping(address => DonationMapping) donationMap;

// address constant USDT_MOCK_ADDRESS = address(0xEce6af52f8eDF69dd2C216b9C3f184e5b31750e9); // mock address
// address constant USDC_MOCK_ADDRESS = address(0x63ba29cAF4c40DaDA8a61D10AB5D2728c806b61f); // mock address

AfterSwapDonationHook donationHook;
// The two currencies (tokens) from the pool
Currency token0;
Currency token1;
PoolKey globalKey;
address constant RECIPIENT = address(0x01);

event HookAddress(address indexed hookAddress);

function setUp() public {
Deployers.deployFreshManagerAndRouters();
(currency0, currency1) = Deployers.deployMintAndApprove2Currencies();
(token0, token1) = (currency0, currency1);

// Deploy the hook to an address with the correct flags
uint160 flags = uint160(
Hooks.AFTER_SWAP_FLAG
);

address hookAddress = address(flags);
deployCodeTo(
"HookDonation.sol",
abi.encode(manager, ""),
hookAddress
);
emit HookAddress(hookAddress);
donationHook = AfterSwapDonationHook(hookAddress);
console.log("setUp Hook Address: ", hookAddress);
console.log("donation Hook: ", address(donationHook));

// Approve our hook address to spend these tokens as well
MockERC20(Currency.unwrap(token0)).approve(
address(donationHook),
type(uint256).max
);
MockERC20(Currency.unwrap(token1)).approve(
address(donationHook),
type(uint256).max
);
// Approve swapRouter to spend these tokens as well
MockERC20(Currency.unwrap(token0)).approve(
address(swapRouter),
type(uint256).max
);
MockERC20(Currency.unwrap(token1)).approve(
address(swapRouter),
type(uint256).max
);

// Initialize a pool with these two tokens
(key, ) = initPool(token0, token1, IHooks(hookAddress), 3000, SQRT_PRICE_1_1, ZERO_BYTES);
globalKey = key;
}

function donationEnabled(address payee) public view returns (bool) {
return (donationMap[payee].recipient != payable(0x0));
}

function donationRecipient(address payee) public view returns (address) {
return (donationMap[payee].recipient);
}

function enableDonation(address recipient, uint256 percent) public {
DonationMapping memory local;
local.recipient = payable(recipient);
local.percent = percent;

donationMap[msg.sender] = local;
}

function test_internalEnableDonation() public {
address payee = msg.sender;
bool enabled = donationEnabled(payee);
address recipient = donationRecipient(payee);
console.log("Before enabling donation");
console.log("--------------------------------------------------------------------------------");
console.log("enabled: %s", enabled);
console.log("recipient: %s", recipient);
console.log();

enableDonation(RECIPIENT, 10); // recipient = 0x01, 10 percent
console.log("After enabling donation");
console.log("--------------------------------------------------------------------------------");
enabled = donationEnabled(payee);
recipient = donationRecipient(payee);
console.log("enabled: %s", enabled);
console.log("recipient: %s", recipient);
}

function test_enableDonation() public {
address payee = msg.sender;
bool enabled = donationHook.donationEnabled(payee);
address recipient = donationHook.donationRecipient(payee);
console.log("Before enabling donation");
console.log("--------------------------------------------------------------------------------");
console.log("enabled: %s", enabled);
console.log("recipient: %s", recipient);
console.log();

donationHook.enableDonation(RECIPIENT, 10); // recipient = 0x01, 10 percent
console.log("After enabling donation");
console.log("--------------------------------------------------------------------------------");
enabled = donationHook.donationEnabled(payee);
recipient = donationHook.donationRecipient(payee);
console.log("enabled: %s", enabled);
console.log("recipient: %s", recipient);
}

function test_Donation() public {
bool zeroForOne = true;
// PoolKey memory pool = PoolKey(
// token0, token1, 3000, 60, IHooks(address(donationHook))
// );
PoolKey memory pool = globalKey;
bytes memory data = abi.encode(msg.sender);
address recipient = address(0x01);
console.log("Donation not enabled");
console.log("Donation enabled for %s: %s", msg.sender, donationHook.donationEnabled(msg.sender));
console.log("Donation recipient for %s: %s", msg.sender, donationHook.donationRecipient(msg.sender));

console.log("Donation enabled: 10%%");
donationHook.enableDonation(RECIPIENT, 10); // recipient = 0x01, 10 percent
console.log("Test Donation sender: ", msg.sender);

console.log("Donation enabled for %s: %s", msg.sender, donationHook.donationEnabled(msg.sender));
console.log("Donation recipient for %s: %s", msg.sender, donationHook.donationRecipient(msg.sender));

console.log("beforeSwap Balance token0: ", token0.balanceOf(msg.sender));
console.log("beforeSwap Balance token1: ", token1.balanceOf(msg.sender));
console.log();
console.log("beforeSwap Balance token0: ", token0.balanceOf(recipient));
console.log("beforeSwap Balance token1: ", token1.balanceOf(recipient));


int256 amountSpecified = 10;
Deployers.swap(pool, zeroForOne, amountSpecified, data);

console.log(" afterSwap Balance token0: ", token0.balanceOf(msg.sender));
console.log(" afterSwap Balance token1: ", token1.balanceOf(msg.sender));
console.log(" afterSwap Balance token0: ", token0.balanceOf(recipient));
console.log(" afterSwap Balance token1: ", token1.balanceOf(recipient));
}

}
Loading

0 comments on commit 12d0921

Please sign in to comment.