Skip to content
This repository was archived by the owner on Oct 18, 2024. It is now read-only.

Commit 0312108

Browse files
committed
add test and fix build options
1 parent d9390b9 commit 0312108

File tree

10 files changed

+503
-33
lines changed

10 files changed

+503
-33
lines changed

__fixtures__/amm/src/contract.js

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import { getBalance, sendCoins } from 'bank';
2+
3+
const store = (storeDefinition) => {
4+
return (target, state) => {
5+
Object.entries(storeDefinition).forEach(([key, value]) => {
6+
if (typeof value === 'function') {
7+
// For functions, create a getter and a setter
8+
Object.defineProperty(target, key, {
9+
get() {
10+
return (...args) => state.get([key, ...args].join('/')) ?? value(undefined); // TODO: support nested mappings
11+
},
12+
enumerable: true,
13+
});
14+
15+
const setterName = `set${key.charAt(0).toUpperCase() + key.slice(1)}`;
16+
Object.defineProperty(target, setterName, {
17+
value: (...args) => {
18+
const newValue = args[args.length - 1];
19+
const keys = args.slice(0, -1);
20+
state.set([key, ...keys].join('/'), newValue);
21+
},
22+
enumerable: false,
23+
});
24+
} else {
25+
// For non-functions, create a getter and a setter
26+
Object.defineProperty(target, key, {
27+
get() {
28+
return state.get(key) ?? value;
29+
},
30+
set(newValue) {
31+
state.set(key, newValue);
32+
},
33+
enumerable: true,
34+
});
35+
36+
const setterName = `set${key.charAt(0).toUpperCase() + key.slice(1)}`;
37+
Object.defineProperty(target, setterName, {
38+
value: (newValue) => {
39+
state.set(key, newValue);
40+
},
41+
enumerable: false,
42+
});
43+
}
44+
});
45+
};
46+
};
47+
48+
//////////////////////////////////////////////////////////////////////////////////
49+
50+
const useStore = store({
51+
totalSupply: 0,
52+
balance:address => 0,
53+
reserves: [0, 0],
54+
});
55+
// should construct
56+
// this.totalSupply // getter
57+
// this.setTotalSupply(value) // setter
58+
// this.balance(address) // getter
59+
// this.setBalance(address, value) // setter
60+
// this.reserves // getter
61+
// this.setReserves(value) // setter
62+
export default class AMMContract {
63+
constructor(state, {msg, address}) {
64+
this.msg = msg;
65+
this.address = address;
66+
67+
useStore(this, state);
68+
}
69+
70+
token0 = 'USDC';
71+
token1 = 'ATOM';
72+
73+
getTotalSupply() {
74+
return this.totalSupply;
75+
}
76+
77+
getBalance(address) {
78+
return this.balance(address);
79+
}
80+
81+
getReserves() {
82+
return this.reserves;
83+
}
84+
85+
#getBankBalance(address, token) {
86+
return getBalance(address, token);
87+
}
88+
89+
#mint(to, amount) {
90+
const balance = this.balance(to);
91+
this.setBalance(to, balance + amount);
92+
this.totalSupply += amount;
93+
}
94+
95+
#burn(from, amount) {
96+
const balance = this.balance(from);
97+
if (balance < amount) {
98+
throw Error('insufficient balance');
99+
}
100+
this.setBalance(from, balance - amount);
101+
this.totalSupply -= amount;
102+
}
103+
104+
#update(amount0, amount1) {
105+
const [reserve0, reserve1] = this.reserves;
106+
this.reserves = [
107+
reserve0 + amount0,
108+
reserve1 + amount1,
109+
];
110+
}
111+
112+
swap({tokenIn, amountIn}) {
113+
const isToken0 = tokenIn == this.token0;
114+
const isToken1 = tokenIn == this.token1;
115+
116+
if (!isToken0 && !isToken1) {
117+
throw Error('invalid token');
118+
}
119+
120+
const [reserve0, reserve1] = this.reserves;
121+
let tokenOut, reserveIn, reserveOut;
122+
123+
[tokenIn, tokenOut, reserveIn, reserveOut] =
124+
isToken0
125+
? [this.token0, this.token1, reserve0, reserve1]
126+
: [this.token1, this.token0, reserve1, reserve0];
127+
128+
sendCoins(this.msg.sender, this.address, {
129+
[tokenIn]: amountIn,
130+
});
131+
132+
const amountInWithFee = amountIn * 997 / 1000;
133+
const amountOut = (reserveOut * amountInWithFee) / (reserveIn + amountInWithFee);
134+
135+
sendCoins(this.address, this.msg.sender, {
136+
[tokenOut]: amountOut,
137+
});
138+
139+
this.#update(
140+
this.#getBankBalance(this.address, this.token0).amount,
141+
this.#getBankBalance(this.address, this.token1).amount,
142+
);
143+
144+
return amountOut;
145+
}
146+
147+
addLiquidity({amount0, amount1}) {
148+
sendCoins(this.msg.sender, this.address, {
149+
[this.token0]: amount0,
150+
[this.token1]: amount1,
151+
});
152+
153+
const [reserve0, reserve1] = this.reserves;
154+
155+
if (reserve0 > 0 || reserve1 > 0) {
156+
if (reserve0 * amount1 != reserve1 * amount0) {
157+
throw Error('invalid liquidity');
158+
}
159+
}
160+
161+
let shares = 0;
162+
if (this.totalSupply > 0) {
163+
shares = Math.sqrt(amount0 * amount1);
164+
} else {
165+
shares = Math.min(
166+
(amount0 * this.totalSupply) / reserve0,
167+
(amount1 * this.totalSupply) / reserve1,
168+
);
169+
}
170+
171+
this.#mint(this.msg.sender, shares);
172+
173+
this.#update(
174+
this.#getBankBalance(this.address, this.token0).amount,
175+
this.#getBankBalance(this.address, this.token1).amount,
176+
);
177+
178+
return shares;
179+
}
180+
181+
removeLiquidity({shares}) {
182+
const bal0 = this.#getBankBalance(this.address, this.token0);
183+
const bal1 = this.#getBankBalance(this.address, this.token1);
184+
const totalSupply = this.totalSupply;
185+
186+
const amount0 = bal0 * shares / totalSupply;
187+
const amount1 = bal1 * shares / totalSupply;
188+
this.#burn(this.msg.sender, shares);
189+
this.#update(bal0 - amount0, bal1 - amount1);
190+
sendCoins(this.address, this.msg.sender, {
191+
[this.token0]: amount0,
192+
[this.token1]: amount1,
193+
});
194+
195+
return [amount0, amount1];
196+
}
197+
}

__fixtures__/amm/src/index.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// top level middleware for JSD contracts
2+
3+
import * as contract from './contract.js';
4+
5+
function contractNamespaceObject() {
6+
// iterate over all properties of the contract object
7+
// ESM modules are not enumerable, use getOwnPropertyNames
8+
const exports = {};
9+
10+
for (const key of Object.getOwnPropertyNames(contract)) {
11+
if (key === 'default') {
12+
continue;
13+
}
14+
15+
exports[key] = contract[key];
16+
}
17+
18+
return exports;
19+
}
20+
21+
function callClassContract(state, functionName, body) {
22+
const instance = new contract.default(state, {msg: {sender: state.sender}, address: state.self});
23+
24+
return instance[functionName](body);
25+
}
26+
27+
export default function(state, functionName, body) {
28+
if (typeof functionName !== 'string') {
29+
return new Error('contract function name must be a string, got ' + typeof functionName);
30+
}
31+
32+
// module 'contract' exports contract as a class via the default export
33+
// TODO: check if contract is a class or just a function
34+
if (contract.default && typeof contract.default === 'function') {
35+
return callClassContract(state, functionName, body);
36+
}
37+
38+
const entry = contractNamespaceObject()[functionName];
39+
40+
if (typeof entry === 'undefined') {
41+
return new Error('contract function ' + functionName + ' not found: got undefined');
42+
}
43+
44+
if (typeof entry !== 'function' && !body) {
45+
return entry;
46+
}
47+
48+
if (typeof entry === 'function') {
49+
return entry(state, body);
50+
}
51+
52+
return new Error('contract function' + functionName + ' not found: got ' + typeof entry);
53+
}

0 commit comments

Comments
 (0)