Skip to content

Commit

Permalink
feat: add avalon strategy test cases
Browse files Browse the repository at this point in the history
  • Loading branch information
nakul1010 committed Jan 7, 2025
1 parent 7c99ae1 commit 2b0029c
Show file tree
Hide file tree
Showing 5 changed files with 318 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
id: build
- name: Run forge tests
run: |
forge test -vvv --deny-warnings
forge test --no-match-path "test/gateway/e2e-tests/*" -vvv --deny-warnings
id: test
- name: Run forge docs
run: |
Expand Down
119 changes: 119 additions & 0 deletions test/gateway/e2e-strategy-tests/AvalonLendingStrategyForked.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {stdStorage, StdStorage, Test, console} from "forge-std/Test.sol";

using stdStorage for StdStorage;

import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {IAvalonIPool, AvalonLendingStrategy} from "../../../src/gateway/strategy/AvalonStrategy.sol";
import {StrategySlippageArgs} from "../../../src/gateway/CommonStructs.sol";
import {Constants} from "./Constants.sol";

// Command to run this test with Foundry:
// BOB_PROD_PUBLIC_RPC_URL=https://rpc.gobob.xyz/ forge test --match-contract AvalonTBTCLendingStrategyForked -vv

contract AvalonTBTCLendingStrategyForked is Test {
// Instantiate TBTC token using its address from Constants
IERC20 token = IERC20(Constants.TBTC_ADDRESS);

function setUp() public {
// Set up the test environment by creating a fork of the BOB_PROD_PUBLIC_RPC_URL
// Fixing the block number ensures reproducible test results
vm.createSelectFork(vm.envString("BOB_PROD_PUBLIC_RPC_URL"), 6077077);

// Transfer 100 TBTC tokens to DUMMY_SENDER
vm.prank(0xa79a356B01ef805B3089b4FE67447b96c7e6DD4C);
token.transfer(Constants.DUMMY_SENDER, 100 ether);
vm.stopPrank();
}

function testAvalonTBTCStrategy() public {
// Instantiate the Avalon TBTC token and pool contracts
IERC20 avalonTBTCToken = IERC20(0x5E007Ed35c7d89f5889eb6FD0cdCAa38059560ef);
IAvalonIPool pool = IAvalonIPool(0x35B3F1BFe7cbE1e95A3DC2Ad054eB6f0D4c879b6);

// Deploy a new AvalonLendingStrategy contract
AvalonLendingStrategy strategy = new AvalonLendingStrategy(avalonTBTCToken, pool);

// DUMMY_SENDER approves the strategy contract to spend 1 TBTC on their behalf
vm.prank(Constants.DUMMY_SENDER);
token.approve(address(strategy), 1 ether);
vm.stopPrank();

// DUMMY_SENDER sends 1 TBTC to the strategy with slippage arguments
vm.prank(Constants.DUMMY_SENDER);
strategy.handleGatewayMessageWithSlippageArgs(
token,
1 ether, // Amount: 1 TBTC
Constants.DUMMY_RECEIVER,
StrategySlippageArgs(0) // No slippage allowed
);
vm.stopPrank();

// Assert that DUMMY_RECEIVER's token balance is still 0 (funds are in the pool)
assertEq(token.balanceOf(Constants.DUMMY_RECEIVER), 0 ether);

// DUMMY_RECEIVER withdraws Received TBTC from the pool
vm.prank(Constants.DUMMY_RECEIVER);
pool.withdraw(address(token), 1 ether, Constants.DUMMY_RECEIVER);
vm.stopPrank();

// Assert that DUMMY_RECEIVER now has 1 TBTC in their balance
assertEq(token.balanceOf(Constants.DUMMY_RECEIVER), 1 ether);
}
}

// Command to run this test with Foundry:
// BOB_PROD_PUBLIC_RPC_URL=https://rpc.gobob.xyz/ forge test --match-contract AvalonWBTCLendingStrategyForked -vv

