Skip to content

Commit

Permalink
Merge pull request #53 from casper-ecosystem/feature/newTransferToUni…
Browse files Browse the repository at this point in the history
…qAddress

Feature/new transfer to uniq address
  • Loading branch information
hoffmannjan authored May 17, 2021
2 parents 5da919e + 43cf701 commit c20f886
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 24 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to casper-client-sdk.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 1.3.1

### Changed

- Added `newTransferToUniqAddress` and `UniqAddress`.
- Fix in `newTransfer` - `id` now can be `0`

## 1.3.0

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "casper-client-sdk",
"version": "1.3.0",
"version": "1.3.1",
"license": "Apache 2.0",
"description": "SDK to interact with the Casper blockchain",
"main": "dist/lib.node.js",
Expand Down
131 changes: 108 additions & 23 deletions src/lib/DeployUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@ import { RuntimeArgs } from './RuntimeArgs';
// import JSBI from 'jsbi';
import { DeployUtil, Keys, URef } from './index';
import { AsymmetricKey, SignatureAlgorithm } from './Keys';
import { BigNumberish } from '@ethersproject/bignumber';
import { BigNumber, BigNumberish } from '@ethersproject/bignumber';
import { jsonArrayMember, jsonMember, jsonObject, TypedJSON } from 'typedjson';
import { ByteArray } from 'tweetnacl-ts';
import { Result, Ok, Err } from 'ts-results';

