Skip to content

Commit

Permalink
self funded ping pong (#232)
Browse files Browse the repository at this point in the history
Co-authored-by: Matt Yang <[email protected]>
Co-authored-by: Rens Rooimans <[email protected]>
  • Loading branch information
3 people authored Nov 1, 2023
1 parent fd840fa commit 2ed2a07
Show file tree
Hide file tree
Showing 10 changed files with 1,629 additions and 20 deletions.
9 changes: 6 additions & 3 deletions contracts/gas-snapshots/ccip.gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -297,9 +297,9 @@ OCR2Base_transmit:testUnAuthorizedTransmitterReverts() (gas: 23447)
OCR2Base_transmit:testUnauthorizedSignerReverts() (gas: 43673)
OCR2Base_transmit:testWrongNumberOfSignaturesReverts() (gas: 20513)
OnRampTokenPoolReentrancy:testSuccess() (gas: 334413)
PingPong_ccipReceive:testCcipReceiveSuccess() (gas: 146529)
PingPong_plumbing:testPausingSuccess() (gas: 14471)
PingPong_startPingPong:testStartPingPongSuccess() (gas: 171743)
PingPong_ccipReceive:testCcipReceiveSuccess() (gas: 145351)
PingPong_plumbing:testPausingSuccess() (gas: 14605)
PingPong_startPingPong:testStartPingPongSuccess() (gas: 170564)
PriceRegistry_applyFeeTokensUpdates:testApplyFeeTokensUpdatesSuccess() (gas: 77463)
PriceRegistry_applyFeeTokensUpdates:testOnlyCallableByOwnerReverts() (gas: 16532)
PriceRegistry_applyPriceUpdatersUpdates:testApplyPriceUpdaterUpdatesSuccess() (gas: 80844)
Expand Down Expand Up @@ -372,6 +372,9 @@ Router_routeMessage:testManualExecSuccess() (gas: 31374)
Router_routeMessage:testOnlyOffRampReverts() (gas: 27270)
Router_routeMessage:testWhenNotHealthyReverts() (gas: 43002)
Router_setWrappedNative:testOnlyOwnerReverts() (gas: 10978)
SelfFundedPingPong_ccipReceive:test_FundingIfNotANopReverts() (gas: 53509)
SelfFundedPingPong_ccipReceive:test_FundingSuccess() (gas: 403626)
SelfFundedPingPong_setCountIncrBeforeFunding:test_setCountIncrBeforeFunding() (gas: 19775)
TokenPoolWithAllowList_applyAllowListUpdates:testOnlyOwnerReverts() (gas: 12120)
TokenPoolWithAllowList_applyAllowListUpdates:testSetAllowListSkipsZeroSuccess() (gas: 20517)
TokenPoolWithAllowList_applyAllowListUpdates:testSetAllowListSuccess() (gas: 175853)
Expand Down
13 changes: 8 additions & 5 deletions contracts/scripts/native_solc_compile_all_ccip
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,20 @@ compileContractLowOpts () {
$ROOT/contracts/src/v0.8/$1
}

compileContract ccip/Router.sol
# Solc produces and overwrites intermediary contracts.
# Contracts should be ordered in reverse-import-complexity-order to minimize overwrite risks.
compileContract ccip/offRamp/EVM2EVMOffRamp.sol
compileContract ccip/applications/PingPongDemo.sol
compileContract ccip/applications/SelfFundedPingPong.sol
compileContractLowOpts ccip/onRamp/EVM2EVMOnRamp.sol
compileContract ccip/CommitStore.sol
compileContract ccip/offRamp/EVM2EVMOffRamp.sol
compileContract ccip/ARM.sol
compileContract ccip/ARMProxy.sol
compileContract ccip/Router.sol
compileContract ccip/PriceRegistry.sol
compileContract ccip/pools/LockReleaseTokenPool.sol
compileContract ccip/pools/BurnMintTokenPool.sol
compileContract shared/token/ERC677/BurnMintERC677.sol
compileContract ccip/applications/PingPongDemo.sol
compileContract ccip/ARM.sol
compileContract ccip/ARMProxy.sol

# Test helpers
compileContract ccip/test/helpers/BurnMintERC677Helper.sol
Expand Down
26 changes: 17 additions & 9 deletions contracts/src/v0.8/ccip/applications/PingPongDemo.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.0;

import {IRouterClient} from "../interfaces/IRouterClient.sol";
import {ITypeAndVersion} from "../../shared/interfaces/ITypeAndVersion.sol";

import {OwnerIsCreator} from "../../shared/access/OwnerIsCreator.sol";
import {Client} from "../libraries/Client.sol";
Expand All @@ -10,23 +11,27 @@ import {CCIPReceiver} from "./CCIPReceiver.sol";
import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol";

/// @title PingPongDemo - A simple ping-pong contract for demonstrating cross-chain communication
contract PingPongDemo is CCIPReceiver, OwnerIsCreator {
contract PingPongDemo is CCIPReceiver, OwnerIsCreator, ITypeAndVersion {
event Ping(uint256 pingPongCount);
event Pong(uint256 pingPongCount);

// The chain ID of the counterpart ping pong contract
uint64 private s_counterpartChainSelector;
uint64 internal s_counterpartChainSelector;
// The contract address of the counterpart ping pong contract
address private s_counterpartAddress;

address internal s_counterpartAddress;
// Pause ping-ponging
bool private s_isPaused;
IERC20 private s_feeToken;
// The fee token used to pay for CCIP transactions
IERC20 internal s_feeToken;

constructor(address router, IERC20 feeToken) CCIPReceiver(router) {
s_isPaused = false;
s_feeToken = feeToken;
s_feeToken.approve(address(router), 2 ** 256 - 1);
s_feeToken.approve(address(router), type(uint256).max);
}

function typeAndVersion() external pure virtual returns (string memory) {
return "PingPongDemo 1.2.0";
}

function setCounterpart(uint64 counterpartChainSelector, address counterpartAddress) external onlyOwner {
Expand All @@ -39,19 +44,18 @@ contract PingPongDemo is CCIPReceiver, OwnerIsCreator {
_respond(1);
}

function _respond(uint256 pingPongCount) private {
function _respond(uint256 pingPongCount) internal virtual {
if (pingPongCount & 1 == 1) {
emit Ping(pingPongCount);
} else {
emit Pong(pingPongCount);
}

bytes memory data = abi.encode(pingPongCount);
Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
receiver: abi.encode(s_counterpartAddress),
data: data,
tokenAmounts: new Client.EVMTokenAmount[](0),
extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: 200_000})),
extraArgs: "",
feeToken: address(s_feeToken)
});
IRouterClient(getRouter()).ccipSend(s_counterpartChainSelector, message);
Expand Down Expand Up @@ -80,6 +84,10 @@ contract PingPongDemo is CCIPReceiver, OwnerIsCreator {
return s_counterpartAddress;
}

function getFeeToken() external view returns (IERC20) {
return s_feeToken;
}

function setCounterpartAddress(address addr) external onlyOwner {
s_counterpartAddress = addr;
}
Expand Down
68 changes: 68 additions & 0 deletions contracts/src/v0.8/ccip/applications/SelfFundedPingPong.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {PingPongDemo} from "./PingPongDemo.sol";
import {Client} from "../libraries/Client.sol";
import {Router} from "../Router.sol";
import {EVM2EVMOnRamp} from "../onRamp/EVM2EVMOnRamp.sol";

import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol";

contract SelfFundedPingPong is PingPongDemo {
// solhint-disable-next-line chainlink-solidity/all-caps-constant-storage-variables
string public constant override typeAndVersion = "SelfFundedPingPong 1.2.0";

event Funded();
event CountIncrBeforeFundingSet(uint8 countIncrBeforeFunding);

// Defines the increase in ping pong count before self-funding is attempted.
// Set to 0 to disable auto-funding, auto-funding only works for ping-pongs that are set as NOPs in the onRamp.
uint8 private s_countIncrBeforeFunding;

constructor(address router, IERC20 feeToken, uint8 roundTripsBeforeFunding) PingPongDemo(router, feeToken) {
// PingPong count increases by 2 for each round trip.
s_countIncrBeforeFunding = roundTripsBeforeFunding * 2;
}

function _respond(uint256 pingPongCount) internal override {
if (pingPongCount & 1 == 1) {
emit Ping(pingPongCount);
} else {
emit Pong(pingPongCount);
}

fundPingPong(pingPongCount);

Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
receiver: abi.encode(s_counterpartAddress),
data: abi.encode(pingPongCount),
tokenAmounts: new Client.EVMTokenAmount[](0),
extraArgs: "",
feeToken: address(s_feeToken)
});
Router(getRouter()).ccipSend(s_counterpartChainSelector, message);
}

