Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

don't merge me - Feat/add funds #276

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
109 changes: 109 additions & 0 deletions contracts/Multipay.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.8.6;

import './interfaces/IJBPaymentTerminal.sol';

/**
@title
Juicebox Multi-addToBalance / multi-pay() with custom beneficiaries
@dev
Part of the FC post-bug recovery -> this to recreate the activity existing projects affected
had and refund the gas used while deploying the previous (buggy) project
*/
contract Multipay {
IJBPaymentTerminal public immutable jbTerminal;

//eligible for gas refund: [28, 26, 24, 23, 22, 21, 19, 13, 11, 10, 4, 1]

/**
@param _jbTerminal the current eth terminal
*/
constructor(IJBPaymentTerminal _jbTerminal) {
jbTerminal = _jbTerminal;
}

/**
@notice wrapper around the gas refund and the pay(..) calls
@dev the 4 first arrays NEED to be ordered accordingly
@param projectIds the project id to contribute to
@param beneficiaries the beneficiary contributing to the corresponding projectId
@param amounts the amount to contribute, in wei
@param memos the optional memo passed
@param projectsGas the lish of project id requiring a gas refund
*/
function process(
uint256[] calldata projectIds,
address[] calldata beneficiaries,
uint256[] calldata amounts,
string[] calldata memos,
uint256[] calldata projectsGas
) external payable {
refundGas(projectsGas);
processPay(
projectIds,
beneficiaries,
amounts,
memos
);
}

/**
@notice stand-alone to refund gas to projects - hardcoded at 0.2eth for fairness
*/
function refundGas(uint256[] calldata projectsGas) public payable {
for (uint256 i; i < projectsGas.length; ++i) {
jbTerminal.addToBalanceOf{value: 0.2 ether}(
projectsGas[i],
0.2 ether,
address(0),
'gas refund',
new bytes(0)
);
}
}

/**
@notice stand-alone to process a list of pay(..) to trigger
@dev the 4 arrays NEED to follow the same order. ETH left-over are sent back to the caller.
*/
function processPay(
uint256[] calldata projectIds,
address[] calldata beneficiaries,
uint256[] calldata amounts,
string[] calldata memos
) public payable {
for (uint256 i; i < projectIds.length; ++i) {
jbTerminal.pay{value: amounts[i]}(
projectIds[i],
amounts[i],
address(0),
beneficiaries[i],
0,
true,
memos[i],
new bytes(0)
);
}

if (payable(address(this)).balance > 0)
payable(msg.sender).call{value: payable(address(this)).balance}('');
}

/**
@notice compute the amount (in wei) to send to cover the activity based on the
array passed
*/
function computeTotalEthToSend(
uint256[] calldata projectIds,
address[] calldata beneficiaries,
uint256[] calldata amounts,
string[] calldata memos,
uint256[] calldata gasToRefund
) external view returns (uint256 amount) {
amount = 0.2 ether * gasToRefund.length;

for (uint256 i; i < projectIds.length; ++i) {
amount += amounts[i];
}
}
}
54 changes: 54 additions & 0 deletions contracts/system_tests/E2ETestMultipay.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;

// Run on a fork (--rpc-url MAINNET_RPC)
import './helpers/hevm.sol';
import '../../lib/ds-test/src/test.sol';

import '../interfaces/IJBPaymentTerminal.sol';
import '../interfaces/IJBTokenStore.sol';
import '../Multipay.sol';