contract AvalonWBTCLendingStrategyForked is Test {
// Instantiate TBTC token using its address from Constants
IERC20 token = IERC20(Constants.WBTC_ADDRESS);

function setUp() public {
// Set up the test environment by creating a fork of the BOB_PROD_PUBLIC_RPC_URL
// Fixing the block number ensures reproducible test results
vm.createSelectFork(vm.envString("BOB_PROD_PUBLIC_RPC_URL"), 6216882);

// Transfer 100 WBTC tokens to DUMMY_SENDER
vm.prank(0x5A8E9774d67fe846C6F4311c073e2AC34b33646F);
token.transfer(Constants.DUMMY_SENDER, 100 * 1e8);
vm.stopPrank();
}

function testAvalonWBTCStrategy() public {
// Instantiate the Avalon WBTC token and pool contracts
IERC20 avalonWBTCToken = IERC20(0xd6890176e8d912142AC489e8B5D8D93F8dE74D60);
IAvalonIPool pool = IAvalonIPool(0x35B3F1BFe7cbE1e95A3DC2Ad054eB6f0D4c879b6);

// Deploy a new AvalonLendingStrategy contract
AvalonLendingStrategy strategy = new AvalonLendingStrategy(avalonWBTCToken, pool);

// DUMMY_SENDER approves the strategy contract to spend 1 WBTC on their behalf
vm.prank(Constants.DUMMY_SENDER);
token.approve(address(strategy), 1 * 1e8);
vm.stopPrank();

// DUMMY_SENDER sends 1 WBTC to the strategy with slippage arguments
vm.prank(Constants.DUMMY_SENDER);
strategy.handleGatewayMessageWithSlippageArgs(
token,
1 * 1e8, // Amount: 1 WBTC
Constants.DUMMY_RECEIVER,
StrategySlippageArgs(0) // No slippage allowed
);
vm.stopPrank();

// Assert that DUMMY_RECEIVER's token balance is still 0 (funds are in the pool)
assertEq(token.balanceOf(Constants.DUMMY_RECEIVER), 0);

// DUMMY_RECEIVER withdraws Received WBTC from the pool
vm.prank(Constants.DUMMY_RECEIVER);
pool.withdraw(address(token), 1 * 1e8, Constants.DUMMY_RECEIVER);
vm.stopPrank();

// Assert that DUMMY_RECEIVER now has 1 WBTC in their balance
assertEq(token.balanceOf(Constants.DUMMY_RECEIVER), 1 * 1e8);
}
}
64 changes: 64 additions & 0 deletions test/gateway/e2e-strategy-tests/AvalonLstStrategyForked.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {Test, console} from "forge-std/Test.sol";
import {
IAvalonIPool, AvalonLendingStrategy, AvalonLstStrategy
} from "../../../src/gateway/strategy/AvalonStrategy.sol";
import {StrategySlippageArgs} from "../../../src/gateway/CommonStructs.sol";
import {Constants} from "./Constants.sol";
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {SolvLSTStrategy, ISolvBTCRouter} from "../../../src/gateway/strategy/SolvStrategy.sol";

// Command to run this test with Foundry:
// BOB_PROD_PUBLIC_RPC_URL=https://rpc.gobob.xyz/ forge test --match-contract AvalonWBTCLstStrategyForked -vv

