Add interchain accounts package (#1057)

* Push initial ICA package

* Update README

* Remove generated files

* Make _salt pure

* Rename package to interchain accounts
nambrot/verification-fies
Yorke Rhodes 2 years ago committed by GitHub
parent 24e2c81236
commit 53593ef6f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      typescript/ica/.gitignore
  2. 6
      typescript/ica/README.md
  3. 85
      typescript/ica/contracts/InterchainAccountRouter.sol
  4. 34
      typescript/ica/contracts/OwnableMulticall.sol
  5. 12
      typescript/ica/contracts/TestRecipient.sol
  6. 26
      typescript/ica/hardhat.config.ts
  7. 66
      typescript/ica/package.json
  8. 16
      typescript/ica/src/contracts.ts
  9. 44
      typescript/ica/src/deploy.ts
  10. 73
      typescript/ica/test/accounts.test.ts
  11. 11
      typescript/ica/tsconfig.json
  12. 48
      typescript/interchain/scripts/deploy.ts
  13. 120
      yarn.lock

@ -0,0 +1,9 @@
artifacts/
cache/
coverage/
coverage.json
dist/
node_modules/
types/
*.swp
.yarn/install-state.gz

@ -0,0 +1,6 @@
This package provides smart contracts with "sovereignty" on remote Abacus chains via operating interchain accounts.
An interchain account is a smart contract that is deployed on a remote chain and is controlled exclusively by the deploying local account.
Interchain accounts provide developers with a [transparent multicall API](./contracts/OwnableMulticall.sol) to remote smart contracts.
This avoids the need to deploy application specific smart contracts on remote chains while simultaneously enabling crosschain composability.
See [IBC Interchain Accounts](https://github.com/cosmos/ibc/blob/main/spec/app/ics-027-interchain-accounts/README.md) for the Cosmos ecosystem equivalent.

@ -0,0 +1,85 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;
import {OwnableMulticall, Call} from "./OwnableMulticall.sol";
// ============ External Imports ============
import {Router} from "@abacus-network/app/contracts/Router.sol";
import {TypeCasts} from "@abacus-network/core/contracts/libs/TypeCasts.sol";
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
/*
* @title The Hello World App
* @dev You can use this simple app as a starting point for your own application.
*/
contract InterchainAccountRouter is Router {
bytes constant bytecode = type(OwnableMulticall).creationCode;
bytes32 constant bytecodeHash = bytes32(keccak256(bytecode));
constructor(
address _abacusConnectionManager,
address _interchainGasPaymaster
) {
// Transfer ownership of the contract to deployer
_transferOwnership(msg.sender);
// Set the addresses for the ACM and IGP
// Alternatively, this could be done later in an initialize method
_setAbacusConnectionManager(_abacusConnectionManager);
_setInterchainGasPaymaster(_interchainGasPaymaster);
}
function dispatch(uint32 _destinationDomain, Call[] calldata calls)
external
{
_dispatch(_destinationDomain, abi.encode(msg.sender, calls));
}
function getInterchainAccount(uint32 _origin, address _sender)
public
view
returns (address)
{
return _getInterchainAccount(_salt(_origin, _sender));
}
function getDeployedInterchainAccount(uint32 _origin, address _sender)
public
returns (OwnableMulticall)
{
bytes32 salt = _salt(_origin, _sender);
address interchainAccount = _getInterchainAccount(salt);
if (!Address.isContract(interchainAccount)) {
interchainAccount = Create2.deploy(0, salt, bytecode);
}
return OwnableMulticall(interchainAccount);
}
function _salt(uint32 _origin, address _sender)
internal
pure
returns (bytes32)
{
return bytes32(abi.encodePacked(_origin, _sender));
}
function _getInterchainAccount(bytes32 salt)
internal
view
returns (address)
{
return Create2.computeAddress(salt, bytecodeHash);
}
function _handle(
uint32 _origin,
bytes32, // router sender
bytes memory _message
) internal override {
(address sender, Call[] memory calls) = abi.decode(
_message,
(address, Call[])
);
getDeployedInterchainAccount(_origin, sender).proxyCalls(calls);
}
}

@ -0,0 +1,34 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.13;
// ============ External Imports ============
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
struct Call {
address to;
bytes data;
}
/*
* @title OwnableMulticall
* @dev Allows only only address to execute calls to other contracts
*/
contract OwnableMulticall is OwnableUpgradeable {
constructor() {
_transferOwnership(msg.sender);
}
function proxyCalls(Call[] calldata calls) external onlyOwner {
for (uint256 i = 0; i < calls.length; i += 1) {
(bool success, bytes memory returnData) = calls[i].to.call(
calls[i].data
);
if (!success) {
assembly {
revert(add(returnData, 32), returnData)
}
}
}
}
}

@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.8.0;
contract TestRecipient {
address public lastSender;
bytes public lastData;
function foo(bytes calldata data) external {
lastSender = msg.sender;
lastData = data;
}
}

@ -0,0 +1,26 @@
import '@nomiclabs/hardhat-ethers';
import '@nomiclabs/hardhat-waffle';
import '@typechain/hardhat';
import 'hardhat-gas-reporter';
import 'solidity-coverage';
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: {
compilers: [
{
version: '0.8.16',
},
],
},
gasReporter: {
currency: 'USD',
},
typechain: {
outDir: './types',
target: 'ethers-v5',
alwaysGenerateOverloads: false, // should overloads with full signatures like deposit(uint256) be generated always, even if there are no overloads?
},
};

@ -0,0 +1,66 @@
{
"name": "@abacus-network/interchain-accounts",
"description": "A router middleware for interchain accounts",
"version": "0.1.0",
"dependencies": {
"@abacus-network/app": "^0.4.1",
"@abacus-network/sdk": "^0.4.1",
"@abacus-network/utils": "^0.4.1",
"@openzeppelin/contracts-upgradeable": "^4.6.0",
"ethers": "^5.6.8"
},
"devDependencies": {
"@nomicfoundation/hardhat-chai-matchers": "^1.0.3",
"@nomicfoundation/hardhat-toolbox": "^1.0.2",
"@nomiclabs/hardhat-ethers": "^2.0.5",
"@nomiclabs/hardhat-waffle": "^2.0.2",
"@trivago/prettier-plugin-sort-imports": "^3.2.0",
"@typechain/ethers-v5": "10.0.0",
"@typechain/hardhat": "^6.0.0",
"@types/mocha": "^9.1.0",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"chai": "^4.3.0",
"eslint": "^8.16.0",
"eslint-config-prettier": "^8.5.0",
"ethereum-waffle": "^3.4.4",
"hardhat": "^2.8.4",
"hardhat-gas-reporter": "^1.0.7",
"prettier": "^2.4.1",
"prettier-plugin-solidity": "^1.0.0-beta.5",
"solhint": "^3.3.2",
"solhint-plugin-prettier": "^0.0.5",
"solidity-coverage": "^0.7.14",
"ts-node": "^10.8.0",
"typechain": "8.0.0",
"typescript": "^4.7.2"
},
"files": [
"/dist",
"/contracts"
],
"homepage": "https://www.useabacus.network",
"keywords": [
"Abacus",
"Interchain Accounts",
"Solidity",
"Typescript"
],
"license": "Apache-2.0",
"main": "dist/src/index.js",
"packageManager": "yarn@3.2.0",
"repository": {
"type": "git",
"url": "https://github.com/abacus-network/abacus-app-template"
},
"scripts": {
"build": "hardhat compile && tsc",
"clean": "hardhat clean && rm -rf dist cache src/types",
"coverage": "hardhat coverage",
"lint": "eslint . --ext .ts",
"prettier": "prettier --write ./contracts ./src",
"test": "hardhat test ./test/*.test.ts",
"sync": "ts-node scripts/sync-with-template-repo.ts"
},
"types": "dist/src/index.d.ts"
}

@ -0,0 +1,16 @@
import { RouterContracts, RouterFactories } from '@abacus-network/sdk';
import {
InterchainAccountRouter,
InterchainAccountRouter__factory,
} from '../types';
export type InterchainAccountFactories =
RouterFactories<InterchainAccountRouter>;
export const InterchainAccountFactories: InterchainAccountFactories = {
router: new InterchainAccountRouter__factory(),
};
export type InterchainAccountContracts =
RouterContracts<InterchainAccountRouter>;

@ -0,0 +1,44 @@
import {
AbacusCore,
AbacusRouterDeployer,
ChainMap,
ChainName,
MultiProvider,
RouterConfig,
} from '@abacus-network/sdk';
import {
InterchainAccountContracts,
InterchainAccountFactories,
} from './contracts';
export type InterchainAccountConfig = RouterConfig;
export class InterchainAccountDeployer<
Chain extends ChainName,
> extends AbacusRouterDeployer<
Chain,
InterchainAccountConfig,
InterchainAccountContracts,
InterchainAccountFactories
> {
constructor(
multiProvider: MultiProvider<Chain>,
configMap: ChainMap<Chain, InterchainAccountConfig>,
protected core: AbacusCore<Chain>,
) {
super(multiProvider, configMap, InterchainAccountFactories, {});
}
// Custom contract deployment logic can go here
// If no custom logic is needed, call deployContract for the router
async deployContracts(chain: Chain, config: InterchainAccountConfig) {
const router = await this.deployContract(chain, 'router', [
config.abacusConnectionManager,
config.interchainGasPaymaster,
]);
return {
router,
};
}
}

@ -0,0 +1,73 @@
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
import { ethers } from 'hardhat';
import {
ChainMap,
ChainNameToDomainId,
MultiProvider,
RouterConfig,
TestChainNames,
TestCoreApp,
TestCoreDeployer,
getChainToOwnerMap,
getTestMultiProvider,
testChainConnectionConfigs,
} from '@abacus-network/sdk';
import { InterchainAccountDeployer } from '../src/deploy';
import { InterchainAccountRouter, TestRecipient__factory } from '../types';
describe('InterchainAccountRouter', async () => {
const localChain = 'test1';
const remoteChain = 'test2';
const localDomain = ChainNameToDomainId[localChain];
const remoteDomain = ChainNameToDomainId[remoteChain];
let signer: SignerWithAddress;
let local: InterchainAccountRouter;
let remote: InterchainAccountRouter;
let multiProvider: MultiProvider<TestChainNames>;
let coreApp: TestCoreApp;
let config: ChainMap<TestChainNames, RouterConfig>;
before(async () => {
[signer] = await ethers.getSigners();
multiProvider = getTestMultiProvider(signer);
const coreDeployer = new TestCoreDeployer(multiProvider);
const coreContractsMaps = await coreDeployer.deploy();
coreApp = new TestCoreApp(coreContractsMaps, multiProvider);
config = coreApp.extendWithConnectionClientConfig(
getChainToOwnerMap(testChainConnectionConfigs, signer.address),
);
});
beforeEach(async () => {
const InterchainAccount = new InterchainAccountDeployer(
multiProvider,
config,
coreApp,
);
const contracts = await InterchainAccount.deploy();
local = contracts[localChain].router;
remote = contracts[remoteChain].router;
});
it('forwards calls from interchain account', async () => {
const recipientF = new TestRecipient__factory(signer);
const recipient = await recipientF.deploy();
const fooData = '0x12';
const data = recipient.interface.encodeFunctionData('foo', [fooData]);
const icaAddress = await remote.getInterchainAccount(
localDomain,
signer.address,
);
await local.dispatch(remoteDomain, [{ to: recipient.address, data }]);
await coreApp.processMessages();
expect(await recipient.lastData()).to.eql(fooData);
expect(await recipient.lastSender()).to.eql(icaAddress);
});
});

@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist/",
"rootDir": "./",
"noImplicitAny": false,
},
"exclude": ["./node_modules/", "./dist/", "./src/types/hardhat.d.ts"],
"include": ["./src/", "./test", "./scripts"],
"files": ["hardhat.config.ts"]
}