const shortEnglishHumanizer = humanizeDuration.humanizer({
spacer: '',
serialComma: false,
conjunction: " ",
delimiter: " ",
conjunction: ' ',
delimiter: ' ',
language: 'shortEn',
languages: {
// https://docs.rs/humantime/2.0.1/humantime/fn.parse_duration.html
Expand Down Expand Up @@ -71,34 +71,76 @@ export const humanizerTTL = (ttl: number) => {
return shortEnglishHumanizer(ttl);
};


/**
* Returns duration in ms
* @param ttl in humanized string
*/
export const dehumanizerTTL = (ttl: string): number => {
const dehumanizeUnit = (s: string): number => {
if (s.includes("ms")) {
if (s.includes('ms')) {
return Number(s.replace('ms', ''));
};
}
if (s.includes('s') && !s.includes('m')) {
return Number(s.replace('s', '')) * 1000;
}
if (s.includes('m') && !s.includes('s')) {
return Number(s.replace('m', '')) * 60 * 1000;
return Number(s.replace('m', '')) * 60 * 1000;
}
if (s.includes('h')) {
return Number(s.replace('h', '')) * 60 * 60 * 1000;
return Number(s.replace('h', '')) * 60 * 60 * 1000;
}
if (s.includes('day')) {
return Number(s.replace('day', '')) * 24 * 60 * 60 * 1000;
return Number(s.replace('day', '')) * 24 * 60 * 60 * 1000;
}
throw Error("Unsuported TTL unit");
throw Error('Unsuported TTL unit');
};

return ttl.split(" ").map(dehumanizeUnit).reduce((acc, val) => acc += val);
return ttl
.split(' ')
.map(dehumanizeUnit)
.reduce((acc, val) => (acc += val));
};

export class UniqAddress {
publicKey: PublicKey;
transferId: BigNumber;

/**
* Constructs UniqAddress
* @param publicKey PublicKey instance
* @param transferId BigNumberish value (can be also string representing number). Max U64.
*/
constructor(publicKey: PublicKey, transferId: BigNumberish) {
if (!(publicKey instanceof PublicKey)) {
throw new Error('publicKey is not an instance of PublicKey');
}
const bigNum = BigNumber.from(transferId);
if (bigNum.gt('18446744073709551615')) {
throw new Error('transferId max value is U64');
}
this.transferId = bigNum;
this.publicKey = publicKey;
}

/**
* Returns string in format "accountHex-transferIdHex"
* @param ttl in humanized string
*/
toString(): string {
return `${this.publicKey.toAccountHex()}-${this.transferId.toHexString()}`;
}

/**
* Builds UniqAddress from string
* @param value value returned from UniqAddress.toString()
*/
static fromString(value: string): UniqAddress {
const [accountHex, transferHex] = value.split('-');
const publicKey = PublicKey.fromHex(accountHex);
return new UniqAddress(publicKey, transferHex);
}
}

@jsonObject
export class DeployHeader implements ToBytes {
@jsonMember({
Expand Down Expand Up @@ -673,7 +715,7 @@ export class ExecutableDeployItem implements ToBytes {
amount: BigNumberish,
target: URef | PublicKey,
sourcePurse: URef | null = null,
id: number
id: BigNumberish
) {
const runtimeArgs = RuntimeArgs.fromMap({});
runtimeArgs.insert('amount', CLValue.u512(amount));
Expand All @@ -687,8 +729,8 @@ export class ExecutableDeployItem implements ToBytes {
} else {
throw new Error('Please specify target');
}
if (!id) {
throw new Error("transfer-id missing in new transfer.");
if (id === undefined) {
throw new Error('transfer-id missing in new transfer.');
} else {
runtimeArgs.insert(
'id',
Expand All @@ -700,6 +742,47 @@ export class ExecutableDeployItem implements ToBytes {
);
}

/**
* Constructor for Transfer deploy item using UniqAddress.
* @param source PublicKey of source account
* @param target UniqAddress of target account
* @param amount The number of motes to transfer
* @param paymentAmount the number of motes paying to execution engine
* @param chainName Name of the chain, to avoid the `Deploy` from being accidentally or maliciously included in a different chain.
* @param gasPrice Conversion rate between the cost of Wasm opcodes and the motes sent by the payment code.
* @param ttl Time that the `Deploy` will remain valid for, in milliseconds. The default value is 1800000, which is 30 minutes
* @param sourcePurse URef of the source purse. If this is omitted, the main purse of the account creating this \
* transfer will be used as the source purse
*/
public static newTransferToUniqAddress(
source: PublicKey,
target: UniqAddress,
amount: BigNumberish,
paymentAmount: BigNumberish,
chainName: string,
gasPrice = 1,
ttl = 1800000,
sourcePurse?: URef
): Deploy {
const deployParams = new DeployUtil.DeployParams(
source,
chainName,
gasPrice,
ttl
);

const payment = DeployUtil.standardPayment(paymentAmount);

const session = DeployUtil.ExecutableDeployItem.newTransfer(
amount,
target.publicKey,
sourcePurse,
target.transferId
);

return DeployUtil.makeDeploy(deployParams, session, payment);
}

public isModuleBytes(): boolean {
return !!this.moduleBytes;
}
Expand Down Expand Up @@ -1015,35 +1098,37 @@ export const deploySizeInBytes = (deploy: Deploy): number => {
const hashSize = deploy.hash.length;
const bodySize = serializeBody(deploy.payment, deploy.session).length;
const headerSize = serializeHeader(deploy.header).length;
const approvalsSize = deploy.approvals.map(approval => {
return (approval.signature.length + approval.signer.length) / 2;
}).reduce((a, b) => a + b, 0);
const approvalsSize = deploy.approvals
.map(approval => {
return (approval.signature.length + approval.signer.length) / 2;
})
.reduce((a, b) => a + b, 0);

return hashSize + headerSize + bodySize + approvalsSize;
}
};

export const validateDeploy = (deploy: Deploy): Result<Deploy, string> => {
const serializedBody = serializeBody(deploy.payment, deploy.session);
const bodyHash = blake.blake2b(serializedBody, null, 32);

if(!arrayEquals(deploy.header.bodyHash, bodyHash)) {
if (!arrayEquals(deploy.header.bodyHash, bodyHash)) {
return Err(`Invalid deploy: bodyHash missmatch. Expected: ${bodyHash},
got: ${deploy.header.bodyHash}.`);
}

const serializedHeader = serializeHeader(deploy.header);
const deployHash = blake.blake2b(serializedHeader, null, 32);
if(!arrayEquals(deploy.hash, deployHash)) {

if (!arrayEquals(deploy.hash, deployHash)) {
return Err(`Invalid deploy: hash missmatch. Expected: ${deployHash},
got: ${deploy.hash}.`);
}

// TODO: Verify included signatures.

return Ok(deploy);
}
};

const arrayEquals = (a: Uint8Array, b: Uint8Array): boolean => {
return a.length === b.length && a.every((val, index) => val === b[index]);
}
};
58 changes: 58 additions & 0 deletions test/lib/DeployUtil.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,4 +275,62 @@ describe('DeployUtil', () => {

expect(badFn).to.throw('transfer-id missing in new transfer.');
});

it('newTransferToUniqAddress should construct proper deploy', () => {
const senderKey = Keys.Ed25519.new();
const recipientKey = Keys.Ed25519.new();
const networkName = 'test-network';
const paymentAmount = 10000000000000;
const transferAmount = 10;
const transferId = 34;

const uniqAddress = new DeployUtil.UniqAddress(recipientKey.publicKey, transferId);

let deploy = DeployUtil.ExecutableDeployItem.newTransferToUniqAddress(
senderKey.publicKey,
uniqAddress,
transferAmount,
paymentAmount,
networkName
);

deploy = DeployUtil.signDeploy(deploy, senderKey);

assert.isTrue(deploy.isTransfer());
assert.isTrue(deploy.isStandardPayment());
assert.deepEqual(deploy.header.account, senderKey.publicKey);
assert.deepEqual(
deploy.payment.getArgByName('amount')!.asBigNumber().toNumber(),
paymentAmount
);
assert.deepEqual(
deploy.session.getArgByName('amount')!.asBigNumber().toNumber(),
transferAmount
);
assert.deepEqual(
deploy.session.getArgByName('target')!.asBytesArray(),
recipientKey.accountHash()
);
assert.deepEqual(
deploy.session
.getArgByName('id')!
.asOption()
.getSome()
.asBigNumber()
.toNumber(),
transferId
);
});

it('DeployUtil.UniqAddress should serialize and deserialize', () => {
const recipientKey = Keys.Ed25519.new();
const hexAddress = recipientKey.publicKey.toAccountHex();
const transferId = "80172309";
const transferIdHex = "0x04c75515";

const uniqAddress = new DeployUtil.UniqAddress(recipientKey.publicKey, transferId);

expect(uniqAddress).to.be.instanceof(DeployUtil.UniqAddress);
expect(uniqAddress.toString()).to.be.eq(`${hexAddress}-${transferIdHex}`);
});
});

0 comments on commit c20f886

Please sign in to comment.