Skip to content

Commit

Permalink
add live test for OneSidedECLPJoiner on arbi
Browse files Browse the repository at this point in the history
This tests a deployed contract at a given block (approximately now). It
doesn't simulated-deploy new code. See vaults-dev for the code.
  • Loading branch information
sschuldenzucker committed Nov 4, 2024
1 parent e78a803 commit b5708b2
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 0 deletions.
44 changes: 44 additions & 0 deletions lib/gyro-pools/IOneSidedECLPJoiner.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: LicenseRef-Gyro-1.0
// for information on licensing please see the README in the GitHub repository <https://github.com/gyrostable/concentrated-lps>.

// pragma solidity ^0.7.6;

/// @notice A stateless helper contract to join an ECLP pool with only one of the two assets.
///
/// This is slightly inaccurate right now, i.e., not all assets are used due incomplete accounting for fees.
interface IOneSidedECLPJoiner {
/** @notice Perform a swap-join combination to join the pool only with the `tokenInAddress`
* asset. LP shares and leftover assets are sent to `beneficiary`. An approval needs to be
* set up.
*
* @param poolAddress Address of the pool to join
* @param tokenInAddress Address of the token to deposit. Must be one of the tokens of the
* pool.
* @param tokenInAmountRaw Token amount to deposit. Not scaled by decimals or rates.
* @param beneficiary Address to receive the LP shares and any leftover tokens.
*/
function joinECLPOneSided(
address poolAddress,
address tokenInAddress,
uint256 tokenInAmountRaw,
address beneficiary
) external;

/** @notice Variant of `joinECLPOneSided()` where (1) this contract needs to be prefunded with
* the amount of `tokenInAddress` and the whole amount will be used and (2) this never reverts
* unless something is wrong with the `tokenInAddress` or the transfer functions of the involved
* tokens are broken. In case an inner function reverts, all token amounts held are sent to
* `beneficiary`.
*
* This is to be used as together with the GYD CCIP bridge's function call feature.
*/
function joinECLPOneSidedCCIP(
address poolAddress,
address tokenInAddress,
address beneficiary
) external;

/// @notice Emitted by `joinECLPOneSidedCCIP` if execution failed in an inner call.
event ExecutionFailed(string reason, address beneficiary);
event ExecutionFailed(bytes data, address beneficiary);
}
146 changes: 146 additions & 0 deletions test/OneSidedECLPJoinerLive.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "forge-std/Test.sol";
import "../lib/gyro-pools/IOneSidedECLPJoiner.sol";
import "../src/L2GYD.sol";
import {IERC20} from "oz/token/ERC20/IERC20.sol";

