Skip to content

Commit b309847

Browse files
Updated the CCIP tutorial contracts to include support for LINK token transfers (#2875)
* Fixed TokenTransferor contract to support LINK token transfer * Fixed sol formatting * Added token balance check * Implemented the same fix in PTT tutorials
1 parent c212661 commit b309847

File tree

4 files changed

+106
-34
lines changed

4 files changed

+106
-34
lines changed

public/samples/CCIP/ProgrammableDefensiveTokenTransfers.sol

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ contract ProgrammableDefensiveTokenTransfers is CCIPReceiver, OwnerIsCreator {
2222
using SafeERC20 for IERC20;
2323

2424
// Custom errors to provide more descriptive revert messages.
25-
error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough balance to cover the fees.
25+
error NotEnoughBalance(uint256 currentBalance, uint256 requiredBalance); // Used to make sure contract has enough token balance
2626
error NothingToWithdraw(); // Used when trying to withdraw Ether but there's nothing to withdraw.
2727
error FailedToWithdrawEth(address owner, address target, uint256 value); // Used when the withdrawal of Ether fails.
2828
error DestinationChainNotAllowlisted(uint64 destinationChainSelector); // Used when the destination chain has not been allowlisted by the contract owner.
@@ -204,14 +204,32 @@ contract ProgrammableDefensiveTokenTransfers is CCIPReceiver, OwnerIsCreator {
204204
// Get the fee required to send the CCIP message
205205
uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage);
206206

207-
if (fees > s_linkToken.balanceOf(address(this)))
208-
revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees);
207+
uint256 requiredLinkBalance;
208+
if (_token == address(s_linkToken)) {
209+
// Required LINK Balance is the sum of fees and amount to transfer, if the token to transfer is LINK
210+
requiredLinkBalance = fees + _amount;
211+
} else {
212+
requiredLinkBalance = fees;
213+
}
209214

210-
// approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK
211-
s_linkToken.approve(address(router), fees);
215+
uint256 linkBalance = s_linkToken.balanceOf(address(this));
212216

213-
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
214-
IERC20(_token).approve(address(router), _amount);
217+
if (requiredLinkBalance > linkBalance) {
218+
revert NotEnoughBalance(linkBalance, requiredLinkBalance);
219+
}
220+
221+
// approve the Router to transfer LINK tokens on contract's behalf. It will spend the requiredLinkBalance
222+
s_linkToken.approve(address(router), requiredLinkBalance);
223+
224+
// If sending a token other than LINK, approve it separately
225+
if (_token != address(s_linkToken)) {
226+
uint256 tokenBalance = IERC20(_token).balanceOf(address(this));
227+
if (_amount > tokenBalance) {
228+
revert NotEnoughBalance(tokenBalance, _amount);
229+
}
230+
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
231+
IERC20(_token).approve(address(router), _amount);
232+
}
215233

216234
// Send the message through the router and store the returned message ID
217235
messageId = router.ccipSend(_destinationChainSelector, evm2AnyMessage);

public/samples/CCIP/ProgrammableTokenTransfers.sol

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ contract ProgrammableTokenTransfers is CCIPReceiver, OwnerIsCreator {
1919
using SafeERC20 for IERC20;
2020

2121
// Custom errors to provide more descriptive revert messages.
22-
error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough balance to cover the fees.
22+
error NotEnoughBalance(uint256 currentBalance, uint256 requiredBalance); // Used to make sure contract has enough token balance
2323
error NothingToWithdraw(); // Used when trying to withdraw Ether but there's nothing to withdraw.
2424
error FailedToWithdrawEth(address owner, address target, uint256 value); // Used when the withdrawal of Ether fails.
2525
error DestinationChainNotAllowed(uint64 destinationChainSelector); // Used when the destination chain has not been allowlisted by the contract owner.
@@ -165,14 +165,32 @@ contract ProgrammableTokenTransfers is CCIPReceiver, OwnerIsCreator {
165165
// Get the fee required to send the CCIP message
166166
uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage);
167167

168-
if (fees > s_linkToken.balanceOf(address(this)))
169-
revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees);
170-
171-
// approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK
172-
s_linkToken.approve(address(router), fees);
173-
174-
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
175-
IERC20(_token).approve(address(router), _amount);
168+
uint256 requiredLinkBalance;
169+
if (_token == address(s_linkToken)) {
170+
// Required LINK Balance is the sum of fees and amount to transfer, if the token to transfer is LINK
171+
requiredLinkBalance = fees + _amount;
172+
} else {
173+
requiredLinkBalance = fees;
174+
}
175+
176+
uint256 linkBalance = s_linkToken.balanceOf(address(this));
177+
178+
if (requiredLinkBalance > linkBalance) {
179+
revert NotEnoughBalance(linkBalance, requiredLinkBalance);
180+
}
181+
182+
// approve the Router to transfer LINK tokens on contract's behalf. It will spend the requiredLinkBalance
183+
s_linkToken.approve(address(router), requiredLinkBalance);
184+
185+
// If sending a token other than LINK, approve it separately
186+
if (_token != address(s_linkToken)) {
187+
uint256 tokenBalance = IERC20(_token).balanceOf(address(this));
188+
if (_amount > tokenBalance) {
189+
revert NotEnoughBalance(tokenBalance, _amount);
190+
}
191+
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
192+
IERC20(_token).approve(address(router), _amount);
193+
}
176194

177195
// Send the message through the router and store the returned message ID
178196
messageId = router.ccipSend(_destinationChainSelector, evm2AnyMessage);

public/samples/CCIP/ProgrammableTokenTransfersLowGasLimit.sol

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ contract ProgrammableTokenTransfersLowGasLimit is CCIPReceiver, OwnerIsCreator {
1919
using SafeERC20 for IERC20;
2020

2121
// Custom errors to provide more descriptive revert messages.
22-
error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough balance to cover the fees.
22+
error NotEnoughBalance(uint256 currentBalance, uint256 requiredBalance); // Used to make sure contract has enough token balance
2323
error NothingToWithdraw(); // Used when trying to withdraw Ether but there's nothing to withdraw.
2424
error DestinationChainNotAllowed(uint64 destinationChainSelector); // Used when the destination chain has not been allowlisted by the contract owner.
2525
error SourceChainNotAllowed(uint64 sourceChainSelector); // Used when the source chain has not been allowlisted by the contract owner.
@@ -175,14 +175,32 @@ contract ProgrammableTokenTransfersLowGasLimit is CCIPReceiver, OwnerIsCreator {
175175
// Get the fee required to send the CCIP message
176176
uint256 fees = router.getFee(_destinationChainSelector, evm2AnyMessage);
177177

178-
if (fees > s_linkToken.balanceOf(address(this)))
179-
revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees);
180-
181-
// approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK
182-
s_linkToken.approve(address(router), fees);
183-
184-
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
185-
IERC20(_token).approve(address(router), _amount);
178+
uint256 requiredLinkBalance;
179+
if (_token == address(s_linkToken)) {
180+
// Required LINK Balance is the sum of fees and amount to transfer, if the token to transfer is LINK
181+
requiredLinkBalance = fees + _amount;
182+
} else {
183+
requiredLinkBalance = fees;
184+
}
185+
186+
uint256 linkBalance = s_linkToken.balanceOf(address(this));
187+
188+
if (requiredLinkBalance > linkBalance) {
189+
revert NotEnoughBalance(linkBalance, requiredLinkBalance);
190+
}
191+
192+
// approve the Router to transfer LINK tokens on contract's behalf. It will spend the requiredLinkBalance
193+
s_linkToken.approve(address(router), requiredLinkBalance);
194+
195+
// If sending a token other than LINK, approve it separately
196+
if (_token != address(s_linkToken)) {
197+
uint256 tokenBalance = IERC20(_token).balanceOf(address(this));
198+
if (_amount > tokenBalance) {
199+
revert NotEnoughBalance(tokenBalance, _amount);
200+
}
201+
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
202+
IERC20(_token).approve(address(router), _amount);
203+
}
186204

187205
// Send the message through the router and store the returned message ID
188206
messageId = router.ccipSend(_destinationChainSelector, evm2AnyMessage);

public/samples/CCIP/TokenTransferor.sol

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ contract TokenTransferor is OwnerIsCreator {
1818
using SafeERC20 for IERC20;
1919

2020
// Custom errors to provide more descriptive revert messages.
21-
error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough balance to cover the fees.
21+
error NotEnoughBalance(uint256 currentBalance, uint256 requiredBalance); // Used to make sure contract has enough token balance
2222
error NothingToWithdraw(); // Used when trying to withdraw Ether but there's nothing to withdraw.
2323
error FailedToWithdrawEth(address owner, address target, uint256 value); // Used when the withdrawal of Ether fails.
2424
error DestinationChainNotAllowlisted(uint64 destinationChainSelector); // Used when the destination chain has not been allowlisted by the contract owner.
@@ -112,14 +112,32 @@ contract TokenTransferor is OwnerIsCreator {
112112
evm2AnyMessage
113113
);
114114

115-
if (fees > s_linkToken.balanceOf(address(this)))
116-
revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees);
117-
118-
// approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK
119-
s_linkToken.approve(address(s_router), fees);
120-
121-
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
122-
IERC20(_token).approve(address(s_router), _amount);
115+
uint256 requiredLinkBalance;
116+
if (_token == address(s_linkToken)) {
117+
// Required LINK Balance is the sum of fees and amount to transfer, if the token to transfer is LINK
118+
requiredLinkBalance = fees + _amount;
119+
} else {
120+
requiredLinkBalance = fees;
121+
}
122+
123+
uint256 linkBalance = s_linkToken.balanceOf(address(this));
124+
125+
if (requiredLinkBalance > linkBalance) {
126+
revert NotEnoughBalance(linkBalance, requiredLinkBalance);
127+
}
128+
129+
// approve the Router to transfer LINK tokens on contract's behalf. It will spend the requiredLinkBalance
130+
s_linkToken.approve(address(s_router), requiredLinkBalance);
131+
132+
// If sending a token other than LINK, approve it separately
133+
if (_token != address(s_linkToken)) {
134+
uint256 tokenBalance = IERC20(_token).balanceOf(address(this));
135+
if (_amount > tokenBalance) {
136+
revert NotEnoughBalance(tokenBalance, _amount);
137+
}
138+
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token
139+
IERC20(_token).approve(address(s_router), _amount);
140+
}
123141

124142
// Send the message through the router and store the returned message ID
125143
messageId = s_router.ccipSend(

0 commit comments

Comments
 (0)