Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,8 @@ src/vendor
src/dist
config.js
blockchain/EVM/build
blockchain/EVM/artifacts
blockchain/EVM/cache
blockchain/EVM/ignition/deployments
blockchain/solana/test/data
tmp/
34 changes: 34 additions & 0 deletions blockchain/EVM/hardhat.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
require('@nomicfoundation/hardhat-ethers');
require('@nomicfoundation/hardhat-ignition-ethers');

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: {
compilers: [
{ version: '0.4.24' },
// add more here
]
},
ignition: {
requiredConfirmations: 1
},
networks: {
geth: {
url: `http://geth:8545`,
gas: 4700000,
chainId: 1337
},
local: {
url: process.env.HARDHAT_URL || 'http://localhost:8545',
gas: 4700000,
chainId: 1337
}
},
paths: {
sources: './contracts',
tests: './test',
cache: './cache',
artifacts: './artifacts',
modules: './ignition/modules'
}
};
29 changes: 29 additions & 0 deletions blockchain/EVM/ignition/modules/all.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const hardhat = require('hardhat');
const { buildModule } = require('@nomicfoundation/hardhat-ignition/modules');
const fs = require('fs');

const sequential = process.env.HH_SEQUENTIAL_DEPLOY == '1' || hardhat.network.config['sequentialDeploy'];
if (sequential) {
// eslint-disable-next-line no-console
console.log('Sequential deployment enabled');
}


module.exports = buildModule('Deploy_All', (m) => {
const mods = fs.readdirSync(__dirname);
let prev;
for (const mod of mods) {
if (mod === __filename.split('/').pop()) {
continue;
}
const contracts = m.useModule(require(`${__dirname}/${mod}`));
if (sequential) { // sequentially deploy the contracts instead of in parallel
if (prev) {
for (const contract of Object.values(contracts)) {
contract.dependencies.add(prev);
}
}
prev = Object.values(contracts)[0];
}
}
});
7 changes: 7 additions & 0 deletions blockchain/EVM/ignition/modules/erc20.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const { buildModule } = require('@nomicfoundation/hardhat-ignition/modules');


module.exports = buildModule('ERC20', (m) => {
const contract = m.contract('CryptoErc20');
return { contract };
});
7 changes: 7 additions & 0 deletions blockchain/EVM/ignition/modules/sendToMany.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const { buildModule } = require('@nomicfoundation/hardhat-ignition/modules');