contract AvalonWBTCLstStrategyForked is Test {
// Instantiate WBTC token using its address from Constants
IERC20 token = IERC20(Constants.WBTC_ADDRESS);

function setUp() public {
// Set up the test environment by creating a fork of the BOB_PROD_PUBLIC_RPC_URL
// Fixing the block number ensures reproducible test results
vm.createSelectFork(vm.envString("BOB_PROD_PUBLIC_RPC_URL"), 6216882);

// Transfer 100 WBTC tokens to DUMMY_SENDER
vm.prank(0x5A8E9774d67fe846C6F4311c073e2AC34b33646F);
token.transfer(Constants.DUMMY_SENDER, 100 * 1e8);
vm.stopPrank();
}

function testWbtcLstStrategy() public {
IERC20 solvBTC = IERC20(0x541FD749419CA806a8bc7da8ac23D346f2dF8B77);
IERC20 solvBTCBBN = IERC20(0xCC0966D8418d412c599A6421b760a847eB169A8c);
SolvLSTStrategy solvLSTStrategy = new SolvLSTStrategy(
ISolvBTCRouter(0x49b072158564Db36304518FFa37B1cFc13916A90),
ISolvBTCRouter(0xbA46FcC16B464D9787314167bDD9f1Ce28405bA1),
0x5664520240a46b4b3e9655c20cc3f9e08496a9b746a478e476ae3e04d6c8fc31,
0x6899a7e13b655fa367208cb27c6eaa2410370d1565dc1f5f11853a1e8cbef033,
solvBTC,
solvBTCBBN
);

IERC20 avalonSolvBtcBBNToken = IERC20(0x2E6500A7Add9a788753a897e4e3477f651c612eb);
IAvalonIPool pool = IAvalonIPool(0x35B3F1BFe7cbE1e95A3DC2Ad054eB6f0D4c879b6);
AvalonLendingStrategy avalonLendingStrategy = new AvalonLendingStrategy(avalonSolvBtcBBNToken, pool);

AvalonLstStrategy avalonLstStrategy = new AvalonLstStrategy(solvLSTStrategy, avalonLendingStrategy);

// DUMMY_SENDER approves the strategy contract to spend 1 WBTC on their behalf
vm.prank(Constants.DUMMY_SENDER);
token.approve(address(avalonLstStrategy), 1 * 1e8);
vm.stopPrank();

assertEq(avalonSolvBtcBBNToken.balanceOf(address(Constants.DUMMY_RECEIVER)), 0);

vm.prank(Constants.DUMMY_SENDER);
avalonLstStrategy.handleGatewayMessageWithSlippageArgs(
token, 1 * 1e8, Constants.DUMMY_RECEIVER, StrategySlippageArgs(0)
);
vm.stopPrank();

assertEq(avalonSolvBtcBBNToken.balanceOf(address(Constants.DUMMY_RECEIVER)), 1 ether);
}
}
13 changes: 13 additions & 0 deletions test/gateway/e2e-strategy-tests/Constants.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

/**
* @title Commonly used constants.
*/
library Constants {
// Used for providing a uniform interface for functions that deal with both ERC20 and native tokens.
address constant WBTC_ADDRESS = 0x03C7054BCB39f7b2e5B2c7AcB37583e32D70Cfa3;
address constant TBTC_ADDRESS = 0xBBa2eF945D523C4e2608C9E1214C2Cc64D4fc2e2;
address constant DUMMY_SENDER = 0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E;
address constant DUMMY_RECEIVER = 0xa2adc38f06704Cd2e633e8656DbF8B3224E840b8;
}
121 changes: 121 additions & 0 deletions test/gateway/unit-tests/AvalonStrategy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {stdStorage, StdStorage, Test, console} from "forge-std/Test.sol";

using stdStorage for StdStorage;

import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {ERC20} from "openzeppelin-contracts/token/ERC20/ERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

import {IAvalonIPool, AvalonLendingStrategy} from "../../../src/gateway/strategy/AvalonStrategy.sol";
import {StrategySlippageArgs} from "../../../src/gateway/CommonStructs.sol";