@ -0,0 +1,48 @@
import { Wallet } from 'ethers';
import {
AbacusCore,
MultiProvider,
chainConnectionConfigs,
getChainToOwnerMap,
objMap,
serializeContracts,
} from '@abacus-network/sdk';
import { InterchainAccountDeployer } from '../src/deploy';
export const prodConfigs = {
alfajores: chainConnectionConfigs.alfajores,
fuji: chainConnectionConfigs.fuji,
bsctestnet: chainConnectionConfigs.bsctestnet,
};
/* eslint-disable no-console */
async function main() {
console.info('Getting signer');
const signer = new Wallet('pkey');
console.info('Preparing utilities');
const chainProviders = objMap(prodConfigs, (_, config) => ({
provider: config.provider,
confirmations: config.confirmations,
overrides: config.overrides,
signer: new Wallet('pkey', config.provider),
}));
const multiProvider = new MultiProvider(chainProviders);
const core = AbacusCore.fromEnvironment('testnet2', multiProvider);
const config = core.extendWithConnectionClientConfig(
getChainToOwnerMap(prodConfigs, signer.address),
);
const deployer = new InterchainAccountDeployer(multiProvider, config, core);
const chainToContracts = await deployer.deploy();
const addresses = serializeContracts(chainToContracts);
console.info('===Contract Addresses===');
console.info(JSON.stringify(addresses));
}
main()
.then(() => console.info('Deploy complete'))
.catch(console.error);

