add hardhat scripts for common tasks (#37)

* feat: add hh task for deploy replica

* feat: add a reportTxOutcome utils function

* ref: add convenience functions to optics. allow signer passing

* feat: scripts for submit-update and submit-double-update

* feat: add scripts for processing messages

* feat: change errors to use console.error

* refactor: add utils.validateUpdate to hh tasks

* fix: change to the correct new call sig for double update

* fix: update hh task to conform to new proveAndProcess api

* doc: slightly improve hh task docstrings for processing

* feature: add a hh task to enqueue messages
buddies-main-deployment
James Prestwich 4 years ago committed by GitHub
parent 319ebd4369
commit d4e36f6373
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 48
      solidity/lib/index.js
  2. 21720
      solidity/package-lock.json
  3. 67
      solidity/scripts/deploy.js
  4. 2
      solidity/scripts/index.js
  5. 151
      solidity/scripts/process.js
  6. 59
      solidity/scripts/update.js
  7. 40
      solidity/scripts/utils.js

@ -10,18 +10,46 @@ extendEnvironment((hre) => {
constructor(address, abi, providerOrSigner) {
super(address, abi, providerOrSigner);
}
async submitDoubleUpdate(left, right) {
if (left.oldRoot !== right.oldRoot) {
throw new Error('Old roots do not match');
}
return await this.doubleUpdate(
right.oldRoot,
[left.newRoot, right.newRoot],
left.signature,
right.signature,
);
}
}
class Home extends Common {
constructor(address, providerOrSigner) {
super(address, HomeAbi, providerOrSigner);
}
async submitSignedUpdate(update) {
return await this.update(
update.oldRoot,
update.newRoot,
update.signature,
);
}
}
class Replica extends Common {
constructor(address, providerOrSigner) {
super(address, ReplicaAbi, providerOrSigner);
}
async submitSignedUpdate(update) {
return await this.update(
update.oldRoot,
update.newRoot,
update.signature,
);
}
}
class Updater {
@ -62,25 +90,27 @@ extendEnvironment((hre) => {
}
}
const getHomeFactory = async () => ethers.getContractFactory('Home');
const getReplicaFactory = async () =>
ethers.getContractFactory('ProcessingReplica');
const getHomeFactory = async (...args) =>
ethers.getContractFactory('Home', ...args);
const getReplicaFactory = async (...args) =>
ethers.getContractFactory('ProcessingReplica', ...args);
hre.optics = {
Common,
Home,
Replica,
Updater,
getHomeFactory,
getReplicaFactory,
deployHome: async (...args) => {
let contract = await (await getHomeFactory()).deploy(...args);
deployHome: async (signer, ...args) => {
let contract = await (await getHomeFactory(signer)).deploy(...args);
await contract.deployed();
return new Home(contract.address, contract.signer);
return new Home(contract.address, signer);
},
deployReplica: async (...args) => {
let contract = await (await getReplicaFactory()).deploy(...args);
deployReplica: async (signer, ...args) => {
let contract = await (await getReplicaFactory(signer)).deploy(...args);
await contract.deployed();
return new Replica(contract.address, contract.signer);
return new Replica(contract.address, signer);
},
};
});

File diff suppressed because it is too large Load Diff

@ -1,8 +1,63 @@
// const { hre } = require('hardhat');
const { types } = require('hardhat/config');
const { types, task } = require('hardhat/config');
const utils = require('./utils.js');
task('deploy-home')
task('deploy-home', 'Deploy a home.')
.addParam('slip44', 'The origin chain SLIP44 ID', undefined, types.int)
.addParam('updater', 'The origin chain updater', undefined, types.string)
.addParam('currentRoot', 'The current root')
.setAction(async (args) => {});
.addParam(
'sortition',
'The updater identity handler',
undefined,
types.string,
)
.setAction(async (args) => {
let address = ethers.utils.getAddress(args.sortition);
let signer = ethers.getSigner();
let home = await optics.deployHome(signer, args.slip44, address);
console.log(home.address);
});
task('deploy-replica', 'Deploy a replica.')
.addParam('origin', 'The origin chain SLIP44 ID', undefined, types.int)
.addParam(
'destination',
'The destination chain SLIP44 ID',
undefined,
types.int,
)
.addParam('updater', 'The address of the updater', undefined, types.string)
.addOptionalParam(
'wait',
'The optimistic wait period in seconds',
60 * 60 * 2, // 2 hours
types.int,
)
.addOptionalParam(
'current',
'The current root to init with',
`0x${'00'.repeat(32)}`,
types.string,
)
.addOptionalParam(
'lastProcessed',
'The last processed message sequence',
0,
types.int,
)
.setAction(async (args) => {
let updater = ethers.utils.getAddress(args.updater);
if (!ethers.utils.isHexString(args.current, 32)) {
throw new Error('current must be a 32-byte 0x prefixed hex string');
}
let signer = ethers.getSigner();
await optics.deployReplica(
signer,
args.origin,
args.destination,
updater,
args.wait,
args.current,
args.lastProcessed,
);
});

@ -1 +1,3 @@
require('./deploy.js');
require('./process.js');
require('./deploy.js');

@ -0,0 +1,151 @@
const ethers = require('ethers');
const { types, task } = require('hardhat/config');
const utils = require('./utils.js');
task('prove', 'Prove a message inclusion to a replica')
.addParam(
'address',
'The address of the replica contract.',
undefined,
types.string,
)
.addParam('message', 'The message to prove.', undefined, types.string)
.addParam(
'proof',
'The 32 * 32 byte proof as a single hex string',
undefined,
types.string,
)
.addParam(
'index',
'The index of the message in the merkle tree',
undefined,
types.int,
)
.setAction(async (args) => {
let address = ethers.utils.getAddress(args.address);
let { rawProof, message, index } = args;
let proof = utils.parseProof(rawProof);
if (!ethers.utils.isHexString(message)) {
throw new Error('newRoot must be a 0x prefixed hex string');
}
let signer = await ethers.getSigner();
let replica = new optics.Replica(address, signer);
// preflight
if (
await replica.callStatic.prove(
ethers.utils.keccak256(message),
proof,
index,
)
) {
let tx = await replica.prove(
ethers.utils.keccak256(message),
proof,
index,
);
await utils.reportTxOutcome(tx);
} else {
console.log('Error: Replica will reject proof');
}
});
task('process', 'Process a message that has been proven to a replica')
.addParam(
'address',
'The address of the replica contract.',
undefined,
types.string,
)
.addParam('message', 'The message to prove.', undefined, types.string)
.setAction(async (args) => {
let address = ethers.utils.getAddress(args.address);
let { message } = args;
if (!ethers.utils.isHexString(message)) {
throw new Error('newRoot must be a 0x prefixed hex string');
}
let signer = await ethers.getSigner();
let replica = new optics.Replica(address, signer);
try {
await replica.callStatic.process(message);
let tx = await replica.process(message);
await utils.reportTxOutcome(tx);
} catch (e) {
console.error(
`Error: Replica will reject process with message\n\t${e.message}`,
);
}
});
task('prove-and-process', 'Prove and process a message')
.addParam(
'address',
'The address of the replica contract.',
undefined,
types.string,
)
.addParam('message', 'The message to prove.', undefined, types.string)
.addParam(
'proof',
'The 32 * 32 byte proof as a single hex string',
undefined,
types.string,
)
.addParam(
'index',
'The index of the message in the merkle tree',
undefined,
types.int,
)
.setAction(async (args) => {
let address = ethers.utils.getAddress(args.address);
let { rawProof, message, index } = args;
let proof = utils.parseProof(rawProof);
if (!ethers.utils.isHexString(message)) {
throw new Error('message must be a 0x prefixed hex string');
}
let signer = await ethers.getSigner();
let replica = new optics.Replica(address, signer);
try {
// preflight and make sure it works. This throws on revert
await replica.callStatic.proveAndProcess(message, proof, index);
await replica.proveAndProcess(message, proof, index);
} catch (e) {
console.error(
`Error: Replica will reject proveAndProcess with message\n\t${e.message}`,
);
}
});
task('enqueue', 'Enqueue a message on the Home chain')
.addParam(
'address',
'The address of the replica contract.',
undefined,
types.string,
)
.addParam('destination', 'The destination chain.', undefined, types.int)
.addParam('recipient', 'The message recipient.', undefined, types.string)
.addParam('body', 'The message body.', undefined, types.string)
.setAction(async (args) => {
let address = ethers.utils.getAddress(args.address);
let { destination, recipient, body } = args;
ethers.utils.isHexString(recipient, 32);
if (!ethers.utils.isHexString(message)) {
throw new Error('body must be a 0x prefixed hex string');
}
let home = new optics.Home(address, signer);
let tx = await home.enqueue(destination, recipient, body);
await utils.reportTxOutcome(tx);
});

@ -0,0 +1,59 @@
const { types, task } = require('hardhat/config');
const utils = require('./utils.js');
task('submit-update', 'Submit an update to a home or replica contract.')
.addParam(
'address',
'The address of the contract to update.',
undefined,
types.string,
)
.addParam('oldRoot', 'The old root', undefined, types.string)
.addParam('newRoot', 'The new root', undefined, types.string)
.addParam('signature', 'The updater signature', undefined, types.string)
.setAction(async (args) => {
let address = ethers.utils.getAddress(args.address);
let { newRoot, oldRoot, signature } = args;
let update = await utils.validateUpdate(newRoot, oldRoot, signature);
let signer = await ethers.getSigner();
// we should be able to use home for either. Consider moving this to common?
let contract = new optics.Home(address, signer);
let tx = await contract.submitSignedUpdate({ oldRoot, newRoot, signature });
await utils.reportTxOutcome(tx);
});
task('submit-double-update', 'Submit a double update to a home or replica.')
.addParam(
'address',
'The address of the contract to update.',
undefined,
types.string,
)
.addParam('oldRoot1', 'The old root', undefined, types.string)
.addParam('newRoot1', 'The new root', undefined, types.string)
.addParam('signature1', 'The updater signature', undefined, types.string)
.addParam('oldRoot2', 'The old root', undefined, types.string)
.addParam('newRoot2', 'The new root', undefined, types.string)
.addParam('signature2', 'The updater signature', undefined, types.string)
.setAction(async (args) => {
let {
oldRoot1,
newRoot1,
signature1,
oldRoot2,
newRoot2,
signature2,
} = args;
let address = ethers.utils.getAddress(args.address);
let update1 = await utils.validateUpdate(newRoot1, oldRoot1, signature1);
let update2 = await utils.validateUpdate(newRoot2, oldRoot2, signature2);
let signer = await ethers.getSigner();
let contract = new optics.Common(address, signer);
let tx = await contract.submitDoubleUpdate(update1, update2);
await utils.reportTxOutcome(tx);
});

@ -0,0 +1,40 @@
async function reportTxOutcome(tx, confs) {
confs = confs ? confs : 1;
console.log(`\tSent tx with ID ${tx.hash} to ${tx.to}`);
console.log(`\tWaiting for ${confs} confs`);
return await tx.wait(confs);
}
// turn a tightly packed proof into an array
async function parseProof(rawProof) {
return ethers.utils.defaultAbiCoder.decode(['bytes32[32]'], rawProof);
}
async function validateUpdate(oldRoot, newRoot, signature, slip44) {
if (!ethers.utils.isHexString(oldRoot, 32)) {
throw new Error('oldRoot must be a 32-byte 0x prefixed hex string');
}
if (!ethers.utils.isHexString(newRoot, 32)) {
throw new Error('newRoot must be a 32-byte 0x prefixed hex string');
}
if (!ethers.utils.isHexString(signature, 65)) {
throw new Error('signature must be a 65-byte 0x prefixed hex string');
}
if (slip44) {
// TODO: validate the signature
}
return {
oldRoot,
newRoot,
signature,
};
}
module.exports = {
reportTxOutcome,
parseProof,
validateUpdate,
};
Loading…
Cancel
Save