/// @notice A function that is responsible for funding this contract.
/// The contract can only be funded if it is set as a nop in the target onRamp.
/// In case your contract is not a nop you can prevent this function from being called by setting s_countIncrBeforeFunding=0.
function fundPingPong(uint256 pingPongCount) public {
// If selfFunding is disabled, or ping pong count has not reached s_countIncrPerFunding, do not attempt funding.
if (s_countIncrBeforeFunding == 0 || pingPongCount < s_countIncrBeforeFunding) return;

// Ping pong on one side will always be even, one side will always to odd.
if (pingPongCount % s_countIncrBeforeFunding <= 1) {
EVM2EVMOnRamp(Router(getRouter()).getOnRamp(s_counterpartChainSelector)).payNops();
emit Funded();
}
}

function getCountIncrBeforeFunding() external view returns (uint8) {
return s_countIncrBeforeFunding;
}

function setCountIncrBeforeFunding(uint8 countIncrBeforeFunding) public onlyOwner {
s_countIncrBeforeFunding = countIncrBeforeFunding;
emit CountIncrBeforeFundingSet(countIncrBeforeFunding);
}
}
107 changes: 107 additions & 0 deletions contracts/src/v0.8/ccip/test/applications/SelfFundedPingPong.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.19;

import {SelfFundedPingPong} from "../../applications/SelfFundedPingPong.sol";
import {EVM2EVMOnRampSetup} from "../onRamp/EVM2EVMOnRampSetup.t.sol";
import {EVM2EVMOnRamp} from "../../onRamp/EVM2EVMOnRamp.sol";
import {Client} from "../../libraries/Client.sol";