contract OneSidedECLPJoinerLiveTest is Test {
IOneSidedECLPJoiner joiner = IOneSidedECLPJoiner(0xA0a555c1c11ef36D2381768EB734Fa2bddf1322b);
L2Gyd l2gyd = L2Gyd(0xCA5d8F8a8d49439357d3CF46Ca2e720702F132b8);

address alice = makeAddr("alice");
address router = 0x141fa059441E0ca23ce184B6A78bafD2A517DdE8;
address l1escrow = 0xCA5d8F8a8d49439357d3CF46Ca2e720702F132b8;
address bootstrapping_pool_addr = 0x820b69faD931d4b4Bf14E70fF234A8390F6A0658;

// Here this is stataUSDC
address other_token_addr = 0x7CFaDFD5645B50bE87d546f42699d863648251ad;

uint64 mainnetChainSelector = 3_734_403_246_176_062_136;

uint256 MAX_UNDEPLOYED_PERCENTAGE = 1e-4 * 1e18; // 1bp

function setUp() public {
// Fork arbitrum.
// Block from: Mon Nov 4 16:57:12 CET 2024
vm.createSelectFork(vm.envString("ARBITRUM_RPC_URL"), 270993705);
}

function testExecutionNormal1() public {
_testExecutionNormal(1e6 * 1e18);
}

function testExecutionNormal2() public {
_testExecutionNormal(1e4 * 1e18);
}

/// @notice Tests a "normal" execution, where some address approves tokens, then calls into
/// the joiner.
function _testExecutionNormal(uint256 amount_in_s) internal {
// First, fund alice with GYD from the bridge. We have to jump through all of the hoops
// unfortunately.
vm.startPrank(router);
bytes memory data = abi.encode(alice, amount_in_s, "");
l2gyd.ccipReceive(
_receivedMessage(mainnetChainSelector, l1escrow, data)
);
vm.stopPrank();

// Now, call into the joiner.
vm.startPrank(alice);
l2gyd.approve(address(joiner), amount_in_s);
joiner.joinECLPOneSided(bootstrapping_pool_addr, address(l2gyd), amount_in_s, alice);
vm.stopPrank();

IERC20 lp_token = IERC20(bootstrapping_pool_addr);
IERC20 gyd_token = IERC20(address(l2gyd));
IERC20 other_token = IERC20(other_token_addr);

// joiner has no tokens left
assertEq(lp_token.balanceOf(address(joiner)), 0);
assertEq(gyd_token.balanceOf(address(joiner)), 0);
assertEq(other_token.balanceOf(address(joiner)), 0);

// alice now has tokens as expected
assertGt(lp_token.balanceOf(alice), 0);
assertEq(other_token.balanceOf(alice), 0);

// not too many tokens are leftover
uint256 undeployed_gyd = gyd_token.balanceOf(alice);
console.log("Total undeployed GYD", undeployed_gyd);
console.log("Bp/100 undeployed (approximate)", divDown(undeployed_gyd, amount_in_s) * 1e6 / 1e18);
assertLe(undeployed_gyd, mulDown(amount_in_s, MAX_UNDEPLOYED_PERCENTAGE));
}

function testExecutionCCIP1() public {
_testExecutionCCIP(1e6 * 1e18);
}

function testExecutionCCIP2() public {
_testExecutionCCIP(1e4 * 1e18);
}

/// @notice Tests an execution where the contract is called from CCIP using its special method.
function _testExecutionCCIP(uint256 amount_in_s) internal {
bytes memory call_data = abi.encodeWithSelector(
joiner.joinECLPOneSidedCCIP.selector,
bootstrapping_pool_addr,
address(l2gyd),
alice
);

vm.startPrank(router);
bytes memory data = abi.encode(address(joiner), amount_in_s, call_data);
l2gyd.ccipReceive(
_receivedMessage(mainnetChainSelector, l1escrow, data)
);
// TODO this fails.
vm.stopPrank();
// TODO ^ assert that it does NOT emit the failure event.

// After this, everything should look just like above:

IERC20 lp_token = IERC20(bootstrapping_pool_addr);
IERC20 gyd_token = IERC20(address(l2gyd));
IERC20 other_token = IERC20(other_token_addr);

// joiner has no tokens left
assertEq(lp_token.balanceOf(address(joiner)), 0);
assertEq(gyd_token.balanceOf(address(joiner)), 0);
assertEq(other_token.balanceOf(address(joiner)), 0);

// alice now has tokens as expected
assertGt(lp_token.balanceOf(alice), 0);
assertEq(other_token.balanceOf(alice), 0);

// not too many tokens are leftover
uint256 undeployed_gyd = gyd_token.balanceOf(alice);
console.log("Total undeployed GYD", undeployed_gyd);
console.log("Bp/100 undeployed (approximate)", divDown(undeployed_gyd, amount_in_s) * 1e6 / 1e18);
assertLe(undeployed_gyd, mulDown(amount_in_s, MAX_UNDEPLOYED_PERCENTAGE));
}

function _receivedMessage(
uint64 chainSelector,
address senderContract,
bytes memory data
) internal pure returns (Client.Any2EVMMessage memory) {
return Client.Any2EVMMessage({
messageId: 0,
sourceChainSelector: chainSelector,
sender: abi.encode(senderContract),
data: data,
destTokenAmounts: new Client.EVMTokenAmount[](0)
});
}

function mulDown(uint256 a, uint256 b) internal pure returns (uint256) {
return a * b / 1e18;
}

function divDown(uint256 a, uint256 b) internal pure returns (uint256) {
return a * 1e18 / b;
}
}

0 comments on commit b5708b2

Please sign in to comment.