module.exports = buildModule('SendToMany', (m) => {
const contract = m.contract('SendToMany');
return { contract };
});
78 changes: 47 additions & 31 deletions blockchain/EVM/test/sendToMany.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,64 @@
const SendToMany = artifacts.require('SendToMany');
const CryptoErc20 = artifacts.require('CryptoErc20');
const hardhat = require('hardhat');
const assert = require('assert');
const ZERO_ADDR = '0x0000000000000000000000000000000000000000';
contract('SendToMany', (accounts) => {
it('should exist', async() => {
const batcher = await SendToMany.deployed();
assert(batcher);

describe('SendToMany', () => {
let CryptoErc20;
let SendToMany;
let accounts;
let initialEthBalances = [];
let initialErc20Balances = [];

before(async function() {
accounts = (await hardhat.ethers.getSigners()).map(m => m.address);
CryptoErc20 = await hardhat.ethers.deployContract('CryptoErc20');
SendToMany = await hardhat.ethers.deployContract('SendToMany');
for (const account of accounts) {
initialEthBalances.push(await hardhat.ethers.provider.getBalance(account));
initialErc20Balances.push(await CryptoErc20.balanceOf(account));
}
});

it('should send ether', async() => {
const batcher = await SendToMany.deployed();
const receivers = accounts.slice(1);
const amounts = new Array(receivers.length).fill(1e18.toString());
const balanceBefore = await web3.eth.getBalance(accounts[0]);;
console.log('Token balance before', balanceBefore.toString());
const balanceBefore = await hardhat.ethers.provider.getBalance(accounts[0]);
// console.log('ETH balance before', balanceBefore.toString());
const sum = (1e18*receivers.length).toString();
await batcher.sendMany(receivers, amounts, ZERO_ADDR, {value: sum});
const balanceAfter = await web3.eth.getBalance(accounts[0]);;
console.log('ETH balance after', balanceAfter.toString());
for(const receiver of receivers) {
const balance = await web3.eth.getBalance(receiver);
console.log('ETH Balance', receiver, ':', balance.toString());
await SendToMany.sendMany(receivers, amounts, ZERO_ADDR, {value: sum});
const balanceAfter = await hardhat.ethers.provider.getBalance(accounts[0]);
// console.log('ETH balance after', balanceAfter.toString());
assert.ok(balanceBefore - balanceAfter > sum, 'Diff should be >sum because of gas');
for (let i = 0; i < receivers.length; i++) {
const receiver = receivers[i];
const sentAmount = amounts[i];
const initBalance = initialEthBalances[i+1];
const nowBalance = await hardhat.ethers.provider.getBalance(receiver);
const receiptAmt = nowBalance - initBalance;
// console.log('ETH Balance', receiver, ':', nowBalance.toString());
assert.strictEqual(receiptAmt.toString(), sentAmount.toString(), `Balance mismatch: ${i} - ${receiptAmt.toString()} != ${sentAmount.toString()}`);
}
});

it('should have token it can send', async() => {
const token = await CryptoErc20.deployed();
assert(token);
});

it('should send tokens', async() => {
const batcher = await SendToMany.deployed();
const token = await CryptoErc20.deployed();
const receivers = accounts.slice(1);
const amounts = new Array(receivers.length).fill(1e18.toString());
const sum = (1e18*receivers.length).toString();
const balanceBefore = await token.balanceOf(accounts[0]);
console.log('Token balance before', balanceBefore.toString());
await token.approve(batcher.address, sum);
await batcher.sendMany(receivers, amounts, token.address);
const balanceAfter = await token.balanceOf(accounts[0]);
console.log('Token balance after', balanceAfter.toString());
for(const receiver of receivers) {
const balance = await token.balanceOf(receiver);
console.log('Token Balance', receiver, ':', balance.toString());
const balanceBefore = await CryptoErc20.balanceOf(accounts[0]);
// console.log('Token balance before', balanceBefore.toString());
await CryptoErc20.approve(SendToMany.target, sum);
await SendToMany.sendMany(receivers, amounts, CryptoErc20.target);
const balanceAfter = await CryptoErc20.balanceOf(accounts[0]);
// console.log('Token balance after', balanceAfter.toString());
assert.ok(balanceBefore - balanceAfter == sum, 'Diff should be =sum because gas is paid in ETH');
for (let i = 0; i < receivers.length; i++) {
const receiver = receivers[i];
const sentAmount = amounts[i];
const initBalance = initialErc20Balances[i+1];
const nowBalance = await CryptoErc20.balanceOf(receiver);
const receiptAmt = nowBalance - initBalance;
// console.log('Token Balance', receiver, ':', nowBalance.toString());
assert.strictEqual(receiptAmt.toString(), sentAmount.toString(), `Balance mismatch: ${i} - ${receiptAmt.toString()} != ${sentAmount.toString()}`);
}
});

Expand Down
42 changes: 11 additions & 31 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
version: "3"

services:

start:
build:
context: .
Expand All @@ -21,7 +18,6 @@ services:
- lightning
- lightning2
- geth
- ganache
- solana

test_runner:
Expand All @@ -43,7 +39,6 @@ services:
- lightning
- lightning2
- geth
- ganache
- solana

bitcoin:
Expand Down Expand Up @@ -125,7 +120,7 @@ services:
restart: always

geth:
image: 0labs/geth:v1.10.21
image: ethereum/client-go:v1.14.13
volumes:
- ./tests/docker/geth-keystore:/keystore
ports:
Expand All @@ -134,34 +129,19 @@ services:
default:
ipv4_address: 172.28.0.7
command:
geth
--dev
--datadir=/home/kjoseph/nodes/dev/geth
--networkid=1337
--datadir /geth
--networkid 1337
--http
--http.api=web3,eth,debug,personal,net
--http.corsdomain='*'
--http.vhosts='*'
--http.addr=0.0.0.0
--http.port=8545
--keystore=/keystore
--http.api web3,eth,debug,net
--http.corsdomain '*'
--http.vhosts '*'
--http.addr 0.0.0.0
--http.port 8545
--keystore /keystore
--allow-insecure-unlock
--unlock=00a329c0648769a73afac7f9381e08fb43dbea72
--password=/keystore/pw

ganache:
image: trufflesuite/ganache-cli:v6.12.2
ports:
- "10545:8545"
networks:
default:
ipv4_address: 172.28.0.11
command:
-m "dose youth patient boring disagree tuna random tower tornado version violin around"
-b 2
-g 20000000000
-p 8545
-a 20
--unlock 00a329c0648769a73afac7f9381e08fb43dbea72
--password /keystore/pw

rippled:
networks:
Expand Down
3 changes: 2 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module.exports = {
BalanceProgram: require('./bin/balance'),
SendProgram: require('./bin/send'),
CryptoRpc: require('./lib')
CryptoRpc: require('./lib'),
utils: require('./lib/utils')
};
56 changes: 27 additions & 29 deletions lib/erc20/Erc20Rpc.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,44 +12,43 @@ class Erc20RPC extends EthRPC {
);
}

#getWallet(account) {
return this.web3.eth.accounts.wallet.get(account);
}

// this will only work on ERC20 tokens with decimals
async sendToAddress({ address, amount, fromAccount, passphrase, gasPrice, nonce, gas }) {
async sendToAddress({ address, amount, fromAccount, gasPrice, nonce, gas }) {
if (!gasPrice) {
gasPrice = await this.estimateGasPrice();
}
const account = fromAccount || await this.getAccount();
const account = fromAccount || this.getAccount();
const amountStr = Number(amount).toLocaleString('fullwide', { useGrouping: false });
const contractData = this.erc20Contract.methods
.transfer(address, amountStr)
.encodeABI();

if (passphrase) {
this.emitter.emit('unlockedForOne');
const wallet = this.#getWallet(account);
if (!wallet) {
throw new Error('Account not found. Make sure you add it first with addAccount()');
}

let result;

try {
result = await this.web3.eth.personal.sendTransaction(
{
from: account,
gasPrice,
data: contractData,
to: this.tokenContractAddress,
nonce,
gas
},
passphrase
const signed = await wallet.signTransaction({
from: account,
gasPrice,
data: contractData,
to: this.tokenContractAddress,
nonce,
gas
});
const txid = await new Promise((resolve, reject) =>
this.web3.eth.sendSignedTransaction(signed.rawTransaction).on('transactionHash', resolve).on('error', reject)
);

if (passphrase) {
this.emitter.emit('locked');
}
return txid;
} catch (error) {
this.emitter.emit('locked');
throw new Error(error);
}

return result;
}

async getBalance({ address }) {
Expand All @@ -59,12 +58,11 @@ class Erc20RPC extends EthRPC {
.call();
return balance;
} else {
const accounts = await this.web3.eth.getAccounts();
const balances = [];
for (let account of accounts) {
const balance = await this.getBalance({ address: account });
balances.push({ account, balance });
}
const wallets = await this.web3.eth.accounts.wallet;
const balances = await Promise.all(wallets.map(async (wallet) => {
const balance = await this.getBalance({ address: wallet.address });
return { account: wallet.address, balance };
}));
return balances;
}
}
Expand All @@ -73,7 +71,7 @@ class Erc20RPC extends EthRPC {
const decodedEthTx = await super.decodeRawTransaction({ rawTx });
if (decodedEthTx.data) {
try {
const erc20Interface = new ethers.utils.Interface(erc20);
const erc20Interface = new ethers.Interface(erc20);
decodedEthTx.decodedData = await erc20Interface.parseTransaction({ data: decodedEthTx.data });
} catch (err) {
decodedEthTx.decodedData = undefined;
Expand Down
Loading