import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol";

contract SelfFundedPingPongDappSetup is EVM2EVMOnRampSetup {
event Ping(uint256 pingPongs);
event Pong(uint256 pingPongs);
event CountIncrBeforeFundingSet(uint8 countIncrBeforeFunding);

SelfFundedPingPong internal s_pingPong;
IERC20 internal s_feeToken;
uint8 internal constant s_roundTripsBeforeFunding = 0;

address internal immutable i_pongContract = address(10);

function setUp() public virtual override {
EVM2EVMOnRampSetup.setUp();

s_feeToken = IERC20(s_sourceTokens[0]);
s_pingPong = new SelfFundedPingPong(address(s_sourceRouter), s_feeToken, s_roundTripsBeforeFunding);
s_pingPong.setCounterpart(DEST_CHAIN_ID, i_pongContract);

uint256 fundingAmount = 5e18;

// set ping pong as an onRamp nop to make sure that funding runs
EVM2EVMOnRamp.NopAndWeight[] memory nopsAndWeights = new EVM2EVMOnRamp.NopAndWeight[](1);
nopsAndWeights[0] = EVM2EVMOnRamp.NopAndWeight({nop: address(s_pingPong), weight: 1});
s_onRamp.setNops(nopsAndWeights);

// Fund the contract with LINK tokens
s_feeToken.transfer(address(s_pingPong), fundingAmount);
}
}

/// @notice #ccipReceive
contract SelfFundedPingPong_ccipReceive is SelfFundedPingPongDappSetup {
event Funded();

function test_FundingSuccess() public {
Client.Any2EVMMessage memory message = Client.Any2EVMMessage({
messageId: bytes32("a"),
sourceChainSelector: DEST_CHAIN_ID,
sender: abi.encode(i_pongContract),
data: "",
destTokenAmounts: new Client.EVMTokenAmount[](0)
});

uint8 countIncrBeforeFunding = 5;

vm.expectEmit();
emit CountIncrBeforeFundingSet(countIncrBeforeFunding);

s_pingPong.setCountIncrBeforeFunding(countIncrBeforeFunding);

vm.startPrank(address(s_sourceRouter));
for (uint256 pingPongNumber = 0; pingPongNumber <= countIncrBeforeFunding; ++pingPongNumber) {
message.data = abi.encode(pingPongNumber);
if (pingPongNumber == countIncrBeforeFunding - 1) {
vm.expectEmit();
emit Funded();
vm.expectCall(address(s_onRamp), "");
}
s_pingPong.ccipReceive(message);
}
}

function test_FundingIfNotANopReverts() public {
EVM2EVMOnRamp.NopAndWeight[] memory nopsAndWeights = new EVM2EVMOnRamp.NopAndWeight[](0);
s_onRamp.setNops(nopsAndWeights);

uint8 countIncrBeforeFunding = 3;
s_pingPong.setCountIncrBeforeFunding(countIncrBeforeFunding);

vm.startPrank(address(s_sourceRouter));
Client.Any2EVMMessage memory message = Client.Any2EVMMessage({
messageId: bytes32("a"),
sourceChainSelector: DEST_CHAIN_ID,
sender: abi.encode(i_pongContract),
data: abi.encode(countIncrBeforeFunding),
destTokenAmounts: new Client.EVMTokenAmount[](0)
});

// because pingPong is not set as a nop
vm.expectRevert(EVM2EVMOnRamp.OnlyCallableByOwnerOrAdminOrNop.selector);
s_pingPong.ccipReceive(message);
}
}

/// @notice #setCountIncrBeforeFunding
contract SelfFundedPingPong_setCountIncrBeforeFunding is SelfFundedPingPongDappSetup {
function test_setCountIncrBeforeFunding() public {
uint8 c = s_pingPong.getCountIncrBeforeFunding();

vm.expectEmit();
emit CountIncrBeforeFundingSet(c + 1);

s_pingPong.setCountIncrBeforeFunding(c + 1);
uint8 c2 = s_pingPong.getCountIncrBeforeFunding();
assertEq(c2, c + 1);
}
}
Loading

0 comments on commit 2ed2a07

Please sign in to comment.