contract DummyPoolImplementation is IAvalonIPool {
ArbitaryErc20 avalonToken;
bool private doSupply;

// Constructor with a flag to determine supply behavior
constructor(ArbitaryErc20 _avalonToken, bool _doSupply) {
doSupply = _doSupply;
avalonToken = _avalonToken;
}

// Supply function behavior changes based on the flag
function supply(address, /* asset */ uint256 amount, address onBehalfOf, uint16 /* referralCode */ )
external
override
{
if (doSupply) {
// Supply logic for DummyPoolImplementation2: transfers tokens
avalonToken.transfer(onBehalfOf, amount);
}
// If doSupply is false, no supply action is taken (DummyPoolImplementation behavior)
}

// Withdraw function (unchanged in both cases)
function withdraw(address, uint256, address /* to */ ) external pure override returns (uint256) {
return 0;
}
}

contract ArbitaryErc20 is ERC20, Ownable {
constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {}

// Mint function accessible only to the owner
function sudoMint(address to, uint256 amount) public onlyOwner {
_mint(to, amount); // Provided by the OpenZeppelin ERC20 contract
}
}

// forge test --match-contract AvalonLendingStrategyTest -vv
contract AvalonLendingStrategyTest is Test {
event TokenOutput(address tokenReceived, uint256 amountOut);

ArbitaryErc20 lendingToken;
ArbitaryErc20 avalonToken;

function setUp() public {
lendingToken = new ArbitaryErc20("Lending Token", "Lending Token");
avalonToken = new ArbitaryErc20("Avalon Token", "Avalon Token");
lendingToken.sudoMint(address(this), 100 ether); // Mint 100 tokens to this contract
}

function testLendingStrategyForValidAmount() public {
IAvalonIPool dummyPool = new DummyPoolImplementation(avalonToken, true);
avalonToken.sudoMint(address(dummyPool), 100 ether); // Mint 100 tokens to this contract

AvalonLendingStrategy avalonStrategy = new AvalonLendingStrategy(avalonToken, dummyPool);

// Approve ionicStrategy to spend 100 tBTC tokens on behalf of this contract
lendingToken.increaseAllowance(address(avalonStrategy), 1 ether);

vm.expectEmit();
emit TokenOutput(address(avalonToken), 1 ether);
avalonStrategy.handleGatewayMessageWithSlippageArgs(
lendingToken, 1 ether, vm.addr(1), StrategySlippageArgs(1 ether)
);

assertEq(avalonToken.balanceOf(vm.addr(1)), 1 ether);
assertEq(lendingToken.balanceOf(address(this)), 99 ether);
}

function testWhenInsufficientSupplyProvided() public {
IAvalonIPool dummyPool = new DummyPoolImplementation(avalonToken, false);
AvalonLendingStrategy avalonStrategy = new AvalonLendingStrategy(avalonToken, dummyPool);

// Approve ionicStrategy to spend 100 tBTC tokens on behalf of this contract
lendingToken.increaseAllowance(address(avalonStrategy), 100);

vm.expectRevert("Insufficient supply provided");
avalonStrategy.handleGatewayMessageWithSlippageArgs(lendingToken, 100, vm.addr(1), StrategySlippageArgs(0));
}

function testWhenInsufficientOutputAmount() public {
IAvalonIPool dummyPool = new DummyPoolImplementation(avalonToken, true);
avalonToken.sudoMint(address(dummyPool), 100 ether); // Mint 100 tokens to this contract

AvalonLendingStrategy avalonStrategy = new AvalonLendingStrategy(avalonToken, dummyPool);

// Approve ionicStrategy to spend 100 tBTC tokens on behalf of this contract
lendingToken.increaseAllowance(address(avalonStrategy), 100);

vm.expectRevert("Insufficient output amount");
avalonStrategy.handleGatewayMessageWithSlippageArgs(
lendingToken, 100, vm.addr(1), StrategySlippageArgs(100 + 1)
);
}
}

// forge test --match-contract AvalonLstStrategyTest -vv
contract AvalonLstStrategyTest is Test {
event TokenOutput(address tokenReceived, uint256 amountOut);

ArbitaryErc20 lendingToken;
ArbitaryErc20 avalonToken;

function setUp() public {
//ToDo: When solv lst stragey unit test completed
}
}

0 comments on commit 2b0029c

Please sign in to comment.