-
-
Save cryptoscopia/1156a368c19a82be2d083e04376d261e to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: AGPL-3.0-or-later | |
// The ABI encoder is necessary, but older Solidity versions should work | |
pragma solidity ^0.7.0; | |
pragma experimental ABIEncoderV2; | |
// These definitions are taken from across multiple dydx contracts, and are | |
// limited to just the bare minimum necessary to make flash loans work. | |
library Types { | |
enum AssetDenomination { Wei, Par } | |
enum AssetReference { Delta, Target } | |
struct AssetAmount { | |
bool sign; | |
AssetDenomination denomination; | |
AssetReference ref; | |
uint256 value; | |
} | |
} | |
library Account { | |
struct Info { | |
address owner; | |
uint256 number; | |
} | |
} | |
library Actions { | |
enum ActionType { | |
Deposit, Withdraw, Transfer, Buy, Sell, Trade, Liquidate, Vaporize, Call | |
} | |
struct ActionArgs { | |
ActionType actionType; | |
uint256 accountId; | |
Types.AssetAmount amount; | |
uint256 primaryMarketId; | |
uint256 secondaryMarketId; | |
address otherAddress; | |
uint256 otherAccountId; | |
bytes data; | |
} | |
} | |
interface ISoloMargin { | |
function operate(Account.Info[] memory accounts, Actions.ActionArgs[] memory actions) external; | |
} | |
// The interface for a contract to be callable after receiving a flash loan | |
interface ICallee { | |
function callFunction(address sender, Account.Info memory accountInfo, bytes memory data) external; | |
} | |
// Standard ERC-20 interface | |
interface IERC20 { | |
function totalSupply() external view returns (uint256); | |
function balanceOf(address account) external view returns (uint256); | |
function transfer(address recipient, uint256 amount) external returns (bool); | |
function allowance(address owner, address spender) external view returns (uint256); | |
function approve(address spender, uint256 amount) external returns (bool); | |
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); | |
event Transfer(address indexed from, address indexed to, uint256 value); | |
event Approval(address indexed owner, address indexed spender, uint256 value); | |
} | |
// Additional methods available for WETH | |
interface IWETH is IERC20 { | |
function deposit() external payable; | |
function withdraw(uint wad) external; | |
} | |
contract FlashLoanTemplate is ICallee { | |
// The WETH token contract, since we're assuming we want a loan in WETH | |
IWETH private WETH = IWETH(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); | |
// The dydx Solo Margin contract, as can be found here: | |
// https://github.com/dydxprotocol/solo/blob/master/migrations/deployed.json | |
ISoloMargin private soloMargin = ISoloMargin(0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e); | |
constructor() { | |
// Give infinite approval to dydx to withdraw WETH on contract deployment, | |
// so we don't have to approve the loan repayment amount (+2 wei) on each call. | |
// The approval is used by the dydx contract to pay the loan back to itself. | |
WETH.approve(address(soloMargin), uint(-1)); | |
} | |
// This is the function we call | |
function flashLoan(uint loanAmount) external { | |
/* | |
The flash loan functionality in dydx is predicated by their "operate" function, | |
which takes a list of operations to execute, and defers validating the state of | |
things until it's done executing them. | |
We thus create three operations, a Withdraw (which loans us the funds), a Call | |
(which invokes the callFunction method on this contract), and a Deposit (which | |
repays the loan, plus the 2 wei fee), and pass them all to "operate". | |
Note that the Deposit operation will invoke the transferFrom to pay the loan | |
(or whatever amount it was initialised with) back to itself, there is no need | |
to pay it back explicitly. | |
The loan must be given as an ERC-20 token, so WETH is used instead of ETH. Other | |
currencies (DAI, USDC) are also available, their index can be looked up by | |
calling getMarketTokenAddress on the solo margin contract, and set as the | |
primaryMarketId in the Withdraw and Deposit definitions. | |
*/ | |
Actions.ActionArgs[] memory operations = new Actions.ActionArgs[](3); | |
operations[0] = Actions.ActionArgs({ | |
actionType: Actions.ActionType.Withdraw, | |
accountId: 0, | |
amount: Types.AssetAmount({ | |
sign: false, | |
denomination: Types.AssetDenomination.Wei, | |
ref: Types.AssetReference.Delta, | |
value: loanAmount // Amount to borrow | |
}), | |
primaryMarketId: 0, // WETH | |
secondaryMarketId: 0, | |
otherAddress: address(this), | |
otherAccountId: 0, | |
data: "" | |
}); | |
operations[1] = Actions.ActionArgs({ | |
actionType: Actions.ActionType.Call, | |
accountId: 0, | |
amount: Types.AssetAmount({ | |
sign: false, | |
denomination: Types.AssetDenomination.Wei, | |
ref: Types.AssetReference.Delta, | |
value: 0 | |
}), | |
primaryMarketId: 0, | |
secondaryMarketId: 0, | |
otherAddress: address(this), | |
otherAccountId: 0, | |
data: abi.encode( | |
// Replace or add any additional variables that you want | |
// to be available to the receiver function | |
msg.sender, | |
loanAmount | |
) | |
}); | |
operations[2] = Actions.ActionArgs({ | |
actionType: Actions.ActionType.Deposit, | |
accountId: 0, | |
amount: Types.AssetAmount({ | |
sign: true, | |
denomination: Types.AssetDenomination.Wei, | |
ref: Types.AssetReference.Delta, | |
value: loanAmount + 2 // Repayment amount with 2 wei fee | |
}), | |
primaryMarketId: 0, // WETH | |
secondaryMarketId: 0, | |
otherAddress: address(this), | |
otherAccountId: 0, | |
data: "" | |
}); | |
Account.Info[] memory accountInfos = new Account.Info[](1); | |
accountInfos[0] = Account.Info({owner: address(this), number: 1}); | |
soloMargin.operate(accountInfos, operations); | |
} | |
// This is the function called by dydx after giving us the loan | |
function callFunction(address sender, Account.Info memory accountInfo, bytes memory data) external override { | |
// Decode the passed variables from the data object | |
( | |
// This must match the variables defined in the Call object above | |
address payable actualSender, | |
uint loanAmount | |
) = abi.decode(data, ( | |
address, uint | |
)); | |
// We now have a WETH balance of loanAmount. The logic for what we | |
// want to do with it goes here. The code below is just there in case | |
// it's useful. | |
// It can be useful for debugging to have a verbose error message when | |
// the loan can't be paid, since dydx doesn't provide one | |
require(WETH.balanceOf(address(this)) > loanAmount + 2, "CANNOT REPAY LOAN"); | |
// Leave just enough WETH to pay back the loan, and convert the rest to ETH | |
WETH.withdraw(WETH.balanceOf(address(this)) - loanAmount - 2); | |
// Send any profit in ETH to the account that invoked this transaction | |
actualSender.transfer(address(this).balance); | |
} | |
} |
Hi @cryptoscopia , I'm running into an issue, hope someone can help.
I've replaced the WETH and Solo contract for Kovan, the rest of the contract is original.
IWETH private WETH = IWETH(0xd0A1E359811322d97991E03f863a0C30C2cF029C);
ISoloMargin private soloMargin = ISoloMargin(0x4EC3570cADaAEE08Ae384779B0f3A45EF85289DE);
I've deposited some WETH on my contract to repay the loan, the callbackfunction is unmodified. But.. my transactions keep failing, see https://kovan.etherscan.io/tx/0xf82df4461d4b0b56c3405a2eeb8c4fbdd5abbbe0526fe144ab2e7974630ce4e4#internal for an example.
Any idea how I can troubleshoot this ? Thanks!
dydx's solo contract is not on any testnet @Oliver917
You can try forking eth mainnet state with ganache-cli and then run the flashloan locally.
dydx's solo contract is deployed on Kovan, for about 3 years now.
https://github.com/dydxprotocol/solo/blame/master/migrations/deployed.json
{
"SoloMargin": {
"1": {
"links": {
"AdminImpl": "0x8a6629fEba4196E0A61B8E8C94D4905e525bc055",
"OperationImpl": "0x56E7d4520ABFECf10b38368b00723d9BD3c21ee1"
},
"address": "0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e",
"transactionHash": "0x5d824b179e39313f45da503b1d75c1c7ce5287646a45ebe42431d50141fd451a"
},
"42": {
"links": {
"AdminImpl": "0x078D400C1a723b792de8afE240bf039b7069ac39",
"OperationImpl": "0xcfa0451D8D1F08504Fa44a6f72D00F455b858a1d"
},
"address": "0x4EC3570cADaAEE08Ae384779B0f3A45EF85289DE",
"transactionHash": "0xb949f10eabe6b7e66c7304a3e9d300375638e82932c5c1e723da3530ace277e3"
}
},
For correct Kovan addresses:
if (isKovan(network)) {
return [
{ address: '0xd0a1e359811322d97991e03f863a0c30c2cf029c' }, // Kovan WETH
{ address: '0xc4375b7de8af5a38a93548eb8453a498222c4ff2' }, // Kovan DAI
{ address: '0x03226d9241875DbFBfE0e814ADF54151e4F3fd4B' }, // Kovan USDC
];
}
FlashLoan.sol:167:27: Warning: Unused function parameter. Remove or comment out the variable name to silence this warning.
function callFunction(address sender, Account.Info memory accountInfo, bytes memory data) external override {
^------------^
FlashLoan.sol:167:43: Warning: Unused function parameter. Remove or comment out the variable name to silence this warning.
function callFunction(address sender, Account.Info memory accountInfo, bytes memory data) external override {
^-----------------------------^
help pls
@tyler904 same problem! someone to guide please
FlashLoan.sol:167:27: Warning: Unused function parameter. Remove or comment out the variable name to silence this warning. function callFunction(address sender, Account.Info memory accountInfo, bytes memory data) external override { ^------------^ FlashLoan.sol:167:43: Warning: Unused function parameter. Remove or comment out the variable name to silence this warning. function callFunction(address sender, Account.Info memory accountInfo, bytes memory data) external override { ^-----------------------------^
help pls
Hi, just remove the variable name and leave the type. The warning should disappear.
Cheers
Hi, just came across this and think it's great! Thanks for it. I have a question though. In the flashloan function I see it collecting data for a withdraw, call and deposit and finishes with soloMargin.operate(). Is the 'callFunction' called from here? I'm assuming it is during operations[1] and once complete it returns the load in operations[2]? Thanks for helping out a newbie.