/**
@dev run this test suite against a forked chain: forge test --rpc-url MY_RPC --match-contract TestMultipay
*/
contract TestMultipay is DSTest {

IJBPaymentTerminal jbTerminal = IJBPaymentTerminal(0x7Ae63FBa045Fec7CaE1a75cF7Aa14183483b8397);
IJBTokenStore tokenStore = IJBTokenStore(0xCBB8e16d998161AdB20465830107ca298995f371);

uint256[] projectIds = [4, 4, 3, 3, 3];
uint256[] amounts = [50000000000000000, 50600000000000000, 80000000000000000, 200000000000000000, 8000000000000000];
address[] beneficiaries = [0xCF2a6c431a7a302c1DeF53174f2582bFd3979e88, 0xC655ab8D19138239F7397787a55B0CCeEFd73Fd7,
0xa638E44Da7702b11588f90a0a14b7667937E252f, 0x30670D81E487c80b9EDc54370e6EaF943B6EAB39, 0xF6633b9d1278006d69B71b479D0D553562883494];
string[] memos = ['', 'Let\'s Get Juicy!', '', '', 'w img 1'];

Multipay multipay;

Hevm public evm = Hevm(HEVM_ADDRESS);

function setUp() public {
multipay = new Multipay(jbTerminal);
}

function testComputeTotalEthToSend() public {
uint256 toSend = multipay.computeTotalEthToSend(projectIds, beneficiaries, amounts, memos, projectIds);

uint256 amount;
for(uint i; i<amounts.length; i++) amount += amounts[i];
amount += projectIds.length * 0.2 ether;

assertEq(toSend, amount);
}

function testProcess() public {
uint256 toSend = multipay.computeTotalEthToSend(projectIds, beneficiaries, amounts, memos, projectIds);

multipay.process{value: toSend}(projectIds, beneficiaries, amounts, memos, projectIds);

// Sanity check: did we received project token (assuming no project has a 0 weight)
for(uint256 i; i < beneficiaries.length; ++i)
assertGt(tokenStore.balanceOf(beneficiaries[i], projectIds[i]), 0);
}

}
92 changes: 92 additions & 0 deletions deploy/3-rescue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
const { ethers } = require('hardhat');
const toFundBack = require("./toFundBack.json")
const toReimburseForGas = require("./gasReimburse.json")
const JBTerminal = require("../deployments/mainnet/JBETHPaymentTerminal.json");

/**
* Deploy and use a multipayer contract
*
* Example usage:
*
* npx hardhat deploy --network rinkeby --tag 3
*/
module.exports = async ({ deployments, getChainId }) => {
console.log("Deploying multipayer & send payment");

const { deploy } = deployments;
const [deployer] = await ethers.getSigners();

let multisigAddress;
let chainId = await getChainId();
let baseDeployArgs = {
from: deployer.address,
log: true,
skipIfAlreadyDeployed: true,
};

console.log({ deployer: deployer.address, chain: chainId });

switch (chainId) {
// mainnet
case '1':
multisigAddress = '0xAF28bcB48C40dBC86f52D459A6562F658fc94B1e';
break;
// rinkeby
case '4':
multisigAddress = '0xAF28bcB48C40dBC86f52D459A6562F658fc94B1e';
break;
// hardhat / localhost
case '31337':
multisigAddress = deployer.address;
break;
}

console.log({ multisigAddress });

const multipayDeployment = await deploy('Multipay', {
...baseDeployArgs,
args: [JBTerminal.address],
});

const multipay = new ethers.Contract(multipayDeployment.address, multipayDeployment.abi);

let projectId = [];
let amounts = [];
let beneficiaries = [];
let memos = [];
let projectToReimburseGas = [];

for (let i = 0; i < toFundBack.length; i++) {
projectId[i] = toFundBack[i].projectId;
beneficiaries[i] = toFundBack[i].beneficiaries;
amounts[i] = toFundBack[i].amounts;
memos[i] = toFundBack[i].memos;
}

for (let i = 0; i < toReimburseForGas[0].projectIds.length; i++) {
projectToReimburseGas[i] = toReimburseForGas[0].projectIds[i];
}

const ethToSend = await multipay.connect(deployer).computeTotalEthToSend(
projectId,
beneficiaries,
amounts,
memos,
projectToReimburseGas
);

console.log('about to send ' + ethToSend / 10 ** 18 + 'eth');

await multipay.connect(deployer).process(
projectId,
beneficiaries,
amounts,
memos,
projectToReimburseGas,
{ value: ethToSend }
);

console.log('Done');
};

module.exports.tags = ['3'];
9 changes: 9 additions & 0 deletions deploy/gasReimburse.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
{
"projectIds": [
26,
4,
3
]
}
]
Loading