@ -5,7 +5,7 @@ __metadata:
version: 6
cacheKey: 8
"@abacus-network/app@0.4.1, @abacus-network/app@workspace:solidity/app":
"@abacus-network/app@0.4.1, @abacus-network/app@^0.4.1, @abacus-network/app@workspace:solidity/app":
version: 0.0.0-use.local
resolution: "@abacus-network/app@workspace:solidity/app"
dependencies:
@ -143,6 +143,42 @@ __metadata:
languageName: unknown
linkType: soft
"@abacus-network/interchain-accounts@workspace:typescript/ica":
version: 0.0.0-use.local
resolution: "@abacus-network/interchain-accounts@workspace:typescript/ica"
dependencies:
"@abacus-network/app": ^0.4.1
"@abacus-network/sdk": ^0.4.1
"@abacus-network/utils": ^0.4.1
"@nomicfoundation/hardhat-chai-matchers": ^1.0.3
"@nomicfoundation/hardhat-toolbox": ^1.0.2
"@nomiclabs/hardhat-ethers": ^2.0.5
"@nomiclabs/hardhat-waffle": ^2.0.2
"@openzeppelin/contracts-upgradeable": ^4.6.0
"@trivago/prettier-plugin-sort-imports": ^3.2.0
"@typechain/ethers-v5": 10.0.0
"@typechain/hardhat": ^6.0.0
"@types/mocha": ^9.1.0
"@typescript-eslint/eslint-plugin": ^5.27.0
"@typescript-eslint/parser": ^5.27.0
chai: ^4.3.0
eslint: ^8.16.0
eslint-config-prettier: ^8.5.0
ethereum-waffle: ^3.4.4
ethers: ^5.6.8
hardhat: ^2.8.4
hardhat-gas-reporter: ^1.0.7
prettier: ^2.4.1
prettier-plugin-solidity: ^1.0.0-beta.5
solhint: ^3.3.2
solhint-plugin-prettier: ^0.0.5
solidity-coverage: ^0.7.14
ts-node: ^10.8.0
typechain: 8.0.0
typescript: ^4.7.2
languageName: unknown
linkType: soft
"@abacus-network/monorepo@workspace:.":
version: 0.0.0-use.local
resolution: "@abacus-network/monorepo@workspace:."
@ -4088,6 +4124,52 @@ __metadata:
languageName: node
linkType: hard
"@nomicfoundation/hardhat-chai-matchers@npm:^1.0.3":
version: 1.0.3
resolution: "@nomicfoundation/hardhat-chai-matchers@npm:1.0.3"
dependencies:
"@ethersproject/abi": ^5.1.2
"@types/chai-as-promised": ^7.1.3
chai-as-promised: ^7.1.1
chalk: ^2.4.2
deep-eql: ^4.0.1
ordinal: ^1.0.3
peerDependencies:
"@nomiclabs/hardhat-ethers": ^2.0.0
chai: ^4.2.0
ethers: ^5.0.0
hardhat: ^2.9.4
checksum: bee445e7ed53d6ee00b85c5c76ca79b7cd1792487481803a29357803b09f92b626b6429a50522a8398c8010ad4fd1f0106c79416dc8c01aea644e7360ea9c203
languageName: node
linkType: hard
"@nomicfoundation/hardhat-toolbox@npm:^1.0.2":
version: 1.0.2
resolution: "@nomicfoundation/hardhat-toolbox@npm:1.0.2"
peerDependencies:
"@ethersproject/abi": ^5.4.7
"@ethersproject/providers": ^5.4.7
"@nomicfoundation/hardhat-chai-matchers": ^1.0.0
"@nomicfoundation/hardhat-network-helpers": ^1.0.0
"@nomiclabs/hardhat-ethers": ^2.0.0
"@nomiclabs/hardhat-etherscan": ^3.0.0
"@typechain/ethers-v5": ^10.1.0
"@typechain/hardhat": ^6.1.2
"@types/chai": ^4.2.0
"@types/mocha": ^9.1.0
"@types/node": ">=12.0.0"
chai: ^4.2.0
ethers: ^5.4.7
hardhat: ^2.9.9
hardhat-gas-reporter: ^1.0.8
solidity-coverage: ^0.7.21
ts-node: ">=8.0.0"
typechain: ^8.1.0
typescript: ">=4.5.0"
checksum: d13b3e9f08d8be5f72a25872b6a9d3609fdb34f46b47dbf10963a6fb5003ac1cd0d0c107ef6d91807b864a8766d9e5092518f21db3116902ee5a8ae73fdcaaa2
languageName: node
linkType: hard
"@nomiclabs/hardhat-ethers@npm:^2.0.5":
version: 2.0.6
resolution: "@nomiclabs/hardhat-ethers@npm:2.0.6"
@ -4542,6 +4624,15 @@ __metadata:
languageName: node
linkType: hard
"@types/chai-as-promised@npm:^7.1.3":
version: 7.1.5
resolution: "@types/chai-as-promised@npm:7.1.5"
dependencies:
"@types/chai": "*"
checksum: 7c1345c6e32513d52d8e562ec173c23161648d6b792046525f18803a9932d7b3ad3dca8f0181e3c529ec42b106099f174e34edeb184d61dc93e32c98b5132fd4
languageName: node
linkType: hard
"@types/chai@npm:*, @types/chai@npm:^4.2.21":
version: 4.3.1
resolution: "@types/chai@npm:4.3.1"
@ -6855,6 +6946,17 @@ __metadata:
languageName: node
linkType: hard
"chai-as-promised@npm:^7.1.1":
version: 7.1.1
resolution: "chai-as-promised@npm:7.1.1"
dependencies:
check-error: ^1.0.2
peerDependencies:
chai: ">= 2.1.2 < 5"
checksum: 7262868a5b51a12af4e432838ddf97a893109266a505808e1868ba63a12de7ee1166e9d43b5c501a190c377c1b11ecb9ff8e093c89f097ad96c397e8ec0f8d6a
languageName: node
linkType: hard
"chai@npm:^4.3.0, chai@npm:^4.3.4, chai@npm:^4.3.6":
version: 4.3.6
resolution: "chai@npm:4.3.6"
@ -7704,6 +7806,15 @@ __metadata:
languageName: node
linkType: hard
"deep-eql@npm:^4.0.1":
version: 4.1.0
resolution: "deep-eql@npm:4.1.0"
dependencies:
type-detect: ^4.0.0
checksum: 2fccd527df9a70a92a1dfa8c771d139753625938e137b09fc946af8577d22360ef28d3c74f0e9c5aaa399bab20542d0899da1529c71db76f280a30147cd2a110
languageName: node
linkType: hard
"deep-equal@npm:~1.1.1":
version: 1.1.1
resolution: "deep-equal@npm:1.1.1"
@ -13612,6 +13723,13 @@ __metadata:
languageName: node
linkType: hard
"ordinal@npm:^1.0.3":
version: 1.0.3
resolution: "ordinal@npm:1.0.3"
checksum: 6761c5b7606b6c4b0c22b4097dab4fe7ffcddacc49238eedf9c0ced877f5d4e4ad3f4fd43fefa1cc3f167cc54c7149267441b2ae85b81ccf13f45cf4b7947164
languageName: node
linkType: hard
"os-homedir@npm:^1.0.0":
version: 1.0.2
resolution: "os-homedir@npm:1.0.2"

Loading…
Cancel
Save