Эх сурвалжийг харах

Feature ink metadata for substrate (#989)

Fix metadata related issues by using ink and scale-info crates.

Signed-off-by: Raymond Yeh <extraymond@gmail.com>
Co-authored-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
extraymond 3 жил өмнө
parent
commit
0344a87cf8

+ 1 - 0
.gitignore

@@ -3,3 +3,4 @@ Cargo.lock
 /target
 **/*.rs.bk
 bundle.ll
+

+ 6 - 1
Cargo.toml

@@ -31,7 +31,7 @@ serde_derive = { version = "1.0" }
 inkwell = { version = "^0.1.0-beta.4", features = ["target-webassembly", "target-bpf", "no-libffi-linking", "llvm13-0"], optional = true }
 blake2-rfc = "0.2.18"
 handlebars = "4.2"
-contract-metadata = "0.3.0"
+contract-metadata = "1.5.0"
 semver = { version = "^1.0.3", features = ["serde"] }
 tempfile = "3.3"
 libc = { version = "0.2", optional = true }
@@ -55,6 +55,11 @@ anchor-syn = { version = "0.25", features = ["idl"] }
 convert_case = "0.6"
 parse-display = "0.6.0"
 
+ink_metadata = { git = "https://github.com/paritytech/ink.git", rev = "c19181e44c3300c6bab7b098710ef55e3493eb52" }
+ink_primitives = { git = "https://github.com/paritytech/ink.git", rev = "c19181e44c3300c6bab7b098710ef55e3493eb52" }
+scale-info = "2.3"
+
+
 [dev-dependencies]
 num-derive = "0.3"
 parity-scale-codec = "3.1"

+ 1 - 1
integration/substrate/.gitignore

@@ -2,4 +2,4 @@
 *.contract
 *.wasm
 node_modules
-package-lock.json
+package-lock.json

+ 1 - 2
integration/substrate/UniswapV2ERC20.spec.ts

@@ -9,8 +9,7 @@ const TOTAL_SUPPLY = BigInt(10000e18);
 const TEST_AMOUNT = BigInt(10e18);
 const MAX_UINT256 = BigInt(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
 
-// REGRESSION metadata #666
-describe.skip('Deploy UniswapV2ERC20 contract and test', () => {
+describe('Deploy UniswapV2ERC20 contract and test', () => {
     let conn: ApiPromise;
     let token: ContractPromise;
     let alice: KeyringPair;

+ 3 - 4
integration/substrate/UniswapV2Factory.spec.ts

@@ -10,8 +10,7 @@ const TEST_ADDRESSES: [string, string] = [
   '5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyV1W6M'
 ]
 
-//  REGRESSION metadata #666
-describe.skip('UniswapV2Factory', () => {
+describe('UniswapV2Factory', () => {
   let conn: ApiPromise;
   let factory: ContractPromise;
   let alice: KeyringPair;
@@ -24,13 +23,13 @@ describe.skip('UniswapV2Factory', () => {
     alice = aliceKeypair();
     dave = daveKeypair();
 
-    let deploy_contract = await deploy(conn, alice, 'UniswapV2Factory.contract', BigInt(0), alice.address);
+    let deploy_contract = await deploy(conn, alice, 'UniswapV2Factory.contract', 10000000000000000n, alice.address);
 
     factory = new ContractPromise(conn, deploy_contract.abi, deploy_contract.address);
 
     // Upload UniswapV2Pair contract code so that it can instantiated from the factory
     // there probably is a better way of doing this than deploying a contract. Patches welcome.
-    let pair = await deploy(conn, alice, 'UniswapV2Pair.contract', BigInt(0));
+    let pair = await deploy(conn, alice, 'UniswapV2Pair.contract', 0n);
 
     pairAbi = pair.abi;
   });

+ 14 - 21
integration/substrate/UniswapV2Pair.spec.ts

@@ -8,8 +8,7 @@ import type { Codec } from '@polkadot/types/types';
 const MINIMUM_LIQUIDITY = BigInt(1000);
 const TOTAL_SUPPLY = BigInt(10000e18);
 
-// REGRESSION metadata #666
-describe.skip('UniswapV2Pair', () => {
+describe('UniswapV2Pair', () => {
     let conn: ApiPromise;
     let factory: ContractPromise;
     let pair: ContractPromise;
@@ -26,19 +25,19 @@ describe.skip('UniswapV2Pair', () => {
 
         // Upload UniswapV2Pair contract code so that it can instantiated from the factory
         // there probably is a better way of doing this than deploying a contract. Patches welcome.
-        const pairTmp = await deploy(conn, alice, 'UniswapV2Pair.contract', BigInt(0));
+        const pairTmp = await deploy(conn, alice, 'UniswapV2Pair.contract', 0n);
 
         const pairAbi = pairTmp.abi;
 
-        let deploy_contract = await deploy(conn, alice, 'UniswapV2Factory.contract', BigInt(0), alice.address);
+        let deploy_contract = await deploy(conn, alice, 'UniswapV2Factory.contract', 10000000000000000n, alice.address);
 
         factory = new ContractPromise(conn, deploy_contract.abi, deploy_contract.address);
 
-        const tokenA_contract = await deploy(conn, alice, 'ERC20.contract', TOTAL_SUPPLY);
+        const tokenA_contract = await deploy(conn, alice, 'ERC20.contract', 0n, TOTAL_SUPPLY);
 
         const tokenA = new ContractPromise(conn, tokenA_contract.abi, tokenA_contract.address);
 
-        const tokenB_contract = await deploy(conn, alice, 'ERC20.contract', TOTAL_SUPPLY);
+        const tokenB_contract = await deploy(conn, alice, 'ERC20.contract', 0n, TOTAL_SUPPLY);
 
         const tokenB = new ContractPromise(conn, tokenB_contract.abi, tokenB_contract.address);
 
@@ -81,7 +80,7 @@ describe.skip('UniswapV2Pair', () => {
 
         const { output: totalSupply } = await pair.query.totalSupply(alice.address, {});
         expect(totalSupply?.eq(expectedLiquidity)).toBeTruthy();
-        const { output: bal } = await pair.query.balanceOf(alice.address, {}, alice.address);
+        const { output: bal } = await pair.query.balanceOfAddress(alice.address, {}, alice.address);
         expect(bal?.eq(expectedLiquidity - MINIMUM_LIQUIDITY)).toBeTruthy();
         const { output: bal0 } = await token0.query.balanceOf(alice.address, {}, pair.address);
         expect(bal0?.eq(token0Amount)).toBeTruthy();
@@ -90,10 +89,8 @@ describe.skip('UniswapV2Pair', () => {
         const { output: reserves } = await pair.query.getReserves(alice.address, {});
         // surely there must be a better way.
         let v: any = reserves;
-        let v0: Codec = v._reserve0;
-        expect(v0.eq(token0Amount)).toBeTruthy();
-        let v1: Codec = v._reserve1;
-        expect(v1.eq(token1Amount)).toBeTruthy();
+        expect(v[0].eq(token0Amount)).toBeTruthy();
+        expect(v[1].eq(token1Amount)).toBeTruthy();
     })
 
     async function addLiquidity(token0Amount: BigInt, token1Amount: BigInt) {
@@ -123,10 +120,8 @@ describe.skip('UniswapV2Pair', () => {
         const { output: reserves } = await pair.query.getReserves(alice.address, {});
         // surely there must be a better way.
         let v: any = reserves;
-        let v0: Codec = v._reserve0;
-        expect(v0.eq(token0Amount + swapAmount)).toBeTruthy();
-        let v1: Codec = v._reserve1;
-        expect(v1.eq(token1Amount - expectedOutputAmount)).toBeTruthy();
+        expect(v[0].eq(token0Amount + swapAmount)).toBeTruthy();
+        expect(v[1].eq(token1Amount - expectedOutputAmount)).toBeTruthy();
 
         const { output: bal0 } = await token0.query.balanceOf(alice.address, {}, pair.address);
         expect(bal0?.eq(token0Amount + swapAmount)).toBeTruthy();
@@ -162,10 +157,8 @@ describe.skip('UniswapV2Pair', () => {
         const { output: reserves } = await pair.query.getReserves(alice.address, {});
         // surely there must be a better way.
         let v: any = reserves;
-        let v0: Codec = v._reserve0;
-        expect(v0.eq(token0Amount - expectedOutputAmount)).toBeTruthy();
-        let v1: Codec = v._reserve1;
-        expect(v1.eq(token1Amount + swapAmount)).toBeTruthy();
+        expect(v[0].eq(token0Amount - expectedOutputAmount)).toBeTruthy();
+        expect(v[1].eq(token1Amount + swapAmount)).toBeTruthy();
 
         const { output: bal0 } = await token0.query.balanceOf(alice.address, {}, pair.address);
         expect(bal0?.eq(token0Amount - expectedOutputAmount)).toBeTruthy();
@@ -191,13 +184,13 @@ describe.skip('UniswapV2Pair', () => {
 
         const expectedLiquidity = BigInt(3e18)
 
-        let tx = pair.tx.transfer({ gasLimit }, pair.address, expectedLiquidity - MINIMUM_LIQUIDITY);
+        let tx = pair.tx.transferAddressUint256({ gasLimit }, pair.address, expectedLiquidity - MINIMUM_LIQUIDITY);
         await transaction(tx, alice);
 
         tx = pair.tx.burn({ gasLimit }, alice.address);
         await transaction(tx, alice);
 
-        const { output: walletBal0 } = await pair.query.balanceOf(alice.address, {}, alice.address);
+        const { output: walletBal0 } = await pair.query.balanceOfAddress(alice.address, {}, alice.address);
         expect(walletBal0?.eq(0)).toBeTruthy();
 
         const { output: pairTotalSupply } = await pair.query.totalSupply(alice.address, {});

+ 1 - 3
integration/substrate/arrays.sol

@@ -13,9 +13,7 @@ contract arrays {
 		Permission[] perms;
 	}
 
-	// declare a sparse array. Sparse arrays are arrays which are too large to
-	// fit into account data on Solana; this is not neccessarily a Solidity feature
-	user[type(uint64).max] users;
+	user[2048] users;
 
 	mapping (bytes32 => uint64) addressToUser;
 

+ 1 - 1
integration/substrate/arrays.spec.ts

@@ -29,7 +29,7 @@ describe('Deploy arrays contract and test', () => {
         for (let i = 0; i < 3; i++) {
             let addr = '0x' + crypto.randomBytes(32).toString('hex');
             let name = `name${i}`;
-            let id = crypto.randomBytes(4).readUInt32BE(0);
+            let id = crypto.randomBytes(4).readUInt32BE(0) % 1024;
             let perms: string[] = [];
 
             for (let j = 0; j < Math.random() * 3; j++) {

+ 8 - 9
integration/substrate/balances.spec.ts

@@ -40,21 +40,20 @@ describe('Deploy balances contract and test', () => {
 
         let { data: { free: daveBal1 } } = await conn.query.system.account(dave.address);
 
-        // REGRESSION metadata
-        // let tx1 = contract.tx.transfer({ gasLimit }, dave.address, 20000);
+        let tx1 = contract.tx.transfer({ gasLimit }, dave.address, 20000);
 
-        // await transaction(tx1, alice);
+        await transaction(tx1, alice);
 
-        // let { data: { free: daveBal2 } } = await conn.query.system.account(dave.address);
+        let { data: { free: daveBal2 } } = await conn.query.system.account(dave.address);
 
-        // expect(daveBal2.toBigInt()).toEqual(daveBal1.toBigInt() + 20000n);
+        expect(daveBal2.toBigInt()).toEqual(daveBal1.toBigInt() + 20000n);
 
-        // let tx2 = contract.tx.send({ gasLimit }, dave.address, 10000);
+        let tx2 = contract.tx.send({ gasLimit }, dave.address, 10000);
 
-        // await transaction(tx2, alice);
+        await transaction(tx2, alice);
 
-        // let { data: { free: daveBal3 } } = await conn.query.system.account(dave.address);
+        let { data: { free: daveBal3 } } = await conn.query.system.account(dave.address);
 
-        // expect(daveBal3.toBigInt()).toEqual(daveBal2.toBigInt() + 10000n);
+        expect(daveBal3.toBigInt()).toEqual(daveBal2.toBigInt() + 10000n);
     });
 });

+ 10 - 14
integration/substrate/destruct.spec.ts

@@ -29,23 +29,19 @@ describe('Deploy destruct contract and test', () => {
 
         expect(hello.output?.toJSON()).toBe('Hello');
 
-        // REGRESSION metadata #666
-        // let { data: { free: daveBalBefore } } = await conn.query.system.account(dave.address);
-        // let { data: { free: contractBalBefore } } = await conn.query.system.account(String(deploy_contract.address));
+        let { data: { free: daveBalBefore } } = await conn.query.system.account(dave.address);
+        let { data: { free: contractBalBefore } } = await conn.query.system.account(String(deploy_contract.address));
 
-        // let tx = contract.tx.selfterminate({ gasLimit }, dave.address);
+        let tx = contract.tx.selfterminate({ gasLimit }, dave.address);
 
-        // await transaction(tx, alice);
+        await transaction(tx, alice);
 
-        // let { data: { free: daveBalAfter } } = await conn.query.system.account(dave.address);
-        // let { data: { free: contractBalAfter } } = await conn.query.system.account(String(deploy_contract.address));
+        let { data: { free: daveBalAfter } } = await conn.query.system.account(dave.address);
+        let { data: { free: contractBalAfter } } = await conn.query.system.account(String(deploy_contract.address));
 
-        // //console.log(`bal ${daveBalBefore} and ${daveBalAfter}`);
-        // //console.log(`bal ${contractBalBefore} and ${contractBalAfter}`);
-
-        // // The contact is gone and has no balance
-        // expect(contractBalAfter.toBigInt()).toBe(0n);
-        // // Dave now has the balance previously held by the contract
-        // expect(daveBalAfter.toBigInt()).toEqual(daveBalBefore.toBigInt() + contractBalBefore.toBigInt());
+        // The contact is gone and has no balance
+        expect(contractBalAfter.toBigInt()).toBe(0n);
+        // Dave now has the balance previously held by the contract
+        expect(daveBalAfter.toBigInt()).toEqual(daveBalBefore.toBigInt() + contractBalBefore.toBigInt());
     });
 });

+ 1 - 2
integration/substrate/events.spec.ts

@@ -39,7 +39,6 @@ describe('Deploy events contract and test', () => {
 
         expect(events[1].event.identifier).toBe("foo2");
         expect(events[1].event.docs).toEqual(["Event Foo2\n\nJust a test\n\nAuthor: them is me"]);
-        // REGRESSION metadata
-        // expect(events[1].args.map(a => a.toJSON())).toEqual(["0x7fffffffffffffff", "minor", deploy_contract.address.toString()]);
+        expect(events[1].args.map(a => a.toJSON())).toEqual(["0x7fffffffffffffff", "minor", deploy_contract.address.toString()]);
     });
 });

+ 23 - 24
integration/substrate/external_call.spec.ts

@@ -19,50 +19,49 @@ describe('Deploy external_call contract and test', () => {
 
         const alice = aliceKeypair();
 
-        // REGRESSION metadata #666
-        // // call the constructors
-        // let caller_res = await deploy(conn, alice, 'caller.contract', BigInt(0));
+        // call the constructors
+        let caller_res = await deploy(conn, alice, 'caller.contract', BigInt(0));
 
-        // let caller_contract = new ContractPromise(conn, caller_res.abi, caller_res.address);
+        let caller_contract = new ContractPromise(conn, caller_res.abi, caller_res.address);
 
-        // let callee_res = await deploy(conn, alice, 'callee.contract', BigInt(0));
+        let callee_res = await deploy(conn, alice, 'callee.contract', BigInt(0));
 
-        // let callee_contract = new ContractPromise(conn, callee_res.abi, callee_res.address);
+        let callee_contract = new ContractPromise(conn, callee_res.abi, callee_res.address);
 
-        // let callee2_res = await deploy(conn, alice, 'callee2.contract', BigInt(0));
+        let callee2_res = await deploy(conn, alice, 'callee2.contract', BigInt(0));
 
-        // let callee2_contract = new ContractPromise(conn, callee2_res.abi, callee2_res.address);
+        let callee2_contract = new ContractPromise(conn, callee2_res.abi, callee2_res.address);
 
-        // let tx1 = callee_contract.tx.setX({ gasLimit }, 102);
+        let tx1 = callee_contract.tx.setX({ gasLimit }, 102);
 
-        // await transaction(tx1, alice);
+        await transaction(tx1, alice);
 
-        // let res1 = await callee_contract.query.getX(alice.address, {});
+        let res1 = await callee_contract.query.getX(alice.address, {});
 
-        // expect(res1.output?.toJSON()).toStrictEqual(102);
+        expect(res1.output?.toJSON()).toStrictEqual(102);
 
-        // let res2 = await caller_contract.query.whoAmI(alice.address, {});
+        let res2 = await caller_contract.query.whoAmI(alice.address, {});
 
-        // expect(res2.output?.toString()).toEqual(caller_res.address.toString());
+        expect(res2.output?.toString()).toEqual(caller_res.address.toString());
 
-        // let tx2 = caller_contract.tx.doCall({ gasLimit }, callee_contract.address, 13123);
+        let tx2 = caller_contract.tx.doCall({ gasLimit }, callee_contract.address, 13123);
 
-        // await transaction(tx2, alice);
+        await transaction(tx2, alice);
 
-        // let res3 = await callee_contract.query.getX(alice.address, {});
+        let res3 = await callee_contract.query.getX(alice.address, {});
 
-        // expect(res3.output?.toJSON()).toStrictEqual(13123);
+        expect(res3.output?.toJSON()).toStrictEqual(13123);
 
-        // let res4 = await caller_contract.query.doCall2(alice.address, {}, callee_contract.address, 20000);
+        let res4 = await caller_contract.query.doCall2(alice.address, {}, callee_contract.address, 20000);
 
-        // expect(res4.output?.toJSON()).toStrictEqual(33123);
+        expect(res4.output?.toJSON()).toStrictEqual(33123);
 
-        // let res5 = await caller_contract.query.doCall3(alice.address, {}, callee_contract.address, callee2_contract.address, [3, 5, 7, 9], "yo");
+        let res5 = await caller_contract.query.doCall3(alice.address, {}, callee_contract.address, callee2_contract.address, [3, 5, 7, 9], "yo");
 
-        // expect(res5.output?.toJSON()).toEqual([24, "my name is callee"]);
+        expect(res5.output?.toJSON()).toEqual([24, "my name is callee"]);
 
-        // let res6 = await caller_contract.query.doCall4(alice.address, {}, callee_contract.address, callee2_contract.address, [1, 2, 3, 4], "asda");
+        let res6 = await caller_contract.query.doCall4(alice.address, {}, callee_contract.address, callee2_contract.address, [1, 2, 3, 4], "asda");
 
-        // expect(res6.output?.toJSON()).toEqual([10, "x:asda"]);
+        expect(res6.output?.toJSON()).toEqual([10, "x:asda"]);
     });
 });

+ 3 - 10
integration/substrate/issue666.spec.ts

@@ -23,18 +23,11 @@ describe('issue666 flip and inc', () => {
         let flipper_contract = await deploy(conn, alice, 'Flip.contract', BigInt(0));
         let inc_contract = await deploy(conn, alice, 'Inc.contract', BigInt(0), flipper_contract.address);
 
-        try {
-            // This works for ink contracts and should work for solang compiled contracts too (issue 666):
-            let ss58_addr = flipper_contract.address.toString();
-            await deploy(conn, alice, 'Inc.contract', BigInt(0), ss58_addr);
-            expect(false).toBeTruthy();
-        }
-        catch (satan) {
-            expect(satan).toStrictEqual(Error('createType(AccountId):: Expected input with 32 bytes (256 bits), found 48 bytes'));
-        }
+        let ss58_addr = flipper_contract.address.toString();
+        await deploy(conn, alice, 'Inc.contract', BigInt(0), ss58_addr);
 
         let contract = new ContractPromise(conn, inc_contract.abi, inc_contract.address);
 
-        let tx = contract.tx.superFlip({ gasLimit });
+        contract.tx.superFlip({ gasLimit });
     });
 });

+ 5 - 7
integration/substrate/msg_sender.spec.ts

@@ -23,12 +23,11 @@ describe('Deploy mytoken contract and test', () => {
         let deployed_contract = await deploy(conn, alice, 'mytoken.contract', BigInt(0));
         let contract = new ContractPromise(conn, deployed_contract.abi, deployed_contract.address);
 
-        // REGRESSION metadata #666
-        // let res = await contract.query.test(alice.address, {}, alice.address, true);
-        // expect(res.output?.toJSON()).toEqual(alice.address);
+        let res = await contract.query.test(alice.address, {}, alice.address, true);
+        expect(res.output?.toJSON()).toEqual(alice.address);
 
-        // res = await contract.query.test(alice.address, {}, alice.address, false);
-        // expect(res.output?.toJSON()).toEqual(alice.address);
+        res = await contract.query.test(alice.address, {}, alice.address, false);
+        expect(res.output?.toJSON()).toEqual(alice.address);
     });
 
     it('mytokenEvent', async function () {
@@ -46,7 +45,6 @@ describe('Deploy mytoken contract and test', () => {
         expect(events.length).toEqual(1);
 
         expect(events[0].event.identifier).toBe("Debugging");
-        // REGRESSION metadata #666
-        // expect(events[0].args.map(a => a.toJSON())).toEqual([alice.address]);
+        expect(events[0].args.map(a => a.toJSON())).toEqual([alice.address]);
     });
 });

+ 5 - 5
integration/substrate/package.json

@@ -20,10 +20,10 @@
     "typescript": "^4.7"
   },
   "dependencies": {
-    "@polkadot/api": "^8.13",
-    "@polkadot/api-contract": "^8.13",
-    "@polkadot/types": "^8.13",
-    "@polkadot/keyring": "^10.0",
-    "@polkadot/util-crypto": "^10.0"
+    "@polkadot/api": "^9.7",
+    "@polkadot/api-contract": "^9.7",
+    "@polkadot/types": "^9.7",
+    "@polkadot/keyring": "^10.1",
+    "@polkadot/util-crypto": "^10.1"
   }
 }

+ 6 - 7
integration/substrate/primitives.spec.ts

@@ -131,14 +131,13 @@ describe('Deploy primitives contract and test', () => {
         // TEST address type.
         const default_account = '5GBWmgdFAMqm8ZgAHGobqDqX6tjLxJhv53ygjNtaaAn3sjeZ';
 
-        // RERGRESSION metadata #666
-        //res = await contract.query.addressPassthrough(alice.address, {}, default_account);
-        //expect(res.output?.toJSON()).toEqual(default_account);
+        res = await contract.query.addressPassthrough(alice.address, {}, default_account);
+        expect(res.output?.toJSON()).toEqual(default_account);
 
-        //res = await contract.query.addressPassthrough(alice.address, {}, dave.address);
-        //expect(res.output?.toJSON()).toEqual(dave.address);
+        res = await contract.query.addressPassthrough(alice.address, {}, dave.address);
+        expect(res.output?.toJSON()).toEqual(dave.address);
 
-        //res = await contract.query.addressPassthrough(alice.address, {}, alice.address);
-        //expect(res.output?.toJSON()).toEqual(alice.address);
+        res = await contract.query.addressPassthrough(alice.address, {}, alice.address);
+        expect(res.output?.toJSON()).toEqual(alice.address);
     });
 });

+ 2 - 1
integration/substrate/store.spec.ts

@@ -3,7 +3,8 @@ import { gasLimit, createConnection, deploy, transaction, aliceKeypair, } from '
 import { ContractPromise } from '@polkadot/api-contract';
 import { ApiPromise } from '@polkadot/api';
 
-// REGRESION metadata #666
+// TODO:
+// This apparently works with subxt.
 describe.skip('Deploy store contract and test', () => {
     let conn: ApiPromise;
 

+ 1 - 2
integration/substrate/structs.spec.ts

@@ -3,8 +3,7 @@ import { gasLimit, createConnection, deploy, transaction, aliceKeypair, } from '
 import { ContractPromise } from '@polkadot/api-contract';
 import { ApiPromise } from '@polkadot/api';
 
-// REGRESSION metadata #666
-describe.skip('Deploy struct contract and test', () => {
+describe('Deploy struct contract and test', () => {
     let conn: ApiPromise;
 
     before(async function () {

+ 429 - 590
src/abi/substrate.rs

@@ -1,284 +1,468 @@
 // SPDX-License-Identifier: Apache-2.0
-
-// Parity Substrate style ABIs/Abi
-use crate::sema::ast;
-use crate::sema::tags::render;
-use contract_metadata::*;
+use contract_metadata::{
+    CodeHash, Compiler, Contract, ContractMetadata, Language, Source, SourceCompiler,
+    SourceLanguage, SourceWasm,
+};
+use ink_metadata::{
+    layout::{FieldLayout, Layout, LayoutKey, LeafLayout, RootLayout, StructLayout},
+    ConstructorSpec, ContractSpec, EventParamSpec, EventSpec, InkProject, MessageParamSpec,
+    MessageSpec, ReturnTypeSpec, TypeSpec,
+};
+
+use itertools::Itertools;
+use serde_json::Value;
+
+use num_bigint::BigInt;
 use num_traits::ToPrimitive;
+use scale_info::{
+    form::PortableForm, Field, Path, PortableRegistryBuilder, Type, TypeDef, TypeDefArray,
+    TypeDefComposite, TypeDefPrimitive, TypeDefSequence, TypeDefTuple, TypeDefVariant, Variant,
+};
 use semver::Version;
-use serde::{Deserialize, Serialize};
-use serde_json::{Map, Value};
 use solang_parser::pt;
-use std::convert::TryInto;
 
 use super::non_unique_function_names;
-
-#[derive(Deserialize, Serialize)]
-pub struct Abi {
-    storage: Storage,
-    types: Vec<Type>,
-    pub spec: Spec,
-}
-
-impl Abi {
-    pub fn get_function(&self, name: &str) -> Option<&Message> {
-        self.spec.messages.iter().find(|m| name == m.name)
+use crate::sema::{
+    ast::{self, ArrayLength, EventDecl, Function},
+    tags::render,
+};
+
+macro_rules! path {
+    ($( $segments:expr ),*) => {
+        Path::from_segments_unchecked([$($segments),*].iter().map(ToString::to_string))
     }
 }
 
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
-pub struct ArrayDef {
-    array: Array,
-}
-
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
-pub struct Array {
-    len: usize,
-    #[serde(rename = "type")]
-    ty: usize,
-}
-
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
-pub struct SequenceDef {
-    sequence: Sequence,
-}
-
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
-pub struct Sequence {
-    #[serde(rename = "type")]
-    ty: usize,
-}
-
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
-#[serde(untagged)]
-enum Type {
-    Builtin { def: PrimitiveDef },
-    BuiltinArray { def: ArrayDef },
-    BuiltinSequence { def: SequenceDef },
-    Struct { path: Vec<String>, def: Composite },
-    Enum { path: Vec<String>, def: EnumDef },
-}
-
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
-struct BuiltinType {
-    id: String,
-    def: String,
-}
-
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
-struct EnumVariant {
-    name: String,
-    discriminant: usize,
-}
-
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
-struct EnumDef {
-    variant: Enum,
-}
-
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
-struct Enum {
-    variants: Vec<EnumVariant>,
-}
-
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
-struct Composite {
-    composite: StructFields,
-}
-
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
-struct StructFields {
-    fields: Vec<StructField>,
-}
-
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
-struct PrimitiveDef {
-    primitive: String,
+fn primitive_to_ty(ty: &ast::Type, registry: &mut PortableRegistryBuilder) -> u32 {
+    match ty {
+        ast::Type::Int(_) | ast::Type::Uint(_) => int_to_ty(ty, registry),
+        ast::Type::Bool => registry.register_type(Type::new(
+            path!("bool"),
+            vec![],
+            TypeDef::Primitive(TypeDefPrimitive::Bool),
+            Default::default(),
+        )),
+        ast::Type::String => registry.register_type(Type::new(
+            path!("string"),
+            vec![],
+            TypeDef::Primitive(TypeDefPrimitive::Str),
+            Default::default(),
+        )),
+        _ => unreachable!("non primitive types"),
+    }
 }
 
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
-struct StructField {
-    #[serde(skip_serializing_if = "Option::is_none")]
-    name: Option<String>,
-    #[serde(rename = "type")]
-    ty: usize,
+fn int_to_ty(ty: &ast::Type, registry: &mut PortableRegistryBuilder) -> u32 {
+    let (signed, scalety) = match ty {
+        ast::Type::Uint(n) => ('u', n.next_power_of_two()),
+        ast::Type::Int(n) => ('i', n.next_power_of_two()),
+        _ => unreachable!(),
+    };
+    let def = match (signed, scalety) {
+        ('u', n) => match n {
+            8 => TypeDefPrimitive::U8,
+            16 => TypeDefPrimitive::U16,
+            32 => TypeDefPrimitive::U32,
+            64 => TypeDefPrimitive::U64,
+            128 => TypeDefPrimitive::U128,
+            256 => TypeDefPrimitive::U256,
+            _ => unreachable!(),
+        },
+        ('i', n) => match n {
+            8 => TypeDefPrimitive::I8,
+            16 => TypeDefPrimitive::I16,
+            32 => TypeDefPrimitive::I32,
+            64 => TypeDefPrimitive::I64,
+            128 => TypeDefPrimitive::I128,
+            256 => TypeDefPrimitive::I256,
+
+            _ => unreachable!(),
+        },
+        _ => {
+            unreachable!()
+        }
+    };
+    let path = path!(format!("{signed}{scalety}"));
+    let ty = Type::new(path, vec![], TypeDef::Primitive(def), Default::default());
+    registry.register_type(ty)
 }
 
-#[derive(Deserialize, Serialize)]
-pub struct Constructor {
-    pub name: String,
-    pub selector: String,
-    pub docs: Vec<String>,
-    args: Vec<Param>,
-}
+/// Given an `ast::Type`, find and register the `scale_info::Type` definition in the registry
+fn resolve_ast(ty: &ast::Type, ns: &ast::Namespace, registry: &mut PortableRegistryBuilder) -> u32 {
+    match ty {
+        //  should reflect address_length for different substrate runtime
+        ast::Type::Address(_) | ast::Type::Contract(_) => {
+            // substituted to [u8; address_length]
+            let address_ty = resolve_ast(
+                &ast::Type::Array(
+                    Box::new(ast::Type::Uint(8)),
+                    vec![ArrayLength::Fixed(BigInt::from(ns.address_length))],
+                ),
+                ns,
+                registry,
+            );
+            // substituted to struct { AccountId }
+            let field = Field::new(None, address_ty.into(), None, vec![]);
+            let c = TypeDefComposite::new(vec![field]);
+            let path = path!("ink_env", "types", "AccountId");
+            let ty: Type<PortableForm> =
+                Type::new(path, vec![], TypeDef::Composite(c), Default::default());
+            registry.register_type(ty)
+        }
+        // primitive types
+        ast::Type::Bool | ast::Type::Int(_) | ast::Type::Uint(_) | ast::Type::String => {
+            primitive_to_ty(ty, registry)
+        }
+        // resolve from the deepest element to outside
+        // [[A; a: usize]; b: usize] -> Array(A_id, vec![a, b])
+        ast::Type::Array(ty, dims) => {
+            let mut ty = resolve_ast(ty, ns, registry);
+            for d in dims {
+                if let ast::ArrayLength::Fixed(d) = d {
+                    let def = TypeDefArray::new(d.to_u32().unwrap(), ty.into());
+
+                    // resolve current depth
+                    ty = registry.register_type(Type::new(
+                        Default::default(),
+                        vec![],
+                        TypeDef::Array(def),
+                        Default::default(),
+                    ));
+                } else {
+                    let def = TypeDefSequence::new(ty.into());
+
+                    // resolve current depth
+                    ty = registry.register_type(Type::new(
+                        Default::default(),
+                        vec![],
+                        TypeDef::Sequence(def),
+                        Default::default(),
+                    ));
+                }
+            }
+            ty
+        }
+        // substituted to [u8; len]
+        ast::Type::Bytes(n) => resolve_ast(
+            &ast::Type::Array(
+                Box::new(ast::Type::Uint(8)),
+                vec![ArrayLength::Fixed(BigInt::from(*n as i8))],
+            ),
+            ns,
+            registry,
+        ),
+        // substituted to Vec<u8>
+        ast::Type::DynamicBytes => resolve_ast(
+            &ast::Type::Array(Box::new(ast::Type::Uint(8)), vec![ArrayLength::Dynamic]),
+            ns,
+            registry,
+        ),
+        ast::Type::Struct(s) => {
+            let def = s.definition(ns);
+            let fields = def
+                .fields
+                .iter()
+                .map(|f| {
+                    let f_ty = resolve_ast(&f.ty, ns, registry);
 
-impl Constructor {
-    /// Build byte string from
-    pub fn selector(&self) -> Vec<u8> {
-        parse_selector(&self.selector)
+                    Field::new(Some(f.name_as_str().to_string()), f_ty.into(), None, vec![])
+                })
+                .collect::<Vec<Field<PortableForm>>>();
+            let c = TypeDefComposite::new(fields);
+            let path = path!(&def.name);
+            let ty = Type::new(path, vec![], TypeDef::Composite(c), Default::default());
+            registry.register_type(ty)
+        }
+        ast::Type::Enum(n) => {
+            let decl = &ns.enums[*n];
+            let mut variants = decl.values.iter().collect_vec();
+            // sort by discriminant
+            variants.sort_by(|a, b| a.1 .1.cmp(&b.1 .1));
+            let variants = variants
+                .into_iter()
+                .map(|(k, v)| Variant {
+                    name: k.clone(),
+                    fields: Default::default(),
+                    index: v.1 as u8,
+                    docs: Default::default(),
+                })
+                .collect::<Vec<_>>();
+            let variant = TypeDef::Variant(TypeDefVariant::new(variants));
+            let path = path!(&decl.name);
+            let ty = Type::new(path, vec![], variant, Default::default());
+            registry.register_type(ty)
+        }
+        ast::Type::Ref(ty) => resolve_ast(ty, ns, registry),
+        ast::Type::StorageRef(_, ty) => resolve_ast(ty, ns, registry),
+        ast::Type::InternalFunction { .. } => resolve_ast(&ast::Type::Uint(8), ns, registry),
+        ast::Type::ExternalFunction { .. } => {
+            let fields = [ast::Type::Address(false), ast::Type::Uint(32)]
+                .into_iter()
+                .map(|ty| {
+                    let ty = resolve_ast(&ty, ns, registry);
+
+                    Field::new(
+                        Default::default(),
+                        ty.into(),
+                        Default::default(),
+                        Default::default(),
+                    )
+                })
+                .collect::<Vec<_>>();
+            let composite = TypeDef::Composite(TypeDefComposite::new(fields));
+            let path = path!("ExternalFunction");
+            let ty = Type::new(path, vec![], composite, Default::default());
+            registry.register_type(ty)
+        }
+        ast::Type::UserType(no) => {
+            let decl = &ns.user_types[*no];
+            let resolved = resolve_ast(&decl.ty, ns, registry);
+            match (decl.name.as_ref(), decl.loc) {
+                // Builtin Hash type from ink primitives
+                ("Hash", pt::Loc::Builtin) => {
+                    let field = Field::new(None, resolved.into(), None, vec![]);
+                    let composite = TypeDef::Composite(TypeDefComposite::new([field]));
+                    let path = path!("ink_env", "types", "Hash");
+                    registry.register_type(Type::new(path, vec![], composite, vec![]))
+                }
+                _ => resolved,
+            }
+        }
+        ast::Type::Mapping(k, v) => {
+            resolve_ast(k, ns, registry);
+            resolve_ast(v, ns, registry)
+        }
+        _ => unreachable!(),
     }
 }
 
-#[derive(Deserialize, Serialize)]
-pub struct Message {
-    pub name: String,
-    pub selector: String,
-    pub docs: Vec<String>,
-    mutates: bool,
-    payable: bool,
-    args: Vec<Param>,
-    return_type: Option<ParamType>,
-}
-
-impl Message {
-    /// Build byte string from
-    pub fn selector(&self) -> Vec<u8> {
-        parse_selector(&self.selector)
+/// Recursively build the storage layout after all types are registered
+fn type_to_storage_layout(
+    key: u32,
+    root: &LayoutKey,
+    registry: &PortableRegistryBuilder,
+) -> Layout<PortableForm> {
+    let ty = registry.get(key).unwrap();
+    match ty.type_def() {
+        TypeDef::Composite(inner) => Layout::Struct(StructLayout::new(
+            ty.path().ident().unwrap_or_default(),
+            inner.fields().iter().map(|field| {
+                FieldLayout::new(
+                    field.name().map(ToString::to_string).unwrap_or_default(),
+                    type_to_storage_layout(field.ty().id(), root, registry),
+                )
+            }),
+        )),
+        _ => Layout::Leaf(LeafLayout::new(*root, key.into())),
     }
 }
 
-#[derive(Deserialize, Serialize)]
-pub struct Event {
-    docs: Vec<String>,
-    name: String,
-    args: Vec<ParamIndexed>,
-}
+/// Generate `InkProject` from `ast::Type` and `ast::Namespace`
+pub fn gen_project(contract_no: usize, ns: &ast::Namespace) -> InkProject {
+    let mut registry = PortableRegistryBuilder::new();
 
-#[derive(Deserialize, Serialize)]
-pub struct Spec {
-    pub constructors: Vec<Constructor>,
-    pub messages: Vec<Message>,
-    pub events: Vec<Event>,
-}
+    // This is only used by off-chain tooling. At the moment there is no such tooling available yet.
+    // So it is not exactly clear yet what this should look like.
+    // For now it just contains all root layouts (you get all storage keys in use).
+    let fields: Vec<FieldLayout<PortableForm>> = ns.contracts[contract_no]
+        .layout
+        .iter()
+        .filter_map(|layout| {
+            let var = &ns.contracts[layout.contract_no].variables[layout.var_no];
+            if let Some(slot) = layout.slot.to_u32() {
+                let ty = resolve_ast(&layout.ty, ns, &mut registry);
+                let layout_key = LayoutKey::new(slot);
+                let root = RootLayout::new(
+                    layout_key,
+                    type_to_storage_layout(ty, &layout_key, &registry),
+                );
+                Some(FieldLayout::new(var.name.clone(), root))
+            } else {
+                None
+            }
+        })
+        .collect();
+    let contract_name = ns.contracts[contract_no].name.clone();
+    let storage = Layout::Struct(StructLayout::new(contract_name, fields));
 
-#[derive(Deserialize, Serialize)]
-struct Param {
-    name: String,
-    #[serde(rename = "type")]
-    ty: ParamType,
-}
+    let constructor_spec = |f: &Function| -> ConstructorSpec<PortableForm> {
+        let payable = matches!(f.mutability, ast::Mutability::Payable(_));
+        let args = f
+            .params
+            .iter()
+            .map(|p| {
+                let ty = resolve_ast(&p.ty, ns, &mut registry);
 
-#[derive(Deserialize, Serialize)]
-struct ParamIndexed {
-    #[serde(flatten)]
-    param: Param,
-    indexed: bool,
-}
+                let path = registry.get(ty).unwrap().path().clone();
+                let spec = TypeSpec::new(ty.into(), path);
 
-#[derive(Deserialize, Serialize)]
-struct ParamType {
-    #[serde(rename = "type")]
-    ty: usize,
-    display_name: Vec<String>,
-}
+                MessageParamSpec::new(p.name_as_str().to_string())
+                    .of_type(spec)
+                    .done()
+            })
+            .collect::<Vec<MessageParamSpec<PortableForm>>>();
+
+        ConstructorSpec::from_label(if f.name.is_empty() { "new" } else { &f.name }.into())
+            .selector(f.selector().try_into().unwrap())
+            .payable(payable)
+            .args(args)
+            .docs(vec![render(&f.tags).as_str()])
+            .returns(ReturnTypeSpec::new(None))
+            .done()
+    };
 
-#[derive(Deserialize, Serialize)]
-struct Storage {
-    #[serde(rename = "struct")]
-    structs: StorageStruct,
-}
+    let constructors = ns.contracts[contract_no]
+        .functions
+        .iter()
+        .filter_map(|i| {
+            // include functions of type constructor
+            let f = &ns.functions[*i];
+            if f.is_constructor() {
+                Some(f)
+            } else {
+                None
+            }
+        })
+        .chain(
+            // include default constructor if exists
+            ns.contracts[contract_no]
+                .default_constructor
+                .as_ref()
+                .map(|(e, _)| e),
+        )
+        .map(constructor_spec)
+        .collect::<Vec<ConstructorSpec<PortableForm>>>();
 
-#[derive(Deserialize, Serialize)]
-struct StorageStruct {
-    fields: Vec<StorageLayout>,
-}
+    let conflicting_names = non_unique_function_names(contract_no, ns);
 
-#[derive(Deserialize, Serialize)]
-struct StorageLayout {
-    name: String,
-    layout: LayoutField,
-}
+    let message_spec = |f: &Function| -> MessageSpec<PortableForm> {
+        let payable = matches!(f.mutability, ast::Mutability::Payable(_));
+        let mutates = matches!(
+            f.mutability,
+            ast::Mutability::Payable(_) | ast::Mutability::Nonpayable(_)
+        );
+        let ret_spec: Option<TypeSpec<PortableForm>> = match f.returns.len() {
+            0 => None,
+            1 => {
+                let ty = resolve_ast(&f.returns[0].ty, ns, &mut registry);
+                let path = registry.get(ty).unwrap().path().clone();
+                Some(TypeSpec::new(ty.into(), path))
+            }
+            _ => {
+                let fields = f
+                    .returns
+                    .iter()
+                    .map(|r_p| {
+                        let ty = resolve_ast(&r_p.ty, ns, &mut registry);
 
-#[derive(Deserialize, Serialize)]
-struct LayoutField {
-    cell: LayoutFieldCell,
-}
+                        ty.into()
+                    })
+                    .collect::<Vec<_>>();
 
-#[derive(Deserialize, Serialize)]
-struct LayoutFieldCell {
-    key: String,
-    ty: usize,
-}
+                let t = TypeDefTuple::new_portable(fields);
 
-/// Create a new registry and create new entries. Note that the registry is
-/// accessed by number, and the first entry is 1, not 0.
-impl Abi {
-    /// Add a type to the list unless already present
-    fn register_ty(&mut self, ty: Type) -> usize {
-        match self.types.iter().position(|t| *t == ty) {
-            Some(i) => i + 1,
-            None => {
-                self.types.push(ty);
-
-                self.types.len()
-            }
-        }
-    }
+                let path = path!(&ns.contracts[contract_no].name, &f.name, "return_type");
 
-    /// Returns index to builtin type in registry. Type is added if not already present
-    fn builtin_type(&mut self, ty: &str) -> usize {
-        self.register_ty(Type::Builtin {
-            def: PrimitiveDef {
-                primitive: ty.to_owned(),
-            },
-        })
-    }
+                let ty = registry.register_type(Type::new(
+                    path,
+                    vec![],
+                    TypeDef::Tuple(t),
+                    Default::default(),
+                ));
+                let path = registry.get(ty).unwrap().path().clone();
+                Some(TypeSpec::new(ty.into(), path))
+            }
+        };
+        let ret_type = ReturnTypeSpec::new(ret_spec);
+        let args = f
+            .params
+            .iter()
+            .map(|p| {
+                let ty = resolve_ast(&p.ty, ns, &mut registry);
+                let path = registry.get(ty).unwrap().path().clone();
+                let spec = TypeSpec::new(ty.into(), path);
+
+                MessageParamSpec::new(p.name_as_str().to_string())
+                    .of_type(spec)
+                    .done()
+            })
+            .collect::<Vec<MessageParamSpec<PortableForm>>>();
+        let label = if conflicting_names.contains(&f.name) {
+            &f.mangled_name
+        } else {
+            &f.name
+        };
+        MessageSpec::from_label(label.into())
+            .selector(f.selector().try_into().unwrap())
+            .mutates(mutates)
+            .payable(payable)
+            .args(args)
+            .returns(ret_type)
+            .docs(vec![render(&f.tags)])
+            .done()
+    };
 
-    /// Returns index to builtin type in registry. Type is added if not already present
-    fn builtin_array_type(&mut self, elem: usize, array_len: usize) -> usize {
-        self.register_ty(Type::BuiltinArray {
-            def: ArrayDef {
-                array: Array {
-                    len: array_len,
-                    ty: elem,
-                },
-            },
+    let messages = ns.contracts[contract_no]
+        .all_functions
+        .keys()
+        .filter_map(|function_no| {
+            let func = &ns.functions[*function_no];
+            // libraries are never in the public interface
+            if let Some(base_contract_no) = func.contract_no {
+                if ns.contracts[base_contract_no].is_library() {
+                    return None;
+                }
+            }
+            Some(func)
         })
-    }
-
-    /// Returns index to builtin type in registry. Type is added if not already present
-    fn builtin_slice_type(&mut self, elem: usize) -> usize {
-        self.register_ty(Type::BuiltinSequence {
-            def: SequenceDef {
-                sequence: Sequence { ty: elem },
-            },
+        .filter(|f| match f.visibility {
+            pt::Visibility::Public(_) | pt::Visibility::External(_) => matches!(
+                f.ty,
+                pt::FunctionTy::Function | pt::FunctionTy::Fallback | pt::FunctionTy::Receive
+            ),
+            _ => false,
         })
-    }
+        .map(message_spec)
+        .collect::<Vec<MessageSpec<PortableForm>>>();
 
-    /// Returns index to builtin type in registry. Type is added if not already present
-    fn builtin_enum_type(&mut self, e: &ast::EnumDecl) -> usize {
-        let mut variants: Vec<EnumVariant> = e
-            .values
+    let mut event_spec = |e: &EventDecl| -> EventSpec<PortableForm> {
+        let args = e
+            .fields
             .iter()
-            .map(|(key, val)| EnumVariant {
-                name: key.to_owned(),
-                discriminant: val.1,
+            .map(|p| {
+                let ty = resolve_ast(&p.ty, ns, &mut registry);
+                let path = registry.get(ty).unwrap().path().clone();
+                let spec = TypeSpec::new(ty.into(), path);
+                EventParamSpec::new(p.name_as_str().into())
+                    .of_type(spec)
+                    .indexed(p.indexed)
+                    .docs(vec![])
+                    .done()
             })
-            .collect();
-
-        variants.sort_by(|a, b| a.discriminant.partial_cmp(&b.discriminant).unwrap());
+            .collect::<Vec<_>>();
+        EventSpec::new(e.name.clone())
+            .args(args)
+            .docs(vec![render(&e.tags)])
+            .done()
+    };
 
-        self.register_ty(Type::Enum {
-            path: vec![e.name.to_owned()],
-            def: EnumDef {
-                variant: Enum { variants },
-            },
+    let events = ns.contracts[contract_no]
+        .sends_events
+        .iter()
+        .map(|event_no| {
+            let event = &ns.events[*event_no];
+            event_spec(event)
         })
-    }
+        .collect::<Vec<EventSpec<PortableForm>>>();
 
-    /// Adds struct type to registry. Does not check for duplication (yet)
-    fn struct_type(&mut self, path: Vec<String>, fields: Vec<StructField>) -> usize {
-        self.register_ty(Type::Struct {
-            path,
-            def: Composite {
-                composite: StructFields { fields },
-            },
-        })
-    }
-}
+    let spec = ContractSpec::new()
+        .constructors(constructors)
+        .messages(messages)
+        .events(events)
+        .docs(vec![render(&ns.contracts[contract_no].tags)])
+        .done();
 
-pub fn load(bs: &str) -> Result<Abi, serde_json::error::Error> {
-    serde_json::from_str(bs)
+    InkProject::new_portable(storage, spec, registry.finish())
 }
 
 fn tags(contract_no: usize, tagname: &str, ns: &ast::Namespace) -> Vec<String> {
@@ -295,7 +479,7 @@ fn tags(contract_no: usize, tagname: &str, ns: &ast::Namespace) -> Vec<String> {
         .collect()
 }
 
-/// Generate the metadata for Substrate 2.0
+/// Generate the metadata for Substrate 4.0
 pub fn metadata(contract_no: usize, code: &[u8], ns: &ast::Namespace) -> Value {
     let hash = blake2_rfc::blake2b::blake2b(32, &[], code);
     let version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
@@ -303,371 +487,26 @@ pub fn metadata(contract_no: usize, code: &[u8], ns: &ast::Namespace) -> Value {
     let compiler = SourceCompiler::new(Compiler::Solang, version);
     let code_hash: [u8; 32] = hash.as_bytes().try_into().unwrap();
     let source_wasm = SourceWasm::new(code.to_vec());
-
     let source = Source::new(Some(source_wasm), CodeHash(code_hash), language, compiler);
-    let mut builder = Contract::builder();
 
-    // Add our name and tags
+    let mut builder = Contract::builder();
     builder.name(&ns.contracts[contract_no].name);
-
     let mut description = tags(contract_no, "title", ns);
-
     description.extend(tags(contract_no, "notice", ns));
-
     if !description.is_empty() {
         builder.description(description.join("\n"));
     };
-
     let authors = tags(contract_no, "author", ns);
-
     if !authors.is_empty() {
         builder.authors(authors);
     } else {
         builder.authors(vec!["unknown"]);
     }
-
-    // FIXME: contract-metadata wants us to provide a version number, but there is no version in the solidity source
-    // code. Since we must provide a valid semver version, we just provide a bogus value.Abi
     builder.version(Version::new(0, 0, 1));
-
     let contract = builder.build().unwrap();
 
-    // generate the abi for our contract
-    let abi = gen_abi(contract_no, ns);
-
-    let mut abi_json: Map<String, Value> = Map::new();
-    abi_json.insert(
-        String::from("types"),
-        serde_json::to_value(&abi.types).unwrap(),
-    );
-    abi_json.insert(
-        String::from("spec"),
-        serde_json::to_value(&abi.spec).unwrap(),
-    );
-    abi_json.insert(
-        String::from("storage"),
-        serde_json::to_value(&abi.storage).unwrap(),
-    );
-
-    let metadata = ContractMetadata::new(source, contract, None, abi_json);
-
-    // serialize to json
-    serde_json::to_value(&metadata).unwrap()
-}
-
-fn gen_abi(contract_no: usize, ns: &ast::Namespace) -> Abi {
-    let mut abi = Abi {
-        types: Vec::new(),
-        storage: Storage {
-            structs: StorageStruct { fields: Vec::new() },
-        },
-        spec: Spec {
-            constructors: Vec::new(),
-            messages: Vec::new(),
-            events: Vec::new(),
-        },
-    };
-
-    let fields = ns.contracts[contract_no]
-        .layout
-        .iter()
-        .filter_map(|layout| {
-            let var = &ns.contracts[layout.contract_no].variables[layout.var_no];
-
-            // mappings and large types cannot be represented
-            if !var.ty.contains_mapping(ns) && var.ty.fits_in_memory(ns) {
-                Some(StorageLayout {
-                    name: var.name.to_string(),
-                    layout: LayoutField {
-                        cell: LayoutFieldCell {
-                            key: format!("0x{:064X}", layout.slot),
-                            ty: ty_to_abi(&var.ty, ns, &mut abi).ty,
-                        },
-                    },
-                })
-            } else {
-                None
-            }
-        })
-        .collect();
-
-    abi.storage.structs.fields = fields;
-
-    let conflicting_names = non_unique_function_names(contract_no, ns);
-
-    let mut constructors = ns.contracts[contract_no]
-        .functions
-        .iter()
-        .filter_map(|function_no| {
-            let f = &ns.functions[*function_no];
-            let name = if conflicting_names.contains(&f.name) {
-                f.mangled_name.clone()
-            } else {
-                f.name.clone()
-            };
-            if f.is_constructor() {
-                Some(Constructor {
-                    name,
-                    selector: render_selector(f),
-                    args: f
-                        .params
-                        .iter()
-                        .map(|p| parameter_to_abi(p, ns, &mut abi))
-                        .collect(),
-                    docs: vec![render(&f.tags)],
-                })
-            } else {
-                None
-            }
-        })
-        .collect::<Vec<Constructor>>();
-
-    if let Some((f, _)) = &ns.contracts[contract_no].default_constructor {
-        constructors.push(Constructor {
-            name: String::from("new"),
-            selector: render_selector(f),
-            args: f
-                .params
-                .iter()
-                .map(|p| parameter_to_abi(p, ns, &mut abi))
-                .collect(),
-            docs: vec![render(&f.tags)],
-        });
-    }
-
-    let messages = ns.contracts[contract_no]
-        .all_functions
-        .keys()
-        .filter_map(|function_no| {
-            let func = &ns.functions[*function_no];
-
-            if let Some(base_contract_no) = func.contract_no {
-                if ns.contracts[base_contract_no].is_library() {
-                    return None;
-                }
-            }
-
-            Some(func)
-        })
-        .filter(|f| match f.visibility {
-            pt::Visibility::Public(_) | pt::Visibility::External(_) => {
-                f.ty == pt::FunctionTy::Function
-            }
-            _ => false,
-        })
-        .map(|f| {
-            let payable = matches!(f.mutability, ast::Mutability::Payable(_));
-
-            Message {
-                name: if conflicting_names.contains(&f.name) {
-                    &f.mangled_name
-                } else {
-                    &f.name
-                }
-                .into(),
-                mutates: matches!(
-                    f.mutability,
-                    ast::Mutability::Payable(_) | ast::Mutability::Nonpayable(_)
-                ),
-                payable,
-                return_type: match f.returns.len() {
-                    0 => None,
-                    1 => Some(ty_to_abi(&f.returns[0].ty, ns, &mut abi)),
-                    _ => {
-                        let fields = f
-                            .returns
-                            .iter()
-                            .map(|f| StructField {
-                                name: f.id.as_ref().map(|id| id.name.to_owned()),
-                                ty: ty_to_abi(&f.ty, ns, &mut abi).ty,
-                            })
-                            .collect();
-
-                        Some(ParamType {
-                            ty: abi.struct_type(Vec::new(), fields),
-                            display_name: vec![],
-                        })
-                    }
-                },
-                selector: render_selector(f),
-                args: f
-                    .params
-                    .iter()
-                    .map(|p| parameter_to_abi(p, ns, &mut abi))
-                    .collect(),
-                docs: vec![render(&f.tags)],
-            }
-        })
-        .collect();
-
-    let events = ns.contracts[contract_no]
-        .sends_events
-        .iter()
-        .map(|event_no| {
-            let event = &ns.events[*event_no];
-
-            let name = event.name.to_owned();
-            let args = event
-                .fields
-                .iter()
-                .map(|p| ParamIndexed {
-                    param: parameter_to_abi(p, ns, &mut abi),
-                    indexed: p.indexed,
-                })
-                .collect();
-            let docs = vec![render(&event.tags)];
-
-            Event { docs, name, args }
-        })
-        .collect();
-
-    abi.spec = Spec {
-        constructors,
-        messages,
-        events,
-    };
-
-    abi
-}
-
-fn ty_to_abi(ty: &ast::Type, ns: &ast::Namespace, registry: &mut Abi) -> ParamType {
-    match ty {
-        ast::Type::Enum(n) => ParamType {
-            ty: registry.builtin_enum_type(&ns.enums[*n]),
-            display_name: vec![ns.enums[*n].name.to_owned()],
-        },
-        ast::Type::Bytes(n) => {
-            let elem = registry.builtin_type("u8");
-            ParamType {
-                ty: registry.builtin_array_type(elem, *n as usize),
-                display_name: vec![],
-            }
-        }
-        ast::Type::Mapping(..) => unreachable!(),
-        ast::Type::Array(ty, dims) => {
-            let mut param_ty = ty_to_abi(ty, ns, registry);
-
-            for d in dims {
-                if let ast::ArrayLength::Fixed(d) = d {
-                    param_ty = ParamType {
-                        ty: registry.builtin_array_type(param_ty.ty, d.to_usize().unwrap()),
-                        display_name: vec![],
-                    }
-                } else {
-                    param_ty = ParamType {
-                        ty: registry.builtin_slice_type(param_ty.ty),
-                        display_name: vec![],
-                    }
-                }
-            }
-
-            param_ty
-        }
-        ast::Type::StorageRef(_, ty) => ty_to_abi(ty, ns, registry),
-        ast::Type::Ref(ty) => ty_to_abi(ty, ns, registry),
-        ast::Type::UserType(no) => ty_to_abi(&ns.user_types[*no].ty, ns, registry),
-        ast::Type::Bool | ast::Type::Uint(_) | ast::Type::Int(_) => {
-            let scalety = match ty {
-                ast::Type::Bool => "bool".into(),
-                // Substrate doesn't like primitive types which aren't a power of 2
-                // The abi encoder/decoder fixes this automatically
-                ast::Type::Uint(n) => format!("u{}", n.next_power_of_two()),
-                ast::Type::Int(n) => format!("i{}", n.next_power_of_two()),
-                _ => unreachable!(),
-            };
-
-            ParamType {
-                ty: registry.builtin_type(&scalety),
-                display_name: vec![scalety.to_string()],
-            }
-        }
-        ast::Type::Address(_) | ast::Type::Contract(_) => {
-            let elem = registry.builtin_type("u8");
-            let ty = registry.builtin_array_type(elem, 32);
-
-            ParamType {
-                ty: registry.struct_type(
-                    vec!["AccountId".to_owned()],
-                    vec![StructField { name: None, ty }],
-                ),
-                display_name: vec!["AccountId".to_owned()],
-            }
-        }
-        ast::Type::Struct(struct_type) => {
-            let mut display_name = vec![struct_type.definition(ns).name.to_owned()];
-
-            if let Some(contract_name) = &struct_type.definition(ns).contract {
-                display_name.insert(0, contract_name.to_owned());
-            }
-
-            let def = struct_type.definition(ns);
-            let fields = def
-                .fields
-                .iter()
-                .map(|f| StructField {
-                    name: Some(f.name_as_str().to_owned()),
-                    ty: ty_to_abi(&f.ty, ns, registry).ty,
-                })
-                .collect();
-
-            ParamType {
-                ty: registry.struct_type(display_name.clone(), fields),
-                display_name,
-            }
-        }
-        ast::Type::DynamicBytes => {
-            let elem = registry.builtin_type("u8");
-
-            ParamType {
-                ty: registry.builtin_slice_type(elem),
-                display_name: vec![String::from("Vec")],
-            }
-        }
-        ast::Type::String => ParamType {
-            ty: registry.builtin_type("str"),
-            display_name: vec![String::from("String")],
-        },
-        ast::Type::InternalFunction { .. } => ParamType {
-            ty: registry.builtin_type("u32"),
-            display_name: vec![String::from("FunctionSelector")],
-        },
-        ast::Type::ExternalFunction { .. } => {
-            let fields = vec![
-                StructField {
-                    name: None,
-                    ty: ty_to_abi(&ast::Type::Address(false), ns, registry).ty,
-                },
-                StructField {
-                    name: None,
-                    ty: ty_to_abi(&ast::Type::Uint(32), ns, registry).ty,
-                },
-            ];
-
-            let display_name = vec![String::from("ExternalFunction")];
-
-            ParamType {
-                ty: registry.struct_type(display_name.clone(), fields),
-                display_name,
-            }
-        }
-        _ => unreachable!(),
-    }
-}
-
-fn parameter_to_abi(param: &ast::Parameter, ns: &ast::Namespace, registry: &mut Abi) -> Param {
-    Param {
-        name: param.name_as_str().to_owned(),
-        ty: ty_to_abi(&param.ty, ns, registry),
-    }
-}
-
-/// Given an u32 selector, generate a byte string like: 0xF81E7E1A
-fn render_selector(f: &ast::Function) -> String {
-    format!("0x{}", hex::encode(f.selector()))
-}
+    let project_json = serde_json::to_value(gen_project(contract_no, ns)).unwrap();
+    let abi = serde_json::from_value(project_json).unwrap();
 
-/// Given a selector like "0xF81E7E1A", parse the bytes. This function
-/// does not validate the input.
-fn parse_selector(selector: &str) -> Vec<u8> {
-    hex::decode(&selector[2..]).unwrap()
+    serde_json::to_value(ContractMetadata::new(source, contract, None, abi)).unwrap()
 }

+ 12 - 5
src/sema/namespace.rs

@@ -760,10 +760,10 @@ impl Namespace {
         id: &pt::Expression,
         diagnostics: &mut Diagnostics,
     ) -> Result<Type, ()> {
-        fn resolve_dimensions(
-            ast_dimensions: &[Option<(pt::Loc, BigInt)>],
-            diagnostics: &mut Diagnostics,
-        ) -> Result<Vec<ArrayLength>, ()> {
+        let is_substrate = self.target.is_substrate();
+
+        let resolve_dimensions = |ast_dimensions: &[Option<(pt::Loc, BigInt)>],
+                                  diagnostics: &mut Diagnostics| {
             let mut dimensions = Vec::new();
 
             for d in ast_dimensions.iter().rev() {
@@ -780,6 +780,13 @@ impl Namespace {
                             "negative size of array declared".to_string(),
                         ));
                         return Err(());
+                    } else if is_substrate && n > &u32::MAX.into() {
+                        let msg = format!(
+                            "array dimension of {} exceeds the maximum of 4294967295 on Substrate",
+                            n
+                        );
+                        diagnostics.push(Diagnostic::decl_error(*loc, msg));
+                        return Err(());
                     }
                     dimensions.push(ArrayLength::Fixed(n.clone()));
                 } else {
@@ -788,7 +795,7 @@ impl Namespace {
             }
 
             Ok(dimensions)
-        }
+        };
 
         let (namespace, id, dimensions) =
             self.expr_to_type(file_no, contract_no, id, diagnostics)?;

+ 1 - 1
tests/contract_testcases/substrate/variables/variable_size.dot

@@ -1,7 +1,7 @@
 strict digraph "tests/contract_testcases/substrate/variables/variable_size.sol" {
 	contract [label="contract x\ntests/contract_testcases/substrate/variables/variable_size.sol:1:1-3:10"]
 	diagnostic [label="found contract 'x'\nlevel Debug\ntests/contract_testcases/substrate/variables/variable_size.sol:1:1-3:10"]
-	diagnostic_4 [label="type is too large to fit into memory\nlevel Error\ntests/contract_testcases/substrate/variables/variable_size.sol:2:26-45"]
+	diagnostic_4 [label="array dimension of 12131231313213 exceeds the maximum of 4294967295 on Substrate\nlevel Error\ntests/contract_testcases/substrate/variables/variable_size.sol:2:30-44"]
 	contracts -> contract
 	diagnostics -> diagnostic [label="Debug"]
 	diagnostics -> diagnostic_4 [label="Error"]

+ 1 - 1
tests/contract_testcases/substrate/variables/variable_size_01.dot

@@ -1,7 +1,7 @@
 strict digraph "tests/contract_testcases/substrate/variables/variable_size_01.sol" {
 	contract [label="contract x\ntests/contract_testcases/substrate/variables/variable_size_01.sol:1:1-3:10"]
 	diagnostic [label="found contract 'x'\nlevel Debug\ntests/contract_testcases/substrate/variables/variable_size_01.sol:1:1-3:10"]
-	diagnostic_4 [label="type is too large to fit into memory\nlevel Error\ntests/contract_testcases/substrate/variables/variable_size_01.sol:2:44-63"]
+	diagnostic_4 [label="array dimension of 12131231313213 exceeds the maximum of 4294967295 on Substrate\nlevel Error\ntests/contract_testcases/substrate/variables/variable_size_01.sol:2:48-62"]
 	contracts -> contract
 	diagnostics -> diagnostic [label="Debug"]
 	diagnostics -> diagnostic_4 [label="Error"]

+ 55 - 11
tests/substrate.rs

@@ -1,5 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
+use contract_metadata::ContractMetadata;
+use ink_metadata::InkProject;
 // Create WASM virtual machine like substrate
 use num_derive::FromPrimitive;
 use num_traits::FromPrimitive;
@@ -10,7 +12,6 @@ use tiny_keccak::{Hasher, Keccak};
 use wasmi::memory_units::Pages;
 use wasmi::*;
 
-use solang::abi;
 use solang::file_resolver::FileResolver;
 use solang::{compile, Target};
 
@@ -112,9 +113,10 @@ impl VirtualMachine {
 }
 
 pub struct Program {
-    abi: abi::substrate::Abi,
+    abi: InkProject,
     code: Vec<u8>,
 }
+
 pub struct MockSubstrate {
     pub store: HashMap<(Account, StorageKey), Vec<u8>>,
     pub programs: Vec<Program>,
@@ -1000,11 +1002,20 @@ impl MockSubstrate {
     }
 
     pub fn constructor(&mut self, index: usize, args: Vec<u8>) {
-        let m = &self.programs[self.current_program].abi.spec.constructors[index];
+        let m = &self.programs[self.current_program]
+            .abi
+            .spec()
+            .constructors()[index];
 
         let module = self.create_module(&self.accounts.get(&self.vm.account).unwrap().0);
 
-        self.vm.input = m.selector().into_iter().chain(args).collect();
+        self.vm.input = m
+            .selector()
+            .to_bytes()
+            .iter()
+            .copied()
+            .chain(args)
+            .collect();
 
         let ret = self.invoke_deploy(module);
 
@@ -1016,11 +1027,20 @@ impl MockSubstrate {
     }
 
     pub fn constructor_expect_return(&mut self, index: usize, expected_ret: i32, args: Vec<u8>) {
-        let m = &self.programs[self.current_program].abi.spec.constructors[index];
+        let m = &self.programs[self.current_program]
+            .abi
+            .spec()
+            .constructors()[index];
 
         let module = self.create_module(&self.accounts.get(&self.vm.account).unwrap().0);
 
-        self.vm.input = m.selector().into_iter().chain(args).collect();
+        self.vm.input = m
+            .selector()
+            .to_bytes()
+            .iter()
+            .copied()
+            .chain(args)
+            .collect();
 
         let ret = self.invoke_deploy(module);
 
@@ -1039,12 +1059,21 @@ impl MockSubstrate {
     pub fn function(&mut self, name: &str, args: Vec<u8>) {
         let m = self.programs[self.current_program]
             .abi
-            .get_function(name)
+            .spec()
+            .messages()
+            .iter()
+            .find(|f| f.label() == name)
             .unwrap();
 
         let module = self.create_module(&self.accounts.get(&self.vm.account).unwrap().0);
 
-        self.vm.input = m.selector().into_iter().chain(args).collect();
+        self.vm.input = m
+            .selector()
+            .to_bytes()
+            .iter()
+            .copied()
+            .chain(args)
+            .collect();
 
         println!("input:{}", hex::encode(&self.vm.input));
 
@@ -1056,12 +1085,21 @@ impl MockSubstrate {
     pub fn function_expect_failure(&mut self, name: &str, args: Vec<u8>) {
         let m = self.programs[self.current_program]
             .abi
-            .get_function(name)
+            .spec()
+            .messages()
+            .iter()
+            .find(|m| m.label() == name)
             .unwrap();
 
         let module = self.create_module(&self.accounts.get(&self.vm.account).unwrap().0);
 
-        self.vm.input = m.selector().into_iter().chain(args).collect();
+        self.vm.input = m
+            .selector()
+            .to_bytes()
+            .iter()
+            .copied()
+            .chain(args)
+            .collect();
 
         match module.invoke_export("call", &[], self) {
             Err(wasmi::Error::Trap(trap)) => match trap.kind() {
@@ -1188,6 +1226,7 @@ impl MockSubstrate {
 pub fn build_solidity(src: &str) -> MockSubstrate {
     build_solidity_with_options(src, false, false)
 }
+
 pub fn build_solidity_with_options(
     src: &str,
     math_overflow_flag: bool,
@@ -1214,7 +1253,7 @@ pub fn build_solidity_with_options(
         .iter()
         .map(|res| Program {
             code: res.0.clone(),
-            abi: abi::substrate::load(&res.1).unwrap(),
+            abi: load_abi(&res.1),
         })
         .collect();
 
@@ -1236,3 +1275,8 @@ pub fn build_solidity_with_options(
         events: Vec::new(),
     }
 }
+
+fn load_abi(s: &str) -> InkProject {
+    let bundle = serde_json::from_str::<ContractMetadata>(s).unwrap();
+    serde_json::from_value::<InkProject>(serde_json::to_value(bundle.abi).unwrap()).unwrap()
+}

+ 8 - 8
tests/substrate_tests/arrays.rs

@@ -98,20 +98,20 @@ fn storage_arrays() {
     #[derive(Debug, PartialEq, Eq, Encode, Decode)]
     struct Val(i32);
     #[derive(Debug, PartialEq, Eq, Encode, Decode)]
-    struct SetArg(u64, i32);
+    struct SetArg(u32, i32);
     #[derive(Debug, PartialEq, Eq, Encode, Decode)]
-    struct GetArg(u64);
+    struct GetArg(u32);
 
     let mut runtime = build_solidity(
         r##"
         contract foo {
-            int32[8589934592] bigarray;
+            int32[type(uint32).max] bigarray;
 
-            function set(uint64 index, int32 val) public {
+            function set(uint32 index, int32 val) public {
                 bigarray[index] = val;
             }
 
-            function get(uint64 index) public returns (int32) {
+            function get(uint32 index) public returns (int32) {
                 return bigarray[index];
             }
         }"##,
@@ -122,7 +122,7 @@ fn storage_arrays() {
     let mut vals = Vec::new();
 
     for _ in 0..100 {
-        let index = rng.gen::<u64>() % 0x200_0000;
+        let index = rng.gen::<u32>();
         let val = rng.gen::<i32>();
 
         runtime.function("set", SetArg(index, val).encode());
@@ -334,10 +334,10 @@ fn array_dimensions() {
     let mut runtime = build_solidity(
         r##"
         contract storage_refs {
-            int32[2**16] a;
+            int32[32] a;
 
             function test() public {
-                assert(a.length == 65536);
+                assert(a.length == 32);
             }
         }"##,
     );

+ 9 - 9
tests/substrate_tests/contracts.rs

@@ -263,10 +263,10 @@ fn mangle_function_names_in_abi() {
         .get(0)
         .unwrap()
         .abi
-        .spec
-        .messages
+        .spec()
+        .messages()
         .iter()
-        .map(|m| m.name.clone())
+        .map(|m| m.label().clone())
         .collect();
 
     assert!(!messages.contains(&"foo".to_string()));
@@ -293,10 +293,10 @@ fn mangle_overloaded_function_names_in_abi() {
         .get(0)
         .unwrap()
         .abi
-        .spec
-        .messages
+        .spec()
+        .messages()
         .iter()
-        .map(|m| m.name.clone())
+        .map(|m| m.label().clone())
         .collect();
 
     assert!(messages_a.contains(&"foo".to_string()));
@@ -307,10 +307,10 @@ fn mangle_overloaded_function_names_in_abi() {
         .get(1)
         .unwrap()
         .abi
-        .spec
-        .messages
+        .spec()
+        .messages()
         .iter()
-        .map(|m| m.name.clone())
+        .map(|m| m.label().clone())
         .collect();
 
     assert!(!messages_b.contains(&"foo